سؤال

لدي AsyncTask أن جلب بعض البيانات ثم التحديثات واجهة المستخدم مع هذه البيانات الجديدة.فقد كان يعمل لعدة أشهر, ولكن مؤخرا تم إضافة ميزة يعرض الإخطار عندما يكون هناك بيانات جديدة.الآن عندما يتم تشغيل التطبيق من خلال الإعلام ، أحيانا الحصول على هذا الاستثناء ، 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:لقد تم حفر من خلال الروبوت شفرة المصدر تعقب فيها استثناء قادم من.هذه هي الخطوط 456 و 457 من sendMessageAtTime في Handler:

msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);

و هذا هو enqueueMessage من 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 كان يسمى msg.target كانت فارغة.

هل كانت مفيدة؟

المحلول

التعميم جوناثان Perlow الحل علة عرف على وجه التحديد ، يجب استخدام ما يلي في أي من الدرجة التي تستخدم AsyncTask.وبير/معالج/آخر هو كيف يمكنك تشغيل شيء على 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<> في الروبوت JUnit اختبار تسبب onPostExecute() لا يدعى.الآن نعرف لماذا.

هذا آخر يوضح كيفية تشغيل مؤشرات المتزامن رمز في الروبوت JUnit الاختبار:

باستخدام CountDownLatch في الروبوت AsyncTask على أساس JUnit الاختبارات

للاستخدام مع عدم UI-وحدة الاختبارات, أنا خلقت بسيطة فرعية من الروبوت.الاختبار.InstrumentationTestCase.أنه يحتوي على "موافق" العلم و CountDownLatch.إعادة تعيين() أو إعادة تعيين(العد) يخلق جديد CountDownLatch({1,count}).جيد() مجموعات موافق=true, العد-- و المكالمات.العد التنازلي() على المزلاج.سيئة() مجموعات موافق=false ، العد التنازلي طول الطريق.waitForIt(ثانية) ينتظر مهلة أو العد التنازلي مزلاج إلى الصفر.ثم يدعو assertTrue(موافق).

ثم الاختبارات مثل:

someTest() {
  reset();
  asyncCall(args, new someListener() {
    public void success(args) { good(); }
    public void fail(args) { bad(); }
  });
  waitForIt();
}

بسبب AsyncTask ثابت التهيئة علة ، كان علينا أن التجارب الفعلية داخل Runnable تمريرها إلى runTestOnUiThread().مع حسن ثابت التهيئة على النحو الوارد أعلاه, يجب أن لا يكون ضروريا إلا إذا كان الاتصال يجري اختبارها تحتاج إلى تشغيل على UI.

أخرى لغة أنا الآن استخدام هو لاختبار ما إذا كان الترابط الحالي هو مؤشر ترابط واجهة المستخدم ثم قم بتشغيل الإجراء المطلوب على حسن الموضوع بغض النظر عن.أحيانا, فمن المنطقي أن تسمح المتصل إلى طلب مزامنة مقابلالمتزامن تجاوز عند الضرورة.على سبيل المثال, طلبات شبكة الاتصال يجب أن تكون دائما على تشغيل مؤشر ترابط خلفية.في معظم الحالات ، AsyncTask موضوع تجميع مثالية لهذا.فقط ندرك أن فقط عدد معين سيتم تشغيل في وقت واحد ، ومنع طلبات إضافية.لاختبار ما إذا كان الترابط الحالي هو UI:

boolean onUiThread = Looper.getMainLooper().getThread() == Thread.currentThread();

ثم استخدام بسيط فرعية (فقط doInBackground() و onPostExecute() مطلوبة) من AsyncTask<> لتشغيل على غير UI أو معالج.بعد() أو 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);
    }});

أو ببساطة:جديد RunAsyncTask("").تنفيذ(جديد Runnable(){الفراغ العام تشغيل(){ 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();
     }
 }

نصائح أخرى

ويرجع ذلك إلى وجود خطأ في Asynctask في إطار Android. لدى Asynctask.java الكود التالي:

private static final InternalHandler sHandler = new InternalHandler();

إنه يتوقع تهيئة هذا على الخيط الرئيسي ، ولكن هذا غير مضمون لأنه سيتم تهيئته بشأن أي مؤشر ترابط يتسبب في تشغيل الفئة من المهيئات الثابتة. لقد استنسخت هذه المسألة حيث يشير المعالج إلى موضوع عامل.

النمط الشائع الذي يتسبب في حدوث هذا هو استخدام فئة intentservice. رمز عينة C2DM يفعل هذا.

يتمثل الحل البديل البسيط في إضافة الكود التالي إلى طريقة OnCreate للتطبيق:

Class.forName("android.os.AsyncTask");

هذا سوف يجبر Asynctask على تهيئة في الخيط الرئيسي. لقد قدمت خطأً في هذا في قاعدة بيانات Bug Android. نرى http://code.google.com/p/android/issues/detail؟id=20915.

واجهت نفس المشكلة على جهاز مع Android 4.0.4 مع خدمة IntentService وحلها كما قال SDW مع class.forname ("Android.os.asynctask"). الشيء نفسه لم يحدث على Android 4.1.2 ، 4.4.4 أو 5.0. أتساءل عما إذا كانت Google حلت قضية Martin West من عام 2011.

أضفت هذا الرمز على طلبي oncreate وعمل:

    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
        try {
            Class.forName("android.os.AsyncTask");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

سيكون من الجيد معرفة ما إذا كان إصدار Android يحتاج إلى تغيير إلى شيء آخر.

AsyncTask.execute() يجب تنفيذها على موضوع واجهة المستخدم ، أي داخل النشاط.

لدي نفس المشكلة ، ويبدو أن ذلك يحدث عندما يتم تشغيل Asynctask أثناء تعليق/سيرة ذاتية.

تحرير: نعم ، لم أكن أعتقد أنني كنت أستخدم هذا http://developer.android.com/guide/appendix/faq/commontasks.html#threadingلبدء دائمًا من Asynctask على موضوع واجهة المستخدم وذهبت المشكلة. ظهرت المشكلة بعد إضافة وظيفة الترخيص ، Siggghhhhh

شكرًا

على الرغم من أن هذا لا يجيب مباشرة على سؤال البروتوكول الاختياري ، إلا أنه أعتقد أنه سيكون مفيدًا للأشخاص الذين يبحثون عن حل نفس المشكلة عند إجراء الاختبارات.

شاملة، إجابة بيتر كنيجو يلخص ذلك جيدا.

كانت مشكلتي على وجه التحديد إجراء اختبار على فصل خارج نشاط استفاد من Asynctask من Android لمكالمة API. يعمل الفصل في التطبيق ، حيث يتم استخدامه بواسطة نشاط ، لكنني أردت إجراء اختبار لإجراء مكالمة API الفعلية من الاختبار.

في حين إجابة جوناثان بيرلو عملت ، لم أحب تقديم التغييرات على طلبي بسبب الاختبار فقط.

لذلك ، في حالة الاختبار runTestOnUiThread من الممكن استخدامه (@UiThreadTest لا يمكن استخدامه ، حيث لا يمكنك الانتظار لنتيجة اختبار يستخدم هذا التعليق التوضيحي).

public void testAPICall() throws Throwable {
    this.runTestOnUiThread(new Runnable() {
        public void run() {
            underTest.thisMethodWillMakeUseOfAnAsyncTaskSomehow();
        }           
    }); 

    // Wait for result here *
    // Asserts here
}

في بعض الأحيان ، خاصة في الاختبارات الوظيفية ، يبدو أن إجابة جوناثان بيرلو هي الوحيدة التي تعمل.


* ألق نظرة هنا لمعرفة كيفية إيقاف اختبار في انتظار النتيجة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top