相关demo源码;java
本文基于: macOS:10.13/AS:3.4/Android build-tools:28.0.0/jdk: 1.8/apktool: 2.3.3android
在 Activity
中建立 Handler
内部类时,AS会给提内存泄露提示及解决方案:git
This Handler class should be static or leaks might occur (anonymous android.os.Handler)
Inspection info:Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue.
If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows:
1. Declare the Handler as a static class;
2. In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler;
3. Make all references to members of the outer class using the WeakReference object.
复制代码
先简单测试下,运行以下代码,而后手机屡次进行横竖屏切换,经过 AS 提供的 Profiler
监控内存变化:github
// HandlerTestActivity.java public class HandlerTestActivity extends AppCompatActivity { // 建立匿名Handler内部类的对象 private Handler leakHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_test); leakHandler.postDelayed(new Runnable() { @Override public void run() { Logger.d("leakHandler 延迟执行,内存泄露测试"); } }, 5 * 60 * 1000); } } 复制代码
内存出现了明显了升高;shell
简单描述下缘由:markdown
Handler
内部类定义在ui线程中,所以使用的主线程的 Looper
和 MessageQueue
;MessageQueue
中的 Message
会持有 Handler
对象;Handler
内部类对象持有着外部 Activity
的强引用;以上三点致使当有 Message
未被处理以前, 外部类 Activity
会一直被强引用,致使即便发生了销毁,也没法被GC回收;app
所以处理方法一般有两种:ide
Activity
销毁时取消全部的 Message
,即 leakHandler.removeCallbacksAndMessages(null);
Activity
的强引用;AS给出的提示方案属于第二种, 咱们经过smali源码来一步步探究验证下;oop
上面的 Java 代码对应的 smali 源码以下:post
# HandlerTestActivity.smali .class public Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; .super Landroid/support/v7/app/AppCompatActivity; .source "HandlerTestActivity.java" # 声明了成员变量 `leakHandler` # instance fields .field private leakHandler:Landroid/os/Handler; # direct methods .method public constructor <init>()V .locals 1 .line 20 invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V # `HandlerTestActivity$1` 是匿名内部类, 此处建立了该类的一个对象,并将其赋值给 v0 寄存器 .line 26 new-instance v0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$1; # p0 表示 `HandlerTestActivity` 对象自身 # 此处表示调用 `HandlerTestActivity$1` 对象的 `init(HandlerTestActivity activity)` 方法 invoke-direct {v0, p0}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$1;-><init>(Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;)V # 将 v0 寄存器的值赋值给了成员变量 `leanHandler` # 因为 `leanHandler` 变量的类型是 `Landroid/os/Handler;` , 可知 `HandlerTestActivity$1` 是 `Handler` 的子类 # 结合上一句代码,咱们就知道 `HandlerTestActivity$1` 会以某种形式持有 `HandlerTestActivity` 的引用 iput-object v0, p0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->leakHandler:Landroid/os/Handler; return-void .end method 复制代码
再来看看 HandlerTestActivity$1
类的代码:
# HandlerTestActivity$1.smali # 指明了本类 `HandlerTestActivity$1` 是 `Handler` 的子类 .class Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$1; .super Landroid/os/Handler; .source "HandlerTestActivity.java" # `EnclosingClass` 代表本类位于 `HandlerTestActivity` 中 # annotations .annotation system Ldalvik/annotation/EnclosingClass; value = Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; .end annotation # `InnerClass` 代表这是一个内部类, 而 `name=null` 表示这是匿名内部类 .annotation system Ldalvik/annotation/InnerClass; accessFlags = 0x0 name = null .end annotation # `synthetic` 代表这是由编译器自动生成的成员变量 # 经过此处咱们知道了, 本 `Handler` 子类强引用了 `Activity`,并将其设置为了成员变量 # instance fields .field final synthetic this$0:Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; # direct methods .method constructor <init>(Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;)V .locals 0 # 使用寄存器 p1 表示传递进来的方法参数 `this$0`, 它是 `HandlerTestActivity` 对象 .param p1, "this$0" # Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; # 将形参 this$0 赋值给本类成员变量 this$0,即: # this.this$0=this$0 .line 26 iput-object p1, p0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$1;->this$0:Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; invoke-direct {p0}, Landroid/os/Handler;-><init>()V return-void .end method 复制代码
以上很明确的说明了: 非静态内部类会持有外部类的引用,且是强引用;
P.S. 上面的代码是匿名内部类,对于具名内部类也是同样的结果;
咱们再定义一个静态内部类,看下其smali源码:
// HandlerTestActivity.java public class HandlerTestActivity extends AppCompatActivity { static class MyEmptyStaticHandler extends Handler { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } } } 复制代码
# HandlerTestActivity.smali .class public Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; .super Landroid/support/v7/app/AppCompatActivity; .source "HandlerTestActivity.java" # 定义了内部类列表 # annotations .annotation system Ldalvik/annotation/MemberClasses; value = { Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$MyEmptyStaticHandler; } .end annotation # 声明成员变量 `myEmptyStaticHandler` # instance fields .field private myEmptyStaticHandler:Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$MyEmptyStaticHandler; # direct methods .method public constructor <init>()V .locals 1 .line 20 invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V .line 35 new-instance v0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$MyEmptyStaticHandler; # 能够发现此处并未把 `HandlerTestActivity` 对象做为参数传递到 `init()` 方法中 invoke-direct {v0}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$MyEmptyStaticHandler;-><init>()V iput-object v0, p0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->myEmptyStaticHandler:Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$MyEmptyStaticHandler; return-void .end method 复制代码
由以上代码便可知: 静态内部类并不会持有外部类的引用;
这就解释了AS给出的优化建议的第一条;
WeakReference
咱们一般都须要在 Handler
的消息处理逻辑中去操做 Activity
,如更新UI等,所以它仍是须要持有 Activity
的引用,但同时又不能阻碍 GC 的回收操做;
天然而然就想到 WeakReference
,关于 Java 的四种引用此处不展开;
// HandlerTestActivity.java public class HandlerTestActivity extends AppCompatActivity { private String pName; private String pName1; private static String sName; private static String sName1; // 编译器会自动生成一个与外部类处于相同package下的内部类: `HandlerTestActivity$MyStaticHandler.smali` private static class MyStaticHandler extends Handler { private final WeakReference<HandlerTestActivity> mWkActivity; public MyStaticHandler(HandlerTestActivity activity) { mWkActivity = new WeakReference<HandlerTestActivity>(activity); } public Activity getActivity() { return mWkActivity.get(); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); HandlerTestActivity targetAct = mWkActivity.get(); // 经过 `WeakReference` 对象去操做外部 `Activity` 属性和事件 if (targetAct != null && !targetAct.isFinishing()) { String name = targetAct.pName; // 访问外部类private属性 String sName = HandlerTestActivity.sName; targetAct.callPrivateFunc(); // 调用外部类private的方法 targetAct.pName = ""; // 设置外部类private属性的值 } } } } 复制代码
看一下生成的smali类文件结构:
➜ Desktop cd app-debug/smali/org/lynxz/smalidemo/ui
➜ ui tree
.
└── activity
├── HandlerTestActivity$1.smali # 匿名内部类
├── HandlerTestActivity$MyStaticHandler.smali # 具名内部类
└── HandlerTestActivity.smali # 外部类smali
复制代码
private
AS 给出的优化提示第三条: 经过持有的外部类对象去操做或访问外部类的全部方法和变量;
此处就产生了一个疑问:
Java 四种访问权限: public
/protect
/default
/private
, 既然编译器会自动生成一个同package下的内部类,为什么其仍能够访问外部类的private参数和方法呢?
看下 MyStaticHandler
源码:
# HandlerTestActivity$MyStaticHandler.smali # instance fields .field private final mWkActivity:Ljava/lang/ref/WeakReference; .annotation system Ldalvik/annotation/Signature; value = { "Ljava/lang/ref/WeakReference<", "Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;", ">;" } .end annotation .end field .method public handleMessage(Landroid/os/Message;)V .locals 4 # 使用寄存器 p1 表示方法形参 `msg` 的值 .param p1, "msg" # Landroid/os/Message; .line 57 invoke-super {p0, p1}, Landroid/os/Handler;->handleMessage(Landroid/os/Message;)V # 获取成员变量 WeakRefrence 所持有的 `HandlerTestActivity` 对象,并定义为局部变量 targetAct,赋值给 v0 寄存器 # 对应Java源码: `HandlerTestActivity targetAct = mWkActivity.get();` .line 58 iget-object v0, p0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity$MyStaticHandler;->mWkActivity:Ljava/lang/ref/WeakReference; invoke-virtual {v0}, Ljava/lang/ref/WeakReference;->get()Ljava/lang/Object; move-result-object v0 check-cast v0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; .line 59 .local v0, "targetAct":Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; # 若该对象为null,则跳转到标签 cond_0 处继续执行 if-eqz v0, :cond_0 # 获取 `activity.isFinishing()` 值并赋值给v1寄存器 # 若 v1 == true ,则跳转到的标签 `cond_0` 定义处继续执行 invoke-virtual {v0}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->isFinishing()Z move-result v1 if-nez v1, :cond_0 # 此处调用 `HandlerTestActivity` 的静态方法 `access$000()` 并返回一个 `String` 值,并值赋值给 v1,而 v1 表示局部变量 name # 所以对应于Java源码: `String name = targetAct.pName;` .line 60 invoke-static {v0}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->access$000(Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;)Ljava/lang/String; move-result-object v1 .line 61 .local v1, "name":Ljava/lang/String; # 用 v1 寄存器表示局部变量 name # 对应Java源码: `String sName = HandlerTestActivity.access$100()` invoke-static {}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->access$100()Ljava/lang/String; move-result-object v2 .line 62 .local v2, "sName":Ljava/lang/String; # 对应Java源码: `targetAct.callPrivateFunc();` invoke-static {v0}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->access$200(Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;)V # 对应Java源码: `targetAct.pName = "";` .line 63 const-string v3, "" invoke-static {v0, v3}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->access$002(Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;Ljava/lang/String;)Ljava/lang/String; .line 65 .end local v1 # "name":Ljava/lang/String; .end local v2 # "sName":Ljava/lang/String; :cond_0 return-void .end method 复制代码
以上源码中的 access$100()
/access$200()
等方法并非咱们定义的,经过其命名方式也能知晓这是编译器生成的,咱们看下他们是作什么用的:
# HandlerTestActivity.smali # `synthetic` 代表这是编译器自动生成的方法, package访问权限的静态方法 # 用于访问实例的私有成员变量 pName .method static synthetic access$002(Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;Ljava/lang/String;)Ljava/lang/String; .locals 0 .param p0, "x0" # Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; .param p1, "x1" # Ljava/lang/String; .line 20 iput-object p1, p0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->pName:Ljava/lang/String; return-object p1 .end method # 编译器自动生成的静态方法,用于类的私有成员变量 sName .method static synthetic access$100()Ljava/lang/String; .locals 1 .line 20 sget-object v0, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->sName:Ljava/lang/String; return-object v0 .end method # 编译器自动生成的静态方法,用于访问实例的私有方法 callPrivateFunc .method static synthetic access$200(Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;)V .locals 0 .param p0, "x0" # Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity; .line 20 invoke-direct {p0}, Lorg/lynxz/smalidemo/ui/activity/HandlerTestActivity;->callPrivateFunc()V return-void .end method 复制代码
由此咱们知道了: 若编译器发现内部类须要访问外部类的私有属性或方法,则会自动生成一个对应包访问权限的静态方法,间接调用;
WeakReference
, 能够实现既能访问外部类的成员,又不影响GC;