我的应用程序运行良好,直到我在安装后首次启动时中断初始化过程为止,只要初始化过程尚未完成,请多次退出并启动该应用程序。处理逻辑和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;
    }
}

我已经尝试过像这样存储区域



但这没有效果。我已经尝试将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();
    }
}

也许是另一个有用的信息,该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();
    }
}

这是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;
    }
}

这是显示问题的堆转储:

这是详细信息列表,它表明此问题与arraylist有关。

有什么主意在这里吗?顺便说一句:我不知道还有什么其他泄漏,因为没有详细信息。

也许很重要:此图显示了我一次又一次不启动和停止应用程序时的状态。这是一个干净的开始的图。但是,当我多次启动和停止时,由于空间不足,可能会导致问题。

这是一个真实的崩溃图。在初始化过程中,我多次启动和停止了该应用程序:

[更新]
通过不将Android上下文存储到我的ApplicationContext类中并使PersistenceManager为非静态,我将其范围缩小了。这个问题没有改变,所以我绝对确定这与我在全局存储Android上下文无关。仍然是上图的“问题可疑1”。所以我必须对这个庞大的 list 做些什么,但是呢?我已经尝试过对其进行序列化,但是取消序列化此列表所花费的时间比20秒要长得多,因此这不是一个选择。

现在我尝试了一些不同的方法。我踢出了整个ApplicationContext,所以不再有任何静态引用。我试图在MainActivity中保存Zone对象的ArrayList。尽管我至少重构了运行应用程序所需的部分,所以我什至没有将Array或Activity传递给需要的所有类,但是我仍然以不同的方式遇到相同的问题,所以我的猜测是该区域对象本身就是问题所在。否则我无法正确读取堆转储。请参阅下面的新图表。这是一个简单的应用程序启动而没有干扰的结果。

[更新]
我得出的结论是没有内存泄漏,因为“内存是在一个实例中累积的” 听起来不像是泄漏。问题是,一遍又一遍地启动和停止会启动新的AsyncTask,因此解决方案是不启动新的AsyncTask。我在SO上找到了一种可能的解决方案,但它对我来说还行不通。

最佳答案

首先,我必须同意Emile:



这也适用于您代码中的所有其他static方法。 static方法与全局函数并没有真正的不同。您将在那里建立一个充满static方法的大意大利面盘。尤其是当他们开始共享某种状态时,它迟早会崩溃或产生晦涩的结果,而如果使用适当的设计,尤其是在Android等高度易懂的平台的情况下,您将无法获得这些结果。

还引起我注意的是,请注意,在onCancelled完成之前不会调用AsyncTaskdoInBackground方法。因此,您的全局取消标志(ProcessNotification.isCancelled())或多或少毫无值(value)(如果仅在显示的代码段落中使用)。

同样,从您发布的内存镜像中,zones列表包含“仅” 31个项目。应该持有多少?它增加多少?如果实际增加,则提示可能在JsonStringParser.parse方法中,该方法又是static。如果它在某些高速缓存中保存了项目列表,并且控制逻辑无法正常工作(例如,存在多个线程同时访问它),则每次调用该缓存时,都可能会将项目添加到该高速缓存中。

  • 猜测1:由于解析方法是static,因此在关闭应用程序时不会(有必要)清除此数据。 static初始化一次,在这种情况下,除非(物理vm-)进程停止,否则永远不要将其初始化。但是,即使应用程序已停止,Android也无法保证杀死该进程(see for example a wonderful explanation here)。因此,您可能会在(可能是解析的)代码的某些static部分中累积一些数据。
  • 猜猜2:由于您要多次重新启动应用程序,因此后台线程会并行运行多次(假设:每次重新启动应用程序时,都会产生一个新线程。请注意,您的代码没有对此的防范措施。 )第一次解析仍在运行,由于全局zones变量仍然不包含任何值,因此开始了另一个解析。全局函数parse可能不是线程安全的,并且多次将多个数据放入最终返回的列表中,从而产生了越来越大的列表。同样,通常通过不使用static方法(并注意多线程)来避免这种情况。

  • (因此,代码还不完整,所以可能甚至还有其他隐患。)

    关于android - 需要帮助以了解我的Android应用程序中的内存泄漏,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/13789289/

    10-11 22:48
    查看更多