【问题标题】:Async Task crashing on backbutton pressed.按下后退按钮时异步任务崩溃。
【发布时间】:2013-01-15 12:49:26
【问题描述】:

当调用片段被暂停/删除时,异步任务会发生什么?我假设任务已完成,但如果有人能证实这一点,那就太好了。我正在尝试解决我的崩溃问题。

我有一个异步任务,它是一个递归图像加载器。图像加载器是它为其加载图像的 listFragment 的内部类。除非我在加载完所有图像之前按下返回按钮,否则图像加载器可以完美运行。当我在所有图像加载完成之前按下后退按钮时,应用程序崩溃。我觉得图像加载器无法访问适配器,但我不确定我是否正确解决了这个问题。我不确定如何在我的异步任务中解决这个问题。如果有人可以帮助我。甚至从我脑海中蹦出一个想法,让我有不同的想法。

任何帮助都会很棒。谢谢你花时间看。

图像加载器

private class ImageLoaderTask extends
        AsyncTask<HashMap<String, Object>, Void, HashMap<String, Object>> {

    @Override
    protected HashMap<String, Object> doInBackground(
            HashMap<String, Object>... hm) {
        InputStream in = null;

        String url = (String) hm[0].get("image");
        int pos = (Integer) hm[0].get("pos");
        url = "http://kzfr.org/u/img/small/" + url;
        URL mUrl;

        try {
            mUrl = new URL(url);
            URLConnection urlConnect = (URLConnection) mUrl
                    .openConnection();
            in = urlConnect.getInputStream();

            File cacheDirectory = getSherlockActivity().getBaseContext()
                    .getCacheDir();

            File tmp = new File(cacheDirectory.getPath() + "/kzfr_small"
                    + pos + ".png");

            FileOutputStream fOut = new FileOutputStream(tmp);

            Bitmap b = BitmapFactory.decodeStream(in);
            b.compress(Bitmap.CompressFormat.PNG, 100, fOut);
            fOut.flush();
            fOut.close();

            HashMap<String, Object> hmBmp = new HashMap<String, Object>();
            hmBmp.put("blank_start", tmp.getPath());
            hmBmp.put("pos", pos);
            return hmBmp;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(HashMap<String, Object> result) {
        // this is the img path that leads to a local image that was just
        // saved on the phone.
        String imgPath = (String) result.get("blank_start");
        // the position of the image in the listview
        int position = (Integer) result.get("pos");
        // local adapter
        SimpleAdapter adapter = (SimpleAdapter) getListAdapter();
        // hashmap entry from the local adapter at the position of the image
        // that we just loaded.
        HashMap<String, Object> hm = (HashMap<String, Object>) adapter
                .getItem(position);
        // replace the actual blank image with the image path that we just
        // loaded
        hm.put("blank_start", imgPath);
        // update listview
        adapter.notifyDataSetChanged();
        // we are done with the current position increment position and move
        // on.
        position++;
        // this while loop finds the next position in the adapter that does
        // not have false for an image path. False indicates that there is
        // no image and i should load a default image.
        while (position < adapter.getCount()) {
            hm = (HashMap<String, Object>) adapter.getItem(position);
            imgPath = (String) hm.get("image");

            if (!imgPath.equalsIgnoreCase("false")) {
                break;
            }
            position++;
        }

        // checks to make sure that the position is not out of bounds on the
        // adapter. If in bounds then there might be more images to load.
        // Start a new ImageLoader. This is a recursive Class.
        if (position < adapter.getCount()) {
            ImageLoaderTask imageLoader = new ImageLoaderTask();

            hm.put("pos", (position));
            imageLoader.execute(hm);
        }

    }

堆栈跟踪

01-14 20:09:28.752: E/AndroidRuntime(6069): FATAL EXCEPTION: main
01-14 20:09:28.752: E/AndroidRuntime(6069): java.lang.NullPointerException
01-14 20:09:28.752: E/AndroidRuntime(6069):     at com.appdomum.doublea.ListFrag$ImageLoaderTask.onPostExecute(ListFrag.java:272)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at com.appdomum.doublea.ListFrag$ImageLoaderTask.onPostExecute(ListFrag.java:1)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.AsyncTask.finish(AsyncTask.java:417)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.AsyncTask.access$300(AsyncTask.java:127)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.Handler.dispatchMessage(Handler.java:99)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.os.Looper.loop(Looper.java:130)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at android.app.ActivityThread.main(ActivityThread.java:3806)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at java.lang.reflect.Method.invokeNative(Native Method)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at java.lang.reflect.Method.invoke(Method.java:507)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at     com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at   com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
01-14 20:09:28.752: E/AndroidRuntime(6069):     at dalvik.system.NativeStart.main(Native Method)

【问题讨论】:

  • 哪一行是272?您可能正在尝试引用不再存在的内容,因为活动已完成()
  • 第 272 行是String imgPath = (String) result.get("blank_start"); onPostExecute。那是引用应该在 Image Loader 类内部的东西,因为它是从 DoIn 背景传递的。
  • 你应该先检查那个 result.hasKey("blank_start") 。看起来该键没有项目,因此它返回 null。
  • 我认为这不是该键中缺少项目,因为该错误仅在我在完成加载所有内容之前取消异步任务时发生。

标签: android android-asynctask


【解决方案1】:

我认为你在这里是正确的。

在编写自定义AsyncTasks 时,许多开发人员忘记了处理任务取消的必要性。简单地取消任务并不意味着它的代码不会在(例如)Activity/Fragment 资源上执行,这些资源当时可能会被释放。这通常通过以下步骤完成:

  1. 创建任务后,保存对它的引用。
  2. 在Activity/Fragment完成后取消所有正在运行的任务。这通常在Activity.onDestroy()/Fragment.onDestroy() 中完成。删除对已取消任务的所有引用。
  3. 在任务代码中:
    1. 在doInBackground() 中定期检查它是否isCancelled()。如果是这样,请整理并中止进一步执行。
    2. 最终在 onCancelled() 中处理取消(在 UI 线程上运行)。
    3. 对onPostExecute() 中的已取消任务执行任何处理(如果任务被取消,则执行并返回null 结果)。

【讨论】:

  • 所以数字 3 解释了 onPostExecute() 中的空结果,因为当我按下后退按钮时,片段被删除。我没有使用显示/隐藏,因为我遇到了太多无法追踪的问题。
  • 这是完美的。正是我需要的信息以一种对我正在处理的事情有意义的方式进行了解释。谢谢你,安德。我要去实施了。
  • 没错,但是如果在 Fragment/Activity 销毁后执行任务,则可能会导致 NPE 或其他错误的更多原因。请小心 :) 哦,请记住,您仍然可以在onPostExecute()(例如)中添加断点并在Variables 选项卡中观察变量值。这有时会帮助很多。
【解决方案2】:

当调用片段被暂停/删除时,异步任务会发生什么?我假设任务已完成,但如果有人能确认这一点,那就太好了。

这是不正确的;该框架不会通过取消您在后台线程中所做的工作来为您完成工作。调用像 onPause() 和 onResume() 这样的回调的原因是通知您的应用程序 您 需要清理并取消任何不在长时间运行的工作 -像Service 这样的活组件。

在这种情况下,如果您不取消,任务将继续运行。由于此类被定义为非静态内部类,这也意味着您已经创建了临时内存泄漏,因为AsyncTask 持有对Fragment 的隐式引用,并且由于任务附加到正在运行的线程即使在onDestroy() 之后,也不能对任何对象(或其成员)进行垃圾回收。

您需要以这样一种方式实现您的AsyncTask,它会检查取消标志,您可以从onPause() 或类似的回调中手动取消工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-13
    • 1970-01-01
    相关资源
    最近更新 更多