1

この問題の原因を再現して理解するために多くの時間を費やしましたが、これらの目標のいずれにも成功していません。

問題に関連するコードのみを残そうとしましたが、問題とコンテキストを理解するにはまだ数分かかると思います。誰かが私の実装の問題を見つけられるか、少なくとも原因を理解するのを手伝ってくれることを願っています。

アプリケーションの説明:

コンピュータと対戦する単語ゲーム。コンピュータが単語をボードに配置した後、この単語の定義がオンラインで取得AsyncTaskされ、TextView

問題を発見した方法:

  1. 私はクラッシュとエラーの報告にACRAを使用しています (ちなみに素晴らしい無料ツールです)。予期しない状況ごとにレポートを送ってくれます (これはクラッシュにはつながりません)。エラー 1、2、3、および 4のレポートを多数受け取っています(コードを参照) 。

  2. Google Play での悪いレビューの中には、インターネットに接続しているにもかかわらず定義が表示されないユーザーがいることが示されている傾向があります。(この機能上のバグは前述のエラーに関連していると確信していますが、証明はできません)

コード設計について一言:

Android のメモリ リークについて多くのことを読んだ後、定義をオンラインで取得する AsyncTask を静的な内部クラスにすることにしました(私のメイン アクティビティは現在、リークの主な原因であるローテーションをサポートしていませんが、マニフェストandroid:screenOrientation="portrait")。

リソースから文字列を取得し、 の UI でいくつかの変更を行うため、Activityここから親にアクセスする必要があります。したがって、親を指している でaを使用します。これにより、 AsyncTask` がまだ実行されている場合のメモリ リークを防ぐことができます。AsyncTaskonPostExecute()WeakReferenceAsyncTaskActivityActivity is recreated or killed while the

問題は正確には何ですか:

  • そのメソッド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 つのかなり一般的な問題は、一部の携帯電話でキーボードを取り外すことですが、私にはさらにわかりにくいものもあります。そこにリストが表示されます

それに加えて、あなたのコードには何も問題がなく、他に何があなたの問題を引き起こしているのか想像もできません。

最悪のエラーはエラー 1 と 3 です。コンストラクターで、weakRefToGameActivity が作成された後に null でないことを確認できますか? (そしてそれがnullの場合、コンテキスト引数はどうなりますか).

問題の根本原因を見つけたら、最新情報を投稿してください。ボンチャンス。

于 2012-06-15T18:21:11.400 に答える