我在活动类中初始化一个成员变量
private String test = new String("A");
然后我用它在从活动启动的匿名AsyncTask的doInBackground()
方法中的一个长时间循环中写入Log
new AsyncTask<Void, Void, Void>() {
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(Void result) {
}
@Override
protected Void doInBackground(Void... params) {
for (int j = 10; j >= 0; j--) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("DOINBACKGROUND ", test);
}
}.execute();
问题:当我在Asynctask
仍在执行时离开"活动",并且在执行"活动"的onDestroy()
后,我在日志中看到成员变量仍处于活动状态且未被销毁。有人能向我解释一下这是怎么可能的吗?
有界问题:成员变量仍然是活动的,因为即使在onDestroy()
之后,由于gc标准和gc优先级,它还没有被乱码。这没关系。
但我怀疑
- 在引用asynctask结束其内容之前,"test"成员变量(和活动的上下文)不会出现乱码,因此asynctask可以始终且肯定地完成其
doInBackground()
而不会崩溃(尽管会消耗临时内存)
或者改为
- 无论asynctask是否正在运行,"test"成员变量迟早都会被篡改,这可能会导致asysnctacsk崩溃
不要混淆垃圾收集和活动生命周期。
一旦GC根对象中所有可追溯到对象的引用都消失了,就可以对对象进行垃圾收集。
onDestroy()
是活动生命周期的一部分。从本质上讲,框架已经完成了活动,并放弃了它可能持有的对活动和相关资源的任何引用。
当您实例化一个匿名内部类时,它会得到对父对象的隐式引用。换句话说,一个匿名的内部类总是一个非静态的内部类。此父引用是对您的Activity
的引用。然后,通过调用execute()
将异步任务对象传递给执行器,执行器在需要时保留异步任务引用,同时防止引用的活动被垃圾收集。
因此,我的代码片段示例中的异步任务将始终且肯定地完成其doInBackground(),而不会因NPE而崩溃?
是的。但请考虑以下内容:
-
将内部类设为
static
,除非它们特别需要访问父对象。由于匿名的内部类总是非static
的,所以使它们成为非匿名的。 -
在具有独立生命周期的对象(如活动或片段)中混合异步操作是脆弱的,最好避免。例如,问题包括取消、将结果传递到消失的对象,以及保持GC阻止对昂贵对象(如活动)的引用。
首先,onDestroy()发生在销毁活动之前,并要求活动管理器释放与该活动相关的所有资源。这意味着所有活动的资源都将被gc删除。然而,它并没有强制gc从内存中删除资源,它们只是候选者。gc将根据这些候选人的大小、年龄、最近性、类型等对其进行评分,每当系统需要更多空间时,它就会要求gc删除候选人,并根据他们的分数进行评分。得分较高的候选人最有可能首先被删除。
当你突然看到崩溃时,即使在你退出应用程序之后,这一点也会很明显。
如果您创建另一个活动并对其调用System.gc(),则可能会看到此崩溃。
干杯A.
成员变量test
在"活动"的实例被垃圾回收之前不会被垃圾回收器回收。
在AsyncTask完成之前,垃圾收集器不会回收活动实例,因为AsyncTask持有对活动实例的引用。
AsyncTask实例在完成其工作之前不会被垃圾回收。
AsyncTask将在不崩溃的情况下完成doInBackground()
方法。当然可以
AsyncTask与包含它的"活动"的生命周期没有关联。因此,例如,如果在"活动"内部启动AsyncTask,并且用户旋转设备,则"活动"将被销毁(并创建一个新的"活动实例"),但AsyncTask不会消亡,而是继续存在,直到它完成。
然后,当AsyncTask完成时,它不是更新新活动的UI,而是更新活动的前一个实例(即创建它但不再显示的实例!)。这可能会导致异常(类型为java.lang.IollegalArgumentException:如果使用findViewById检索"活动"内的视图,则视图未附加到窗口管理器)。
这也有可能导致内存泄漏,因为AsyncTask维护了对Activty的引用,只要AsyncTask保持活动状态,就可以防止"活动"被垃圾收集。
出于这些原因,对长时间运行的后台任务使用AsyncTasks通常是个坏主意。相反,对于长时间运行的后台任务,应该使用不同的机制(如服务)。
我猜异步任务不是静态的,它保存了对封闭活动的引用,防止活动被垃圾收集。
如果你想知道匿名类是如何导致活动泄漏的,请参阅这个链接
此外,您还可以尝试将设备旋转到多个设备,并通过使用以下命令并检查活动的数量来检查是否存在同一活动的多个实例。
adb shell dumpsys meminfo your.app.packagename
Applications Memory Usage (kB):
Uptime: 40343748 Realtime: 164852669
** MEMINFO in pid 16561 [samsung.svl.com.graph] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 5708 5664 16 2380 20480 8849 11630
Dalvik Heap 1163 972 136 27080 37459 29598 7861
Dalvik Other 604 604 0 4
Stack 288 288 0 0
Other dev 4 0 4 0
.so mmap 3569 992 72 2120
.apk mmap 39 0 0 0
.ttf mmap 0 0 0 0
.oat mmap 539 0 4 0
.art mmap 747 524 4 704
Other mmap 5 4 0 0
GL mtrack 10951 10951 0 0
Unknown 2260 2260 0 92
TOTAL 25877 22259 236 32380 57939 38447 19491
Objects
Views: 17 ViewRootImpl: 1
AppContexts: 3 **Activities: 1**
Assets: 3 AssetManagers: 3
Local Binders: 8 Proxy Binders: 23
Parcel memory: 3 Parcel count: 12
Death Recipients: 0 OpenSSL Sockets: 0
SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0