需要帮助来了解我的Android应用中的内存泄漏

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上找到了一个可能的解决方案,但它对我来说还不行.

记忆错误4 记忆错误5

Bev*_*vor 0

我的最终解决方案

我现在自己找到了解决方案。它运行稳定,并且当我多次启动和停止应用程序时不会产生内存泄漏。这个解决方案的另一个优点是我能够剔除所有这些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();调用.