Чтобы обобщить решение Джонатана Перлоу об обнаруженной им ошибке, я использую следующее в любом классе, использующем AsyncTask. Цикл / обработчик / публикация - это то, как вы можете запустить что-либо в потоке пользовательского интерфейса в любом месте приложения Android, не передавая дескриптор действия или другого контекста. Добавьте этот статический блок инициализации внутри класса:
{ // /3736361/onpostexecute-ne-vyzyvaetsya-v-asynctask-isklychenie-vremeni-vypolneniya-obrabotchika
Looper looper = Looper.getMainLooper();
Handler handler = new Handler(looper);
handler.post(new Runnable() {
public void run() {
try {
Class.forName("android.os.AsyncTask");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
});
}
Мы столкнулись с проблемой при попытке запустить модульные тесты. Я нашел обходной путь для этого, но конкретно не определил проблему. Мы только знали, что попытка использовать AsyncTask <> в тесте Android JUnit приводила к тому, что onPostExecute () не вызывался. Теперь мы знаем, почему.
В этом посте показано, как запустить многопоточный асинхронный код в тесте Android JUnit:
Использование CountDownLatch в Android-тестах JUnit на основе AsyncTask
Для использования с юнит-тестами без пользовательского интерфейса я создал простой подкласс android.test.InstrumentationTestCase. Он имеет флаг "ОК" и CountDownLatch. reset () или reset (count) создает новый CountDownLatch ({1, count}). good () устанавливает в защелку ok = true, count-- и Call.CountDown (). bad () устанавливает ok = false и ведет обратный отсчет. waitForIt (секунд) ожидает истечения времени ожидания или защелки coundown к нулю. Затем он вызывает assertTrue (ок).
Тогда тесты похожи на:
someTest() {
reset();
asyncCall(args, new someListener() {
public void success(args) { good(); }
public void fail(args) { bad(); }
});
waitForIt();
}
Из-за ошибки статической инициализации AsyncTask нам пришлось запускать наши реальные тесты внутри Runnable, переданного runTestOnUiThread (). При правильной статической инициализации, как указано выше, в этом нет необходимости, если только тестируемый вызов не должен выполняться в потоке пользовательского интерфейса.
Другая идиома, которую я сейчас использую, заключается в том, чтобы проверить, является ли текущий поток потоком пользовательского интерфейса, а затем выполнить запрошенное действие в соответствующем потоке независимо от этого. Иногда имеет смысл разрешить вызывающей стороне запрашивать синхронизацию и асинхронность, переопределяя при необходимости. Например, сетевые запросы всегда должны выполняться в фоновом потоке. В большинстве случаев пул потоков AsyncTask идеально подходит для этого. Просто поймите, что только определенное количество будет работать одновременно, блокируя дополнительные запросы. Чтобы проверить, является ли текущий поток потоком пользовательского интерфейса:
boolean onUiThread = Looper.getMainLooper().getThread() == Thread.currentThread();
Затем используйте простой подкласс (необходимы только doInBackground () и onPostExecute ()) AsyncTask <> для запуска в потоке, не являющемся пользовательским интерфейсом, или handler.post () или postDelayed () для запуска в потоке пользовательского интерфейса.
Предоставление вызывающей стороне опции для запуска sync или async выглядит следующим образом (получение локально действительного значения onUiThread, не показанного здесь; добавьте локальные логические значения, как указано выше):
void method(final args, sync, listener, callbakOnUi) {
Runnable run = new Runnable() { public void run() {
// method's code... using args or class members.
if (listener != null) listener(results);
// Or, if the calling code expects listener to run on the UI thread:
if (callbackOnUi && !onUiThread)
handler.post(new Runnable() { public void run() {listener()}});
else listener();
};
if (sync) run.run(); else new MyAsync().execute(run);
// Or for networking code:
if (sync && !onUiThread) run.run(); else new MyAsync().execute(run);
// Or, for something that has to be run on the UI thread:
if (sync && onUiThread) run.run() else handler.post(run);
}
Кроме того, использование AsyncTask можно сделать очень простым и лаконичным. Используйте определение RunAsyncTask.java ниже, затем напишите код, подобный этому:
RunAsyncTask rat = new RunAsyncTask("");
rat.execute(new Runnable() { public void run() {
doSomethingInBackground();
post(new Runnable() { public void run() { somethingOnUIThread(); }});
postDelayed(new Runnable() { public void run() { somethingOnUIThreadInABit(); }}, 100);
}});
Или просто: новый RunAsyncTask (""). Execute (new Runnable () {public void run () {doSomethingInBackground ();}});
RunAsyncTask.java:
package st.sdw;
import android.os.AsyncTask;
import android.util.Log;
import android.os.Debug;
public class RunAsyncTask extends AsyncTask<Runnable, String, Long> {
String TAG = "RunAsyncTask";
Object context = null;
boolean isDebug = false;
public RunAsyncTask(Object context, String tag, boolean debug) {
this.context = context;
TAG = tag;
isDebug = debug;
}
protected Long doInBackground(Runnable... runs) {
Long result = 0L;
long start = System.currentTimeMillis();
for (Runnable run : runs) {
run.run();
}
return System.currentTimeMillis() - start;
}
protected void onProgressUpdate(String... values) { }
protected void onPostExecute(Long time) {
if (isDebug && time > 1) Log.d(TAG, "RunAsyncTask ran in:" + time + " ms");
v = null;
}
protected void onPreExecute() { }
/** Walk heap, reliably triggering crash on native heap corruption. Call as needed. */
public static void memoryProbe() {
System.gc();
Runtime runtime = Runtime.getRuntime();
Double allocated = new Double(Debug.getNativeHeapAllocatedSize()) / 1048576.0;
Double available = new Double(Debug.getNativeHeapSize()) / 1048576.0;
Double free = new Double(Debug.getNativeHeapFreeSize()) / 1048576.0;
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
}
}