この問題の原因を再現して理解するために多くの時間を費やしましたが、これらの目標のいずれにも成功していません。
問題に関連するコードのみを残そうとしましたが、問題とコンテキストを理解するにはまだ数分かかると思います。誰かが私の実装の問題を見つけられるか、少なくとも原因を理解するのを手伝ってくれることを願っています。
アプリケーションの説明:
コンピュータと対戦する単語ゲーム。コンピュータが単語をボードに配置した後、この単語の定義がオンラインで取得AsyncTask
され、TextView
問題を発見した方法:
私はクラッシュとエラーの報告にACRAを使用しています (ちなみに素晴らしい無料ツールです)。予期しない状況ごとにレポートを送ってくれます (これはクラッシュにはつながりません)。エラー 1、2、3、および 4のレポートを多数受け取っています(コードを参照) 。
Google Play での悪いレビューの中には、インターネットに接続しているにもかかわらず定義が表示されないユーザーがいることが示されている傾向があります。(この機能上のバグは前述のエラーに関連していると確信していますが、証明はできません)
コード設計について一言:
Android のメモリ リークについて多くのことを読んだ後、定義をオンラインで取得する AsyncTask を静的な内部クラスにすることにしました(私のメイン アクティビティは現在、リークの主な原因であるローテーションをサポートしていませんが、マニフェストandroid:screenOrientation="portrait"
)。
リソースから文字列を取得し、 の UI でいくつかの変更を行うため、Activity
ここから親にアクセスする必要があります。したがって、親を指している でaを使用します。これにより、 AsyncTask` がまだ実行されている場合のメモリ リークを防ぐことができます。AsyncTask
onPostExecute()
WeakReference
AsyncTask
Activity
Activity 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);
}
}
}
}
}
何がうまくいかないのか、または再現する方法について何か考えはありますか?