WeakReference 弱引用与内存泄漏
compile 'com.anthonycr.grant:permissions:1.1.2'
版本更新后,作者为了避免内存泄漏,在源码中添加了一个弱引用来存储一个抽象类。
但在用户有多个权限需要选择的时候,抽象类有时候会被回收。当用户确认权限之后,没有调用到回调函数。
android内存空间分配
首先, 让我们快速看下Android启动流程. 与众多基于Linux内核的系统类似, 启动系统时, bootloader启动内核和init进程. init进程分裂出更多名为"daemons(守护进程)"的底层的Linux进程, 诸如android debug deamon, USB deamon等. 这些守护进程处理底层硬件相关的接口.
随后, init进程会启动一个非常有意思的进程---"Zygote". 顾名思义, 这是一个Android平台的非常基础的进程. 这个进程初始化了第一个VM, 并且预加载了framework和众多App所需要的通用资源. 然后它开启一个Socket接口来监听请求, 根据请求孵化出新的VM来管理新的App进程. 一旦收到新的请求, Zygote会基于自身预先加载的VM来孵化出一个新的VM创建一个新的进程.
app launch启动Zygote之后, init进程会启动runtime进程. Zygote会孵化出一个超级管理进程---System Server. SystemServer会启动所有系统核心服务, 例如Activity Manager Service, 硬件相关的Service等. 到此, 系统准备好启动它的第一个App进程---Home进程了.
dalvik的Heap和Stack当启动一个Android程序时,会启动一个Dalvik VM进程,系统会给它分配固定的内存空间(16M,32M不定),这块内存空间会映射到RAM上某个区域。然后这个Android程序就会运行在这块空间上。Java里会将这块空间分成Stack栈内存和Heap堆内存。stack里存放对象的引用,heap里存放实际对象数据。
在程序运行中会创建对象,如果未合理管理内存,比如不及时回收无效空间就会造成内存泄露,严重的话可能导致使用内存超过系统分配内存,即内存溢出OOM,导致程序卡顿甚至直接退出。
也就是带有回调函数的对象会放到内存堆中。当然,一般处理内存泄漏都是处理内存堆,这里只是提一下。
弱引用
在Java里, 当一个对象o被创建时, 它被放在Heap里. 当GC运行的时候, 如果发现没有任何引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1)没有任何引用指向它 2)GC被运行
private synchronized void addPendingAction(@NonNull String[] permissions,
@Nullable PermissionsResultAction action) {
if (action == null) {
return;
}
action.registerPermissions(permissions);
mPendingActions.add(new WeakReference<>(action));
}
public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) {
int size = permissions.length;
if (results.length < size) {
size = results.length;
}
Iterator<WeakReference<PermissionsResultAction>> iterator = mPendingActions.iterator();
while (iterator.hasNext()) {
PermissionsResultAction action = iterator.next().get();
for (int n = 0; n < size; n++) {
if (action == null || action.onResult(permissions[n], results[n])) {
iterator.remove();
break;
}
}
}
for (int n = 0; n < size; n++) {
mPendingRequests.remove(permissions[n]);
}
}
在源码中执行到这儿的时候,action有时候变成了null 。
在addPendingAction操作中有PermissionsResultAction(强引用)引用指向,但到notifyPermissionsChange()的时候PermissionsResultAction依然被系统回收了,回调函数不被执行。
这是因为编译器在发现进入while循环之后, PermissionsResultAction已经没有被使用, 所以进行了优化(将其置空).
写了一段测试代码,对象最后的确被回收了。
public static void main(String[] args) {
List<WeakReference<PermissionAction>> mPendingActions = new ArrayList<>(1);
mPendingActions.add(new WeakReference<>(new PermissionAction()));
int i = 0;
WeakReference<PermissionAction> actionPermission = null;
Iterator<WeakReference<PermissionAction>> iterator = mPendingActions.iterator();
if(iterator.hasNext()){
actionPermission = iterator.next();
}
while (true) {
PermissionAction action = actionPermission.get();
if (action != null) {
i++;
System.out.println("Object is alive for " + i + " loops - " + action);
} else {
System.out.println("Object has been collected.");
break;
}
}
}
- WeakReference的一个特点是它何时被回收是不可确定的, 因为这是由GC运行的不确定性所确定的. 所以, 一般用weak reference引用的对象是有价值被cache, 而且很容易被重新被构建, 且很消耗内存的对象.
虽然弱引用能让app避免了内存溢出的问题,但也带来了不确定性。
弱引用可以用于Handler,一般的Handler写法可能会导致内存泄漏。因为非静态的内部类持有外部类的对象,而handler又会由于msg的处理而可能常驻在进程中,在activity或者service destroy后,不能及时被系统回收,导致内存泄漏。
建议写法:
private static class OuterHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public OuterHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity != null) {
// do something...
}
}
}