我花了很多时间试图重现并理解这个问题的原因,并没有成功实现这两个目标。
我试图只留下与问题相关的代码,但我相信还需要几分钟才能理解问题和背景。我希望有人能够在我的实施中发现问题,或者至少帮助我理解原因。
申请说明:
与电脑对战的文字游戏。计算机在电路板上放置一个字后,该字的定义将在AsyncTask
中在线获取并显示在TextView
我是如何发现此问题的:
我使用ACRA进行崩溃和错误报告(顺便提一下,这是一款非常棒的免费工具)。它向我发送每个意外情况的报告(这不会导致崩溃)。我收到了很多关于错误1,2,3和4 的报告(见代码)
Google Play上的一些不良评论往往表明某些用户即使连接到互联网也看不到定义。 (我很确定这个功能错误与之前提到的错误有关,但我无法证明这一点)
关于代码设计的一句话:
在阅读了很多有关Android内存泄漏的内容之后,我决定让在线检索定义的AsyncTask成为静态内部类(即使我的主要活动目前不支持旋转,这是泄密的主要原因:我加入了我的清单android:screenOrientation="portrait"
)。
我需要从Activity
访问父AsyncTask
,因为我从资源中检索字符串,并在onPostExecute()
中对UI进行一些更改。
因此,我在WeakReference
中使用AsyncTask
,该Activity
指向父Activity is recreated or killed while the
。如果WeakReference
AsyncTask`仍在运行,这应该可以防止内存泄漏。
究竟是什么问题:
get()
或其null
方法的返回值为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);
}
}
}
}
}
一些无法解释的情况(我怀疑它影响超过1%的游戏或
球员)(见代码)我的代码中有意义的部分:
{{1}}
知道可能出现什么问题或如何重现?
答案 0 :(得分:0)
Sebastien,也许您可以尝试检查onDestroy从未调用过您的Activity ...当屏幕旋转时(您已经处理过)可以重新启动活动,但是还有其他配置更改可能会导致相同行为。
另一个很常见的问题是在某些手机上取出键盘,但还有一些对我来说更加模糊。您可以看到列表there
除此之外,我真的没有看到你的代码有什么问题,也无法想象还有什么可能导致你的麻烦。
最糟糕的是您的错误1和3.您是否可以在构造函数中检查weakRefToGameActivity在创建后是否为null? (如果它为null,那么context参数怎么样)。
一旦找到问题的根本原因,请发布更新。 Bonne chance。