我有一个AsyncTask
来获取一些数据,然后用这个新数据更新UI。它已经工作好几个月,但我最近添加了一个功能,当有新数据时显示通知。现在,当我的应用程序通过通知启动时,有时我会收到此异常,并且不会调用onPostExecute
。
启动应用程序时会发生这种情况:
1)展开用户界面并查找视图
2)取消检查新数据并重置警报的警报(通过AlarmManager
)。 (这样,如果用户禁用警报,则在下次重新启动之前将其取消。)
3)启动AsyncTask
。如果应用程序是从通知启动的,请传入一些数据,然后取消通知。
我坚持可能导致此异常的原因。似乎异常来自AsyncTask
代码,所以我不确定如何解决它。
谢谢!
以下是例外:
I/My App( 501): doInBackground exiting
W/MessageQueue( 501): Handler{442ba140} sending message to a Handler on a dead thread
W/MessageQueue( 501): java.lang.RuntimeException: Handler{442ba140} sending message to a Handler on a dead thread
W/MessageQueue( 501): at android.os.MessageQueue.enqueueMessage(MessageQueue.java:179)
W/MessageQueue( 501): at android.os.Handler.sendMessageAtTime(Handler.java:457)
W/MessageQueue( 501): at android.os.Handler.sendMessageDelayed(Handler.java:430)
W/MessageQueue( 501): at android.os.Handler.sendMessage(Handler.java:367)
W/MessageQueue( 501): at android.os.Message.sendToTarget(Message.java:348)
W/MessageQueue( 501): at android.os.AsyncTask$3.done(AsyncTask.java:214)
W/MessageQueue( 501): at java.util.concurrent.FutureTask$Sync.innerSet(FutureTask.java:252)
W/MessageQueue( 501): at java.util.concurrent.FutureTask.set(FutureTask.java:112)
W/MessageQueue( 501): at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:310)
W/MessageQueue( 501): at java.util.concurrent.FutureTask.run(FutureTask.java:137)
W/MessageQueue( 501): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
W/MessageQueue( 501): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
W/MessageQueue( 501): at java.lang.Thread.run(Thread.java:1096)
编辑:这是我的主要活动中的onCreate
方法(通知打开的方法)。为了节省空间,省略了一些onClickListeners
。我不认为它们会产生任何影响,因为它们所连接的按钮没有按下。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Call the parent
setContentView(R.layout.main); // Create the UI from the XML file
// Find the UI elements
controls = (SlidingDrawer) findViewById(R.id.drawer); // Contains the
// buttons
// comic = (ImageView) findViewById(R.id.comic); // Displays the comic
subtitle = (TextView) findViewById(R.id.subtitleTxt); // Textbox for the
// subtitle
prevBtn = (Button) findViewById(R.id.prevBtn); // The previous button
nextBtn = (Button) findViewById(R.id.nextBtn); // The next button
randomBtn = (Button) findViewById(R.id.randomBtn); // The random button
fetchBtn = (Button) findViewById(R.id.comicFetchBtn); // The go to specific id button
mostRecentBtn = (Button) findViewById(R.id.mostRecentBtn); // The button to go to the most recent comic
comicNumberEdtTxt = (EditText) findViewById(R.id.comicNumberEdtTxt); // The text box to Zooming image view setup
zoomControl = new DynamicZoomControl();
zoomListener = new LongPressZoomListener(this);
zoomListener.setZoomControl(zoomControl);
zoomComic = (ImageZoomView) findViewById(R.id.zoomComic);
zoomComic.setZoomState(zoomControl.getZoomState());
zoomComic.setImage(BitmapFactory.decodeResource(getResources(), R.drawable.defaultlogo));
zoomComic.setOnTouchListener(zoomListener);
zoomControl.setAspectQuotient(zoomComic.getAspectQuotient());
resetZoomState();
// enter the new id
imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // Used to hide the soft keyboard
Log.i(LOG_TAG, "beginning loading of first comic");
int notificationComicNumber = getIntent().getIntExtra("comic", -1);
Log.i(LOG_TAG, "comic number from intent: " + notificationComicNumber);
if (notificationComicNumber == -1) {
fetch = new MyFetcher(this, zoomComic, subtitle, controls, comicNumberEdtTxt, imm, zoomControl);
fetch.execute(MyFetcher.LAST_DISPLAYED_COMIC);
} else {
fetch = new MyFetcher(this, zoomComic, subtitle, controls, comicNumberEdtTxt, imm, zoomControl);
fetch.execute(notificationComicNumber);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
}
Log.i(LOG_TAG, "ending loading of new comic");
Log.i(LOG_TAG, "first run checks beginning");
// Get SharedPreferences
prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);
// Check if this is the first run of the app for this version
if (prefs.getBoolean("firstRun-" + MAJOR_VERSION_NUMBER, true)) {
prefs.edit().putBoolean("firstRun-" + MAJOR_VERSION_NUMBER, false).commit();
firstRunVersionDialog();
}
// Check if this is the first run of the app
if (prefs.getBoolean("firstRun", true)) {
prefs.edit().putBoolean("firstRun", false).commit();
firstRunDialog();
}
Log.i(LOG_TAG, "First run checks done");
// OnClickListener s for the buttons omitted to save space
编辑2:我一直在挖掘Android源代码,追踪异常的来源。这是sendMessageAtTime
中Handler
的第456行和第457行:
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
这是来自enqueueMessage
的{{1}}:
MessageQueue
我对 final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg
+ " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
//Log.d("MessageQueue", "Enqueing: " + msg);
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
的内容感到有些困惑,但看起来前一次调用mQuiting
enqueueMessage
为空。
答案 0 :(得分:40)
这是由于Android框架中的AsyncTask中存在错误。 AsyncTask.java具有以下代码:
private static final InternalHandler sHandler = new InternalHandler();
它期望在主线程上初始化它,但是这不能保证,因为它将在导致类运行其静态初始化器的任何线程上初始化。我重现了Handler引用工作线程的问题。
导致这种情况发生的常见模式是使用IntentService类。 C2DM示例代码执行此操作。
一个简单的解决方法是将以下代码添加到应用程序的onCreate方法中:
Class.forName("android.os.AsyncTask");
这将强制在主线程中初始化AsyncTask。我在android bug数据库中提交了一个bug。请参阅http://code.google.com/p/android/issues/detail?id=20915。
答案 1 :(得分:18)
为了概括Jonathan Perlow针对他明确指出的错误的解决方案,我在任何使用AsyncTask的类中使用以下内容。 looper / handler / post是如何在Android应用程序中的任何位置运行UI线程而不传递活动或其他上下文句柄的方法。在类中添加此静态初始化块:
{ // https://stackoverflow.com/questions/4280330/onpostexecute-not-being-called-in-asynctask-handler-runtime-exception
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&lt;&gt;在Android JUnit测试中导致onPostExecute()不被调用。现在我们知道原因了。
这篇文章展示了如何在Android JUnit测试中运行多线程异步代码:
Using CountDownLatch in Android AsyncTask-based JUnit tests
为了与非UI单元测试一起使用,我创建了一个android.test.InstrumentationTestCase的简单子类。它有一个“ok”标志和一个CountDownLatch。 reset()或reset(count)创建一个新的CountDownLatch({1,count})。 good()在latch上设置ok = true,count--和calls.countDown()。 bad()设置ok = false,并一直倒计时。 waitForIt(seconds)等待超时或coundown latch为零。然后它调用assertTrue(ok)。
然后测试就像:
someTest() {
reset();
asyncCall(args, new someListener() {
public void success(args) { good(); }
public void fail(args) { bad(); }
});
waitForIt();
}
由于AsyncTask静态初始化错误,我们必须在传递给runTestOnUiThread()的Runnable中运行我们的实际测试。如上所述进行适当的静态初始化,除非正在测试的调用需要在UI线程上运行,否则这不是必需的。
我现在使用的另一个习惯是测试当前线程是否是UI线程,然后在适当的线程上运行所请求的操作,无论如何。有时,允许调用者请求同步与异步,在必要时覆盖是有意义的。例如,网络请求应始终在后台线程上运行。在大多数情况下,AsyncTask线程池是完美的。只是意识到只有一个数字会同时运行,阻止其他请求。要测试当前线程是否是UI线程:
boolean onUiThread = Looper.getMainLooper().getThread() == Thread.currentThread();
然后使用AsyncTask&lt;&gt;的简单子类(只需要doInBackground()和onPostExecute())在非UI线程或handler.post()或postDelayed()上运行以在UI线程上运行。
为调用者提供运行同步或异步的选项(获取本地未显示的本地有效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);
}});
或者简单地说:new 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();
}
}
答案 2 :(得分:2)
我在使用Android 4.0.4和IntentService的设备上遇到了同样的问题并解决了它,因为sdw用Class.forName(&#34; android.os.AsyncTask&#34;)说。在Android 4.1.2,4.4.4或5.0上也没有发生同样的情况。我想知道这个谷歌是否在2011年解决了马丁·韦斯特问题。
我在我的Application onCreate上添加了这段代码并且工作正常:
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
try {
Class.forName("android.os.AsyncTask");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
很高兴知道Android的版本是否需要更改为其他内容。
答案 3 :(得分:1)
AsyncTask.execute()
,即在Activity中执行。
答案 4 :(得分:0)
我遇到同样的问题,似乎是在挂起/恢复期间运行AsyncTask时发生的。
编辑: 是的,没想到我有,但我用了这个http://developer.android.com/guide/appendix/faq/commontasks.html#threading 总是在UI线程上启动AsyncTask并且问题已经消失。 在我添加了许可功能siggghhhhh
之后出现了问题由于
答案 5 :(得分:0)
尽管这并没有直接回答OP的问题,但我认为这对于在运行测试时搜索相同问题的解决方案的人来说会很有用。
总的来说,Peter Knego's answer总结得很好。
我的问题特别针对在Activity之外的类上运行测试,该类使用Android的AsyncTask进行API调用。该类在应用程序中工作,因为它由Activity使用,但我想运行一个测试,从测试中进行实际的API调用。
虽然Jonathan Perlow's answer有效,但我不想仅仅因为测试而对我的应用程序进行更改。
因此,在测试的情况下runTestOnUiThread
可以使用(@UiThreadTest
不能使用,因为您不能在使用该注释的测试中等待结果。)
public void testAPICall() throws Throwable {
this.runTestOnUiThread(new Runnable() {
public void run() {
underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow();
}
});
// Wait for result here *
// Asserts here
}
有时候,特别是在功能测试中,Jonathan Perlow的答案似乎是唯一有效的答案。
* Take a look here了解如何暂停等待结果的测试。