我的应用程序运行良好,直到我在安装后首次启动时中断初始化过程为止,只要初始化过程尚未完成,请多次退出并启动该应用程序。处理逻辑和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
完成之前不会调用AsyncTask
的doInBackground
方法。因此,您的全局取消标志(ProcessNotification.isCancelled()
)或多或少毫无值(value)(如果仅在显示的代码段落中使用)。
同样,从您发布的内存镜像中,zones
列表包含“仅” 31个项目。应该持有多少?它增加多少?如果实际增加,则提示可能在JsonStringParser.parse
方法中,该方法又是static
。如果它在某些高速缓存中保存了项目列表,并且控制逻辑无法正常工作(例如,存在多个线程同时访问它),则每次调用该缓存时,都可能会将项目添加到该高速缓存中。
static
,因此在关闭应用程序时不会(有必要)清除此数据。 static
初始化一次,在这种情况下,除非(物理vm-)进程停止,否则永远不要将其初始化。但是,即使应用程序已停止,Android也无法保证杀死该进程(see for example a wonderful explanation here)。因此,您可能会在(可能是解析的)代码的某些static
部分中累积一些数据。 zones
变量仍然不包含任何值,因此开始了另一个解析。全局函数parse
可能不是线程安全的,并且多次将多个数据放入最终返回的列表中,从而产生了越来越大的列表。同样,通常通过不使用static
方法(并注意多线程)来避免这种情况。 (因此,代码还不完整,所以可能甚至还有其他隐患。)
关于android - 需要帮助以了解我的Android应用程序中的内存泄漏,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/13789289/