Bev*_*vor 12 heap android memory-leaks out-of-memory
我的应用运行正常,直到我在安装后的第一次启动中断初始化过程,只要初始化过程尚未完成,退出并启动应用程序几次.处理逻辑和AsyncTask可以很好地处理这个问题,所以我没有遇到任何不一致,但我的堆有问题.当我在应用程序设置中执行此令人不安的退出和启动时,它会越来越多,这将导致OutOfMemory错误.我已经通过MAT分析堆已经发现了泄漏,但我仍然有另一个泄漏,我无法隔离.
有关背景信息:我将应用程序上下文,列表和时间戳存储在静态类中,以便能够从应用程序中的任何位置访问它,而无需使用构造函数的繁琐的传递引用.无论如何,这个静态类(ApplicationContext)肯定有问题,因为它会因区域列表而导致内存泄漏.区域对象处理GeoJSON数据.这是这个类的样子:
public class ApplicationContext extends Application {
private static Context context;
private static String timestamp;
private static List<Zone> zones = new ArrayList<Zone>();
public void onCreate() {
super.onCreate();
ApplicationContext.context = getApplicationContext();
}
public static Context getAppContext() {
return ApplicationContext.context;
}
public static List<Zone> getZones() {
return zones;
}
public static void setData(String timestamp, List<Zone> zones) {
ApplicationContext.timestamp = timestamp;
ApplicationContext.zones = zones;
}
public static String getTimestamp() {
return timestamp;
}
}
Run Code Online (Sandbox Code Playgroud)
我已经尝试像这样存储区域
ApplicationContext.zones = new ArrayList(zones);
但它没有效果.我已经尝试将zones属性放入另一个静态类,因为ApplicationContext在所有其他类之前加载(由于AndroidManifest中的条目),这可能导致这种行为,但这也不是问题.
setData在我的"ProcessController"中调用两次.一旦进入doUpdateFromStorage,就进入doUpdateFromUrl(String).这个类看起来像这样:
public final class ProcessController {
private HttpClient httpClient = new HttpClient();
public final InitializationResult initializeData() {
String urlTimestamp;
try {
urlTimestamp = getTimestampDataFromUrl();
if (isModelEmpty()) {
if (storageFilesExist()) {
try {
String localTimestamp = getLocalTimestamp();
if (isStorageDataUpToDate(localTimestamp, urlTimestamp)) {
return doDataUpdateFromStorage();
}
else {
return doDataUpdateFromUrl(urlTimestamp);
}
}
catch (IOException e) {
return new InitializationResult(false, Errors.cannotReadTimestampFile());
}
}
else {
try {
createNewFiles();
return doDataUpdateFromUrl(urlTimestamp);
}
catch (IOException e) {
return new InitializationResult(false, Errors.fileCreationFailed());
}
}
}
else {
if (isApplicationContextDataUpToDate(urlTimestamp)) {
return new InitializationResult(true, "");
}
else {
return doDataUpdateFromUrl(urlTimestamp);
}
}
}
catch (IOException e1) {
return new InitializationResult(false, Errors.noTimestampConnection());
}
}
private String getTimestampDataFromUrl() throws IOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return httpClient.getDataFromUrl(FileType.TIMESTAMP);
}
private String getJsonDataFromUrl() throws IOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return httpClient.getDataFromUrl(FileType.JSONDATA);
}
private String getLocalTimestamp() throws IOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return PersistenceManager.getFileData(FileType.TIMESTAMP);
}
private List<Zone> getLocalJsonData() throws IOException, ParseException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
return JsonStringParser.parse(PersistenceManager.getFileData(FileType.JSONDATA));
}
private InitializationResult doDataUpdateFromStorage() throws InterruptedIOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
try {
ApplicationContext.setData(getLocalTimestamp(), getLocalJsonData());
return new InitializationResult(true, "");
}
catch (IOException e) {
return new InitializationResult(false, Errors.cannotReadJsonFile());
}
catch (ParseException e) {
return new InitializationResult(false, Errors.parseError());
}
}
private InitializationResult doDataUpdateFromUrl(String urlTimestamp) throws InterruptedIOException {
if (ProcessNotification.isCancelled()) {
throw new InterruptedIOException();
}
String jsonData;
List<Zone> zones;
try {
jsonData = getJsonDataFromUrl();
zones = JsonStringParser.parse(jsonData);
try {
PersistenceManager.persist(jsonData, FileType.JSONDATA);
PersistenceManager.persist(urlTimestamp, FileType.TIMESTAMP);
ApplicationContext.setData(urlTimestamp, zones);
return new InitializationResult(true, "");
}
catch (IOException e) {
return new InitializationResult(false, Errors.filePersistError());
}
}
catch (IOException e) {
return new InitializationResult(false, Errors.noJsonConnection());
}
catch (ParseException e) {
return new InitializationResult(false, Errors.parseError());
}
}
private boolean isModelEmpty() {
if (ApplicationContext.getZones() == null || ApplicationContext.getZones().isEmpty()) {
return true;
}
return false;
}
private boolean isApplicationContextDataUpToDate(String urlTimestamp) {
if (ApplicationContext.getTimestamp() == null) {
return false;
}
String localTimestamp = ApplicationContext.getTimestamp();
if (!localTimestamp.equals(urlTimestamp)) {
return false;
}
return true;
}
private boolean isStorageDataUpToDate(String localTimestamp, String urlTimestamp) {
if (localTimestamp.equals(urlTimestamp)) {
return true;
}
return false;
}
private boolean storageFilesExist() {
return PersistenceManager.filesExist();
}
private void createNewFiles() throws IOException {
PersistenceManager.createNewFiles();
}
}
Run Code Online (Sandbox Code Playgroud)
也许这是另一个有用的信息,这个ProcessController由我的MainActivity的AsyncTask在应用程序设置中调用:
public class InitializationTask extends AsyncTask<Void, Void, InitializationResult> {
private ProcessController processController = new ProcessController();
private ProgressDialog progressDialog;
private MainActivity mainActivity;
private final String TAG = this.getClass().getSimpleName();
public InitializationTask(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
ProcessNotification.setCancelled(false);
progressDialog = new ProgressDialog(mainActivity);
progressDialog.setMessage("Processing.\nPlease wait...");
progressDialog.setIndeterminate(true); //means that the "loading amount" is not measured.
progressDialog.setCancelable(true);
progressDialog.show();
};
@Override
protected InitializationResult doInBackground(Void... params) {
return processController.initializeData();
}
@Override
protected void onPostExecute(InitializationResult result) {
super.onPostExecute(result);
progressDialog.dismiss();
if (result.isValid()) {
mainActivity.finalizeSetup();
}
else {
AlertDialog.Builder dialog = new AlertDialog.Builder(mainActivity);
dialog.setTitle("Error on initialization");
dialog.setMessage(result.getReason());
dialog.setPositiveButton("Ok",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
mainActivity.finish();
}
});
dialog.show();
}
processController = null;
}
@Override
protected void onCancelled() {
super.onCancelled();
Log.i(TAG, "onCancelled executed");
Log.i(TAG, "set CancelNotification status to cancelled.");
ProcessNotification.setCancelled(true);
progressDialog.dismiss();
try {
Log.i(TAG, "clearing files");
PersistenceManager.clearFiles();
Log.i(TAG, "files cleared");
}
catch (IOException e) {
Log.e(TAG, "not able to clear files.");
}
processController = null;
mainActivity.finish();
}
}
Run Code Online (Sandbox Code Playgroud)
这是JSONParser的主体.(更新:我设置方法非静态但问题仍然存在.)我省略了JSON对象的对象创建,因为我不认为这是错误:
public class JsonStringParser {
private static String TAG = JsonStringParser.class.getSimpleName();
public static synchronized List<Zone> parse(String jsonString) throws ParseException, InterruptedIOException {
JSONParser jsonParser = new JSONParser();
Log.i(TAG, "start parsing JSON String with length " + ((jsonString != null) ? jsonString.length() : "null"));
List<Zone> zones = new ArrayList<Zone>();
//does a lot of JSON parsing here
Log.i(TAG, "finished parsing JSON String");
jsonParser = null;
return zones;
}
}
Run Code Online (Sandbox Code Playgroud)
这是堆转储,它显示了问题:

这是详细列表,显示此问题与arraylist有关.

任何想法在这里有什么问题?顺便说一句:由于没有详细信息,我不知道其他泄漏是什么.
也许很重要:此图表显示了我一次又一次不启动和停止应用程序时的状态.这是一个干净的开始图.但是,当我多次开始和停止时,由于空间不足,可能会导致问题.
这是一个真正的崩溃图.我在初始化几次时启动并停止了应用程序:

[更新]
我通过不将Android上下文存储到我的ApplicationContext类中并使PersistenceManager非静态来缩小它.这个问题没有改变,所以我完全相信它与全局存储Android上下文的事实无关.它仍然是上图中的"问题可疑1".所以我必须对这个巨大的清单做点什么,但是什么呢?我已经尝试将其序列化,但是取消分配此列表需要的时间比20秒长,所以这不是一个选项.
现在我尝试了不同的东西.我踢出了整个ApplicationContext,所以我不再有任何静态引用了.我试图在MainActivity中保存Zone对象的ArrayList.虽然我至少重构了运行应用程序所需的部分,所以我甚至没有将Array或Activity传递给我需要它的所有类,我仍然以不同的方式遇到同样的问题,所以我的猜测是区域对象本身就是问题所在.或者我无法正确读取堆转储.请参阅下面的新图表.这是一个简单的应用程序启动没有干扰的结果.
[更新]
我得出结论,没有内存泄漏,因为"内存在一个实例中累积"听起来不像是泄漏.问题是一次又一次地启动和停止会启动新的AsyncTasks,如图所示,因此解决方案是不启动新的AsyncTask.我在SO上找到了一个可能的解决方案,但它对我来说还不行.

我的最终解决方案
我现在自己找到了解决方案。它运行稳定,并且当我多次启动和停止应用程序时不会产生内存泄漏。这个解决方案的另一个优点是我能够剔除所有这些ProcessNotification.isCancelled()部分。
关键是在我的 ApplicationContext 中保存对 InitializationTask 的引用。通过这种方法,当我启动一个新的 MainActivity 时,我可以在新的 MainActivity 中恢复正在运行的 AsyncTask。这意味着我永远不会启动多个 AsyncTask,但我会将每个新的 MainActivity 实例附加到当前正在运行的任务。旧的 Activity 将被分离。这看起来像这样:
ApplicationContext 中的新方法:
public static void register(InitializationTask initializationTask) {
ApplicationContext.initializationTask = initializationTask;
}
public static void unregisterInitializationTask() {
initializationTask = null;
}
public static InitializationTask getInitializationTask() {
return initializationTask;
}
Run Code Online (Sandbox Code Playgroud)
MainActivity
(我必须将进度对话框放在这里,否则如果我停止并启动新的活动,它就不会显示):
@Override
protected void onStart() {
super.onStart();
progressDialog = new ProgressDialog(this);
progressDialog.setMessage("Processing.\nPlease wait...");
progressDialog.setIndeterminate(true); // means that the "loading amount" is not measured.
progressDialog.setCancelable(true);
progressDialog.show();
if (ApplicationContext.getInitializationTask() == null) {
initializationTask = new InitializationTask();
initializationTask.attach(this);
ApplicationContext.register(initializationTask);
initializationTask.execute((Void[]) null);
}
else {
initializationTask = ApplicationContext.getInitializationTask();
initializationTask.attach(this);
}
}
Run Code Online (Sandbox Code Playgroud)
MainActivity 的“onPause”包含initializationTask.detach();和progressDialog.dismiss();。finalizeSetup();也关闭对话框。
InitializationTask 还包含两个方法:
public void attach(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
public void detach() {
mainActivity = null;
}
Run Code Online (Sandbox Code Playgroud)
任务的onPostExecuteApplicationContext.unregisterInitializationTask();调用.