我花了很多时间试图重现和理解这个问题的原因,但在这两个目标中都没有成功。
我试图只留下与问题相关的代码,但我相信仍然需要几分钟来理解问题和上下文。我希望有人能够在我的实施中发现问题,或者至少帮助我理解原因。
申请说明:
与电脑对战的文字游戏。电脑在黑板上打下一个词后,在线获取该词的定义,AsyncTask
并显示在一个TextView
我是如何发现这个问题的:
我使用ACRA进行崩溃和错误报告(顺便说一句,这是一个很棒的免费工具)。它会向我发送每个意外情况的报告(这不会导致崩溃)。我收到了许多关于错误 1、2、3 和 4的报告(见代码)
Google Play 上的一些差评往往表明一些用户即使连接到互联网也看不到定义。(我很确定这个功能性错误与前面提到的错误有关,尽管我无法证明)
关于代码设计的一句话:
在阅读了很多关于 Android 内存泄漏的内容后,我决定将在线检索定义的 AsyncTask 设为静态内部类(尽管我的主要活动目前不支持旋转,这是导致泄漏的主要原因:我把我的清单android:screenOrientation="portrait"
)。
Activity
我需要从这里访问父级,AsyncTask
因为我从资源中检索字符串,并在onPostExecute()
. 因此,我WeakReference
在AsyncTask
其中使用 a 指向 parent Activity
。Activity 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);
}
}
}
}
}
知道什么可能出错或如何重现吗?