Activity 中的 AsyncTask 不是 运行 onPostExexute 从 Notification 启动

AsyncTask not running onPostExexute in Activity launched from Notification

我正在编写一个 Android 应用程序,它使用 AlarmManagerWakefulBroadcastReceiver 在后台检查新消息。接收方启动 IntentService 并使用 AsyncTask 进行网络请求 (HTTPClient)。

重新启动后,使用侦听 android.intent.action.BOOT_COMPLETED 的 BroadcastReceiver 重新启用警报。

当我的 main Activity 的 onCreate 方法使用 AsyncTask 加载完整消息内容时出现问题,但仅在某些情况下:

java.lang.RuntimeException: Handler (android.os.AsyncTask$InternalHandler) {4139f618} sending message to a Handler on a dead thread

doInBackground 被执行并且 returns 具有有效内容的 String,但不是执行 onPostExecute,而是异常增加。

有几种情况:

  1. 我从启动器启动应用程序 -> WORKS.
  2. 应用程序已经像(1)一样启动,发送到后台,然后消息到达。点击通知,Activity 启动 -> WORKS.
  3. 应用程序已经像(1)一样启动,发送到后台并重启设备。消息到达,点击通知,Activity 开始 -> EXCEPTION.

问题:我该如何解决这个问题,而不是因为在 UI 线程上执行网络请求而让开发人员陷入困境?

在搜索解决方案时,我找到了 onPostExecute not being called in AsyncTask (Handler runtime exception),但是 "sdw" 和 "Jonathan Perlow" 的解决方案都没有改变。

服务器查询:

public abstract class ServerQuery extends AsyncTask<Void, Void, String> {
    protected Context context;

    String user = "ERR";
    String pin = "ERR";
    String cmd = "ERR";

    ServerQuery(Context context, String _user, String _pin, String _cmd) {
        this.context = context;
        user = _user;
        pin = _pin;
        cmd = _cmd;
    }

    private ServerQuery() {
    }

    @Override
    protected String doInBackground(Void... params) {
        ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
        nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_client), context.getString(R.string.post_client_app)));
        nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_user), user));
        nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_pin), pin));
        nameValuePairs.add(new BasicNameValuePair(context.getString(R.string.post_cmd), cmd));
        HttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost("http://[my server hostname]/index.php");
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
        } catch (UnsupportedEncodingException e) {
            return "ERR";
        }
        HttpResponse httpResponse = null;
        try{
            httpResponse = httpClient.execute(httpPost);
        }catch (Exception e) {
            return "ERR";
        }
        HttpEntity httpEntity = httpResponse.getEntity();
        InputStream inputStream = null;
        try {
            inputStream = httpEntity.getContent();
        } catch (IOException e) {
            return "ERR";
        }
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder stringBuilder = new StringBuilder();
        String s = null;
        try {
            while ((s = bufferedReader.readLine()) != null) {
                stringBuilder.append(s);
            }
        } catch (IOException e) {
            return "ERR";
        }
        try {
            inputStream.close();
        } catch (IOException e) {
            return "ERR";
        }
        return stringBuilder.toString();
    }
}

lastID:

public class lastID extends ServerQuery {

    Integer lastid, savedid;

    lastID(Context context, String _user, String _pin, String _cmd) {
        super(context, _user, _pin, _cmd);
        lastid = -1;
    }

    @Override
    protected void onPostExecute(final String success) {
        if(success.equals("ERR") || success.length() < 1) {
            Toast.makeText(context, context.getString(R.string.server_no_connection), Toast.LENGTH_LONG).show();
        } else {
            String content[] = success.split("(;)");
            if(content.length == 2) {
                lastid = Integer.parseInt(content[0]);
                SharedPreferences sharedPreferences = context.getSharedPreferences(context.getString(R.string.settings_filename), 0);
                SharedPreferences.Editor editor = sharedPreferences.edit();
                savedid = sharedPreferences.getInt(context.getString(R.string.post_lastid), -1);
                if (lastid > savedid) { // NEUES ANGEBOT!
                    editor.putInt(context.getString(R.string.post_lastid), lastid);
                    editor.commit();
                    // Benachrichtung
                    NotificationCompat.Builder notific = new NotificationCompat.Builder(context);
                    notific.setContentTitle(context.getString(R.string.notify_title));
                    notific.setContentText(content[1]);
                    notific.setSmallIcon(R.drawable.ic_notify);
                    notific.setAutoCancel(false);
                    notific.setPriority(NotificationCompat.PRIORITY_HIGH);
                    notific.setTicker(content[1]);
                    notific.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);

                    Intent resultIntent = new Intent(context, MainActivity.class);
                    TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
                    stackBuilder.addParentStack(MainActivity.class);
                    stackBuilder.addNextIntent(resultIntent);
                    PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_CANCEL_CURRENT);
                    notific.setContentIntent(resultPendingIntent);

                    NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
                    notificationManager.notify(lastid, notific.build());
                }
            }
        }
    }
}

主要Activity onCreate:

[ loading shared preferences, setting onclicklisteners ]
angebot = new Angebot(this, getApplicationContext(), user, pin, getString(R.string.post_cmd_viewAngebot));
angebot.execute((Void) null);

Angebot 类似于 lastID,但它使用 String 来填充 TextView。 编辑 1: 天使机器人:

public class Angebot extends ServerQuery {

    protected Activity activity;

    protected TextView datumView;
    // more TextView s
    protected Button hungerButton;

    Angebot(Activity activity, Context context, String _user, String _pin, String _cmd) {
        super(context, _user, _pin, _cmd);
        this.activity = activity;

        datumView = (TextView) this.activity.findViewById(R.id.datum);
        // finding all other TextView s
    }

    @Override
    protected void onPreExecute() {
        showProgress(true);
    }

    @Override
    protected void onPostExecute(final String success) {
        Log.v(C.TAG_ALARMESSEN, "Angebot.onPostExecute");
        showProgress(false);
        if(success.equals("ERR") || success.length() < 1) {
            Toast.makeText(activity.getApplicationContext(), context.getString(R.string.server_no_connection), Toast.LENGTH_LONG).show();
        } else {
            String content[] = success.split("(;)");
                // put each content string in a TextView
            } else {
                Toast.makeText(context, context.getString(R.string.err02s), Toast.LENGTH_LONG).show(); // nicht angemeldet
            }
        }
    }

    @Override
    protected void onCancelled() {
        showProgress(false);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    public void showProgress(final boolean show) {
        final View progressView = activity.findViewById(R.id.login_progress);
        progressView.setVisibility(show ? View.VISIBLE : View.GONE);
    }
}

编辑 2:堆栈跟踪:

java.lang.RuntimeException: Handler (android.os.AsyncTask$InternalHandler) {4139f618} sending message to a Handler on a dead thread
            at android.os.MessageQueue.enqueueMessage(MessageQueue.java:196)
            at android.os.Handler.sendMessageAtTime(Handler.java:473)
            at android.os.Handler.sendMessageDelayed(Handler.java:446)
            at android.os.Handler.sendMessage(Handler.java:383)
            at android.os.Message.sendToTarget(Message.java:363)
            at android.os.AsyncTask.postResult(AsyncTask.java:300)
            at android.os.AsyncTask.access0(AsyncTask.java:156)
            at android.os.AsyncTask.call(AsyncTask.java:264)
            at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
            at java.util.concurrent.FutureTask.run(FutureTask.java:137)
            at android.os.AsyncTask$SerialExecutor.run(AsyncTask.java:208)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
            at java.lang.Thread.run(Thread.java:856)

我假设此处提到的 AsyncTask 错误的解决方法:onPostExecute not being called in AsyncTask (Handler runtime exception) 在这种情况下也适用,但根据 OP,它们不起作用。

我建议改用 AsyncTaskLoader,而不是解决问题。有很多文章介绍 AsyncTaskLoader 与 AsyncTask 的区别(主要是优点)。这里只是两个(好的)例子: http://www.javacodegeeks.com/2013/01/android-loaders-versus-asynctask.html and http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html

AsyncTaskLoader 的主要优点是它们与启动它的 Activity 或 Fragment 的生命周期同步,并且它们可以优雅地处理配置更改(与 AsyncTasks 不同)。

以下是您的实施方式。首先你需要 AsyncLoaderClass:

class ServerQuery extends AsyncTaskLoader<String> {
    String mResult;

    public ServerQuery(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (mResult == null) {
            forceLoad();    // no data yet -> force load it
        }
        else {
            deliverResult(mResult); // already got the data -> deliver it
        }
    }

    @Override
    public String loadInBackground() {
        // load your data...

        return mResult; // return the loaded data
    }

    @Override
    public void cancelLoadInBackground() {
        // do whatever it takes to stop loading
    }
}

用您在 doInBackground 方法中的任何内容替换“// 加载您的数据...”部分。这就是实际的 AsyncTaskLoader 的全部内容。如您所见,onPostExecute 没有 equivalent,这就是 AsyncTaskLoader 的优点,它用于加载数据,仅此而已。由于 AsyncTask 尝试更新 onPostExecute 上的 ui 而发生的所有问题都消失了。

onPostExecute 中的逻辑现在完全驻留在启动加载程序的 Activity/Fragment 中。操作方法如下:

LoaderManager loaderMgr = getLoaderManager();
loaderMgr.initLoader(0, null, this);

this 代表 LoaderManager.LoaderCallbacks 意味着您必须在 fragment/activity 中实现该接口,如下所示:

@Override
public Loader<String> onCreateLoader(int id, Bundle args) {
    return new ServerQuery(this);
}

@Override public void onLoaderReset(Loader<String> loader) {}

@Override
public void onLoadFinished(Loader<String> loader, String data) {
    // here goes your code to update the ui (previously onPostExecute);
}

onCreateLoader 只是创建您的加载器(在本例中为 ServerQuery),此后将由 LoaderManager 管理。 onLoadFinished 将在数据传送到您的 fragment/activity 时调用,允许您更新 ui。您可以查看我的其他答案之一以获取有关加载程序的更多信息,这些信息可能有助于您了解它们的工作原理:。特别是关于配置更改的部分可能会有所帮助。