1

我花了很多时间试图重现和理解这个问题的原因,但在这两个目标中都没有成功。

我试图只留下与问题相关的代码,但我相信仍然需要几分钟来理解问题和上下文。我希望有人能够在我的实施中发现问题,或者至少帮助我理解原因。

申请说明:

与电脑对战的文字游戏。电脑在黑板上打下一个词后,在线获取该词的定义,AsyncTask并显示在一个TextView

我是如何发现这个问题的:

  1. 我使用ACRA进行崩溃和错误报告(顺便说一句,这是一个很棒的免费工具)。它会向我发送每个意外情况的报告(这不会导致崩溃)。我收到了许多关于错误 1、2、3 和 4的报告(见代码)

  2. Google Play 上的一些差评往往表明一些用户即使连接到互联网也看不到定义。(我很确定这个功能性错误与前面提到的错误有关,尽管我无法证明)

关于代码设计的一句话:

在阅读了很多关于 Android 内存泄漏的内容后,我决定将在线检索定义的 AsyncTask 设为静态内部类(尽管我的主要活动目前不支持旋转,这是导致泄漏的主要原因:我把我的清单android:screenOrientation="portrait")。

Activity我需要从这里访问父级,AsyncTask因为我从资源中检索字符串,并在onPostExecute(). 因此,我WeakReferenceAsyncTask其中使用 a 指向 parent ActivityActivity is recreated or killed while the如果AsyncTask 仍在运行,这应该可以防止内存泄漏。

究竟是什么问题:

  • 其方法的或返回是在WeakReference一些无法解释的情况下(我怀疑它影响了超过 1% 的游戏或玩家)(见代码)get()null
  • 各种设备和Android版本都受到影响,我经常看到同一设备出现几次)
  • 我从来没有能够重现这些错误(最明显的尝试是在下载定义时退出活动,但这并没有导致任何错误)

我的代码中有意义的部分:

public class GameActivity extends Activity {
    private TextView _definition; //inflated from XML in onCreate()
    private ProgressDialog _pDialog; //created in onCreate()

    private Handler _handlerToDelayDroidMove = new Handler();
    private Handler _handlerToDelayProgressDialog = new Handler();
    private Handler _handlerToDelayDefinitionClosure = new Handler();

    public void onClickValidatePlayerMoveAndTriggerDroidMove(View v) {
        int score = _arbitre.validatePlayerMoveAndReturnScore(_listOfLetters);
        toast(String.format(getResources().getString(R.string.player_word_score), score));

        // ***** Only start Droid move when previous toast has been displayed ****
        timedDroidPlayWithSpinner();
    }

    private void timedDroidPlayWithSpinner() {
        _handlerToDelayProgressDialog.removeCallbacks(_droidThinkingDialogRunnable);
        _handlerToDelayDroidMove.removeCallbacks(_droidPlayRunnable);

        _handlerToDelayProgressDialog.postDelayed(_droidThinkingDialogRunnable, 1500);
        _handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 1500 + DUMMY_DELAY);
    }

    private Runnable _droidThinkingDialogRunnable = new Runnable() { //Show a "Droid is thinking spinner dialog"
        public void run() {
            _pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            _pDialog.setMessage(getResources().getString(R.string.droid_thinking));
            _pDialog.setCancelable(false);
            _pDialog.show();
        }
    };

    private Runnable _droidPlayRunnable = new Runnable() {
        public void run() {
            String word = playBestMoveAndUpdateGUI(); // Droid move (CPU intensive, can take several seconds)
            saveGameStateToPrefs();

            _pDialog.dismiss(); //Hide "Thinking dialog")
            new SearchDefinitionTask(GameActivity.this).execute(word);
        }
    };

    private Runnable _hideDefinitionRunnable = new Runnable() {
        public void run() {
            _definition.startAnimation(_slideUpAnim);
            _definition.setVisibility(View.GONE);
        }
    };

    // Made static so we are sure if does not reference the Activity (risk of leak)
    public static class SearchDefinitionTask extends AsyncTask<String, Void, String[]> {
        private WeakReference<GameActivity> weakRefToGameActivity;

        public SearchDefinitionTask(GameActivity context) { //Save a weak reference to the Activity
            super();
            weakRefToGameActivity = new WeakReference<GameActivity>(context);
        }

        protected String[] doInBackground(String... words) {
            try {
                DefFetcherInterface defFetcher = null;
                Language l = weakRefToGameActivity.get()._dictionaryId;
                defFetcher = new OnlineDefinitionFetcher(l);
                return defFetcher.getDefinition(words[0]);
            } catch (Exception e) { // Typical exceptions are due to lack of internet connectivity
                Log.e("Definition fetch error: ", e.toString());
                String[] ret = { "", "" };
                ret[0] = mots[0];
                if (weakRefToGameActivity == null) { // !!! This occurs in ~0.3% of the games !!!
                    ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 1: weakRef is NULL"));
                    return ret;
                }
                if (weakRefToGameActivity.get() == null) { !!! This occurs in ~1% of the games !!!
                    ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 2: weakRef.get() is NULL"));
                    return ret;
                }

                // If we get here we still have a reference on our Activit/context, so let's show a decent error message
                ret[1] = weakRefToGameActivity.get().getResources().getString(R.string.no_connection);
                return ret;
            }
        }

        protected void onPostExecute(String[] result) {
            if (result[0] != "") { //Don't send another error report if WeakRef was already NULL in doInBackground()
                if (weakRefToGameActivity == null) { !!! This occurs in ~0.5% of the games !!!
                    ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 3: weakRef is NULL"));
                } else if (weakRefToGameActivity.get() == null) { !!!!!!!! This occurs in ~1% of the games !!!!!!!!
                    ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 4: weakRef.get() is NULL"));
                } else {
                    // Everything is fine, show a box with the definition of the word for a few seconds 
                    //(with animation to make the box appearing from the top of the screen)
                    weakRefToGameActivity.get()._definition.setVisibility(ImageView.VISIBLE);
                    weakRefToGameActivity.get()._handlerToDelayDefinitionClosure.removeCallbacks(weakRefToGameActivity.get()._hideDefinitionRunnable);
                    weakRefToGameActivity.get()._definition.setText(Html.fromHtml("<b>" + result[0].toUpperCase() + "</b> " + result[1]));

                    weakRefToGameActivity.get()._definition.startAnimation(weakRefToGameActivity.get()._slideDownAnim);
                    weakRefToGameActivity.get()._handlerToDelayDefinitionClosure.postDelayed(weakRefToGameActivity.get()._hideDefinitionRunnable,
                            DURATION_OF_DEFINITION);
                }
            }
        }    
    }
}

知道什么可能出错或如何重现吗?

4

1 回答 1

0

Sebastien,也许您可​​以尝试检查 onDestroy 从未为您的 Activity 调用...当屏幕旋转(您已经处理过)时,可以重新启动 Activity,但还有其他配置更改可能会导致相同的行为。

另一个很常见的方法是在某些手机上取下键盘,但还有一些对我来说更加晦涩难懂。你可以在那里看到列表

除此之外,我真的没有在您的代码中看到任何错误,也无法想象还有什么可能导致您的麻烦。

最糟糕的是你的错误1和3。你能在构造函数中检查weakRefToGameActivity在创建后不为空吗?(如果它为空,那么上下文参数呢)。

找到问题的根本原因后,请发布更新。好机会。

于 2012-06-15T18:21:11.400 回答