我的应用程序运行良好,直到我在安装后第一次启动时中断初始化过程,只要初始化过程尚未完成,就退出并启动应用程序几次。处理逻辑和 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;
}
}
我已经尝试像这样存储区域
ApplicationContext.zones = new ArrayList(zones);
但它没有效果。我已经尝试将 zone 属性放入另一个静态类,因为 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;
}
}
这是显示问题的堆转储:
这是详细信息列表,表明此问题与数组列表有关。
有什么想法吗?顺便说一句:我不知道其他泄漏是什么,因为没有详细信息。
可能很重要:此图显示了我一遍又一遍地不启动和停止应用程序时的状态。这是一个干净的开始的图表。但是当我多次启动和停止时,由于空间不足,可能会导致问题。
这是一个真实崩溃的图表。我在初始化时启动和停止了应用程序几次:
[更新]
我通过不将 Android 上下文存储到我的 ApplicationContext 类并使 PersistenceManager 非静态来缩小范围。问题没有改变,所以我绝对确定它与我在全球存储 Android 上下文的事实无关。它仍然是上图中的“问题嫌疑人 1”。所以我必须对这个庞大的列表做点什么,但是什么?我已经尝试对其进行序列化,但取消序列化此列表所需的时间远远超过 20 秒,因此这不是一个选项。
现在我尝试了一些不同的东西。我踢出了整个 ApplicationContext 所以我没有任何静态引用了。我试图在 MainActivity 中保存 Zone 对象的 ArrayList。尽管我至少重构了使应用程序运行所需的部分,所以我什至没有将 Array 或 Activity 传递给我需要它的所有类,但我仍然以不同的方式遇到同样的问题,所以我的猜测是Zone 对象本身就是问题所在。或者我无法正确读取堆转储。请参阅下面的新图表。这是一个没有干扰的简单应用程序启动的结果。
[更新]
我得出的结论是没有内存泄漏,因为“内存是在一个实例中累积的”听起来不像是泄漏。问题是一遍又一遍地启动和停止会启动新的 AsyncTask,如一张图所示,因此解决方案是不启动新的 AsyncTask。我在 SO 上找到了一个可能的解决方案,但它还不适合我。