LeakCanary提供了一种很方便的方式,让我们在开发阶段测试内存泄露,我们不需要自己根据内存块来分析内存泄露的原因,我们只需要在项目中集成他,然后他就会帮我们检测内存泄露,并给出内存泄露的引用链
集成
- 在gradle中添加依赖,这种不区分debug和release
implementation ‘com.squareup.leakcanary:leakcanary-android:1.5.1’
- 重写Application
public class App extends Application {
private RefWatcher mRefWatcher;
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
mRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
App application = (App) context.getApplicationContext();
return application.mRefWatcher;
}
}
- 比如我们在Activity中制造Handler发延迟消息的内存泄露
public class ActivityOne extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
}
}, 100000);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
- 然后我们打开这个activity然后再关闭,桌面就会出现一个leaks的图标,然后我们打开它
这就是基本使用,其实这种形式只能监视Activity的内存泄露,至于为什么只能监视Activity的内存泄露我们下面再分析,如果想要监视其他的内存泄露怎么办,比如要监视Fragment的内存泄露,可以这样写,主动监视想要监视的对象
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = App.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
原理概述
通过监听Activity的onDestory,手动调用GC,然后通过ReferenceQueue+WeakReference,来判断Activity对象是否被回收,然后结合dump Heap的hpof文件,通过Haha开源库分析泄露的位置
主要的知识点
注册Activity的生命周期的监听器
通过Application.registerActivityLifecycleCallbacks()
方法注册Activity的生命周期的监听器,每一个Actvity的生命周期都会回调到这个ActivityLifecycleCallbacks上,如果一个Activity走到了onDestory,那么就意味着他就不再存在,然后检测这个Activity是否是真的被销毁
通过ReferenceQueue+WeakReference,来判断对象是否被回收
WeakReference创建时,可以传入一个ReferenceQueue对象,假如WeakReference中引用对象被回收,那么就会把WeakReference对象添加到ReferenceQueue中,可以通过ReferenceQueue中是否为空来判断,被引用对象是否被回收
详细介绍推荐博客:www.jianshu.com/p/964fbc301…
MessageQueue中加入一个IdleHandler来得到主线程空闲回调
详细介绍请看Android Handler 源码解析
手动调用GC后还调用了System.runFinalization();
,这个是强制调用已失去引用对象的finalize方法
在可达性算法中,不可达对象,也不是非死不可,这时他们处于“缓刑”阶段,要宣告一个对象真正死亡需要至少俩个标记阶段, 如果发现对象没有引用链,则会进行第一次标记,并进行一次筛选,筛选的条件是此对象是否有必要进行finalize()方法,当对象没有覆盖finalize(),或者finalize()已经调用过,这俩种都视为“没有必要执行”
Apolication中可通过processName判断是否是任务执行进程
通过processName,来判断进程
public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo;
try {
packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);
} catch (Exception e) {
CanaryLog.d(e, “Could not get package info for %s”, context.getPackageName());
return false;
}
String mainProcess = packageInfo.applicationInfo.processName;
ComponentName component = new ComponentName(context, serviceClass);
ServiceInfo serviceInfo;
try {
serviceInfo = packageManager.getServiceInfo(component, 0);
} catch (PackageManager.NameNotFoundException ignored) {
// Service is disabled.
return false;
}
if (serviceInfo.processName.equals(mainProcess)) {
CanaryLog.d(“Did not expect service %s to run in main process %s”, serviceClass, mainProcess);
// Technically we are in the service process, but we’re not in the service dedicated process.
return false;
}
int myPid = android.os.Process.myPid();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.RunningAppProcessInfo myProcess = null;
List<ActivityManager.RunningAppProcessInfo> runningProcesses =
activityManager.getRunningAppProcesses();
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
if (process.pid == myPid) {
myProcess = process;
break;
}
}
}
if (myProcess == null) {
CanaryLog.d(“Could not find running process for %d”, myPid);
return false;
}
return myProcess.processName.equals(serviceInfo.processName);
}
源码分析
SDK初始化
mRefWatcher = LeakCanary.install(this);
这个是SDK向外暴露的方法,我们以此为入口进行源码的分析
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
install
方法首先初始化了一个AndroidRefWatcherBuilder
类,然后通过listenerServiceClass
方法设置了DisplayLeakService
,这个类主要用于分析内存泄露的结果信息,然后发送通知给用户
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder {
/**
- Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
- overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
*/
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
…
}
public class RefWatcherBuilder<T extends RefWatcherBuilder> {
…
/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
…
}
然后调用excludedRefs
方法添加白名单,在AndroidExcludedRefs
枚举类中定义了忽略列表信息,如果这些列表中的类发生了内存泄露,并不会显示出来,同时HeapAnalyzer
计算GCRoot强引用路径,也会忽略这些类,如果你希望自己项目中某个类泄露的,但是不希望他显示,就可以把类添加到这个上面
public enum AndroidExcludedRefs {
// ######## Android SDK Excluded refs ########
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
@Override void add(ExcludedRefs.Builder excluded) {
excluded.instanceField(“android.app.ActivityThread$ActivityClientRecord”, “nextIdle”)
.reason(“Android AOSP sometimes keeps a reference to a destroyed activity as a”
- " nextIdle client record in the android.app.ActivityThread.mActivities map."
- " Not sure what’s going on there, input welcome.");
}
}
…
}
最后调用了buildAndInstall
方法,创建了一个RefWatcher
对象并返回,这个对象是用于检测是否有对象未被回收导致的内存泄露
/**
- Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
因为分析泄露是在另一个进程进行的,所以判断当前启动的Application是否在分析内存泄露的进程中,如果是就直接返回DISABLED
,不在进行后续初始化,如果发现是在程序主进程中,就进行初始化
LeakCanary.enableDisplayLeakActivity(context);
主要作用是调用PackageManager
将DisplayLeakActivity
设置为可用。
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
public static void setEnabled(Context context, final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
从配置文件看LeakCanary这几个文件都是运行在新进程的,DisplayLeakActivity
默认enable=false
,这样就可以一开始隐藏启动图标
接着 ActivityRefWatcher.install((Application) context, refWatcher);
这里把refWatcher
当做参数传入,同时对Activity的生命周期进行了监听
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
// Make sure you don’t get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
首先就是注册Activity的生命周期的监听器lifecycleCallbacks
,这个监听器可以监听项目中所有Activity的的生命周期,然后在Activity销毁调用onActivityDestroyed
的时候,LeakCanary就会获取这个Activity,然后对其进行分析,看是否有内存泄露
分析内存泄露
这里分析对象是否内存泄露的是RefWatcher
类,下面简单介绍一下这个类的成员变量
- WatchExecutor watchExecutor:确保任务在主线程进行,同时默认延迟5s执行任务,留时间给系统GC
- DebuggerControl debuggerControl:控制中心
- GcTrigger gcTrigger:内部调用
Runtime.getRuntime().gc()
,手动触发GC - HeapDumper heapDumper:用于创建.hprof文件,用于储存head堆快照,可以知道哪些程序在大部分使用内存
- HeapDump.Listener heapdumpListener:分析结果完成后会告诉这个监听器
- ExcludedRefs excludedRefs:分析内存泄露的白名单
从上面可以看出,每当Activity销毁,就会调用RefWatcher
的watch
方法,去分析是否是内存泄露
public void watch(Object watchedReference) {
watch(watchedReference, “”);
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, “watchedReference”);
checkNotNull(referenceName, “referenceName”);
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
…(img-pATQ5Mgo-1712142020229)]
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-IxKpx6Ad-1712142020229)]
腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析
[外链图片转存中…(img-g2aFGRHF-1712142020230)]
资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图