Мое приложение работает нормально, пока я не прерываю процесс инициализации при первом запуске после установки, выйдя и запуская приложение несколько раз, пока процесс инициализации еще не завершен. Логика обработки и 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 = новый ArrayList (зоны);
но это не повлияло. Я уже пытался поместить атрибут зон в другой статический класс, поскольку 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. (UPDATE: я устанавливаю метод не статическим, но проблема сохраняется.) Я опускаю создания объектов из объектов 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;
}
}
Вот куча дампа, который показывает проблему:
Это список деталей, который показывает, что эта проблема имеет какое-то отношение к арраисту.
Любые идеи, что здесь неправильно? Btw: Я не знаю, что другая утечка, поскольку нет информации о деталях.
Возможно, важно. Эта диаграмма показывает статус, когда я не запускаю и не останавливаю приложение снова и снова. Это схема чистого старта. Но когда я начинаю и останавливаюсь несколько раз, это может привести к проблемам из-за нехватки места.
Вот диаграмма реального сбоя. Я начал и остановил приложение при инициализации несколько раз:
[UPDATE]
Я немного сузил его, не сохраняя контекст Android в моем классе ApplicationContext и делая PersistenceManager нестатичным. Проблема не изменилась, поэтому я абсолютно уверен, что это не связано с тем, что я храню Android-контекст во всем мире. Это все еще "Проблемный Подозреваемый 1" на графике выше. Поэтому я должен что-то сделать с этим огромным списком, но что? Я уже попытался его сериализовать, но неанализирующий этот список занимает гораздо больше времени, чем 20 секунд, поэтому это не вариант.
Теперь я попробовал что-то другое. Я вытолкнул весь ApplicationContext, поэтому у меня больше нет статических ссылок. Я попытался удерживать объекты ArrayList of Zone в MainActivity. Хотя я реорганизовал, по крайней мере, те части, которые мне нужно, чтобы приложение выполнялось, поэтому я даже не передал массив или активность всем классам, в которых он мне нужен, у меня все еще есть одна и та же проблема по-другому, поэтому я предполагаю, что объекты Зоны сами по себе являются проблемой. Или я не могу правильно прочитать кучу кучи. См. Новые графики ниже. Это результат простого запуска приложения без помех.
[UPDATE]
Я пришел к выводу, что утечки памяти нет, потому что "память накапливается в одном экземпляре" не похожа на утечку. Проблема в том, что запуск и остановка снова и снова запускает новые AsyncTasks, как видно на одном графике, поэтому решение должно состоять в том, чтобы не запускать новую AsyncTask. Я нашел возможное решение для SO, но он пока не работает для меня.