Android LeakCanary 源码解析

LeakCanary : https://github.com/square/leakcanary

version : 1.6.3

Header

LeakCanary 是一款专门用来侦测 Android 内存泄漏的类库。使用方式简单,代码侵入性低,基本上算是 Android 开发必备工具了。

今天就主要来分析一下 LeakCanary 的实现原理。在开头就简单地讲讲它的实现思路:LeakCanary 将检测的对象(一般是 Activity 或 Fragment)放入弱引用中,并且弱引用关联到引用队列中,触发 GC 之后,查看引用队列中是否存在该弱引用,如果发现没有,那么有可能发生内存泄漏了,dump 出堆内存快照进行分析。分析出泄漏实例后再查找到它的引用链,最后发送通知给开发者。

阅读更多

Android 深入理解 Binder

之前一直对 Binder 理解不够透彻,仅仅知道一些皮毛,所以最近抽空深入理解一下,并在这里做个小结。

Binder是什么

Binder 是 Android 系统中实现 IPC (进程间通信)的一种机制。Binder 原意是“胶水、粘合剂”,所以可以想象它的用途就是像胶水一样把两个进程紧紧“粘”在一起,从而可以方便地实现 IPC 。

进程通信

那么为什么会有进程通信呢?这是因为在 Linux 中进程之间是隔离的,也就是说 A 进程不知道有 B 进程的存在,相应的 B 进程也不知道 A 进程的存在。A 、B 两进程的内存是不共享的,所以 A 进程的数据想要传给 B 进程就需要用到 IPC 。

阅读更多

Android 中的内存泄漏

Part 1

在长久以来的 Android 开发过程中,内存泄漏一直是一个比较头疼的问题。内存泄漏会导致应用卡顿,用户体验不佳,甚至会造成应用崩溃的严重后果。所以如何科学地进行内存管理一直是大家探讨的话题,从一开始主动使用 MAT 分析 hprof 文件,到后来 LeakCanary “被动”的接收内存泄漏消息。应用中发现内存泄漏的手段越来越多了,操作也越来越便捷,但内存泄漏的问题还是不能轻易忽视的,提高应用的体验和质量也是迫在眉睫。

那今天,就从最基本的开始聊聊内存泄漏。

Part 2

内存泄漏简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却不再被使用导致 GC 不能回收。我们所说的内存泄露是针对于堆内存而言,堆内存中存放的就是引用指向的对象实体。

阅读更多

Android Activity 生命周期调用流程

注:源码分析基于 Android SDK API 28 

我们知道,Activity A 启动 Activity B ,其生命周期方法调用如下:

  1. Activity A onPause()
  2. Activity B onCreate()
  3. Activity B onStart()
  4. Activity B onResume()
  5. Activity A onStop()

那首先我们来看看 Activity A 的 onPause() 是什么地方调用的?

onPause()

startActivity 的流程中有一步是 resumeTopActivityInnerLocked 。

阅读更多

Android startActivity 启动流程

注:源码分析基于 Android SDK API 28

对于 Activity 大家都已经很熟悉很亲切了吧,在这就不过多介绍了。

直接进入正题,走起!

一般我们启动 Activity 的入口都是 startActivity ,所以这也成为了我们分析整个流程的切入口。

Activity

startActivity(Intent intent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}

@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
}
阅读更多

Android Tinker源码分析(七):dex合成流程

tryRecoverDexFiles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
String patchVersionDirectory, File patchFile) {
// 检查是否开启支持dex补丁开关
if (!manager.isEnabledForDex()) {
TinkerLog.w(TAG, "patch recover, dex is not enabled");
return true;
}
// 检查补丁包中的 dex_meta.txt 是否存在
String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE);

if (dexMeta == null) {
TinkerLog.w(TAG, "patch recover, dex is not contained");
return true;
}

long begin = SystemClock.elapsedRealtime();
// 到这个方法中执行具体的操作
boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile);
long cost = SystemClock.elapsedRealtime() - begin;
TinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost);
return result;
}
阅读更多

Android Tinker源码分析(六):补丁合成流程

本系列 Tinker 源码解析基于 Tinker v1.9.12

补丁合成流程

下发的补丁包其实并不能直接加载,因为补丁包只是差异包,需要和本地的 dex 、资源等进行合成后,得到全量的 dex 才能被完整地使用。这样也就避免了热修复中 dex 的 pre-verify 问题,也减少了补丁包的体积,方便用户下载。

补丁合成的入口在 TinkerInstaller.onReceiveUpgradePatch 方法

TinkerInstaller.onReceiveUpgradePatch

1
2
3
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}
阅读更多

Android Apk 安全之校验签名

校验签名

一般绝大多数的 app 在上线前都会做一层安全防护,比如代码混淆、加固等。

今天就来讲讲其中的一项:校验签名。

校验签名可以有效的防止二次打包,避免你的 app 被植入广告甚至破解等。而今天就从两个角度来讲签名的具体校验:

  • Java 层
  • C/C++ 层

那么就先开始讲 java 层好了。

Java 层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static boolean validateAppSignature(Context context, String apkSignature) {
try {
PackageManager packageManager = context.getApplicationContext().getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
String lowerCaseSignature = signature.toCharsString().toLowerCase();
if (lowerCaseSignature.equals(apkSignature)) {
return true;
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return false;
}
阅读更多

Android Tinker 源码分析(五):加载so补丁流程

本系列 Tinker 源码解析基于 Tinker v1.9.12

校验so补丁流程

与加载资源补丁类似,加载so补丁也要先从校验开始看起。

其实总体来说,Tinker 中加载 so 补丁文件的关键代码就一句:

System.load(String filePath)

tryLoadPatchFilesInternal

1
2
3
4
5
6
7
8
9
10
11
final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);

if (isEnabledForNativeLib) {
//tinker/patch.info/patch-641e634c/lib
boolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);
if (!libCheck) {
//file not found, do not load patch
Log.w(TAG, "tryLoadPatchFiles:native lib check fail");
return;
}
}
阅读更多

Android Tinker源码分析(四):加载资源补丁流程

本系列 Tinker 源码解析基于 Tinker v1.9.12

加载资源补丁流程

将到资源补丁的加载,首先还要回过头来先看资源补丁的校验和检查。

我们回到 TinkerLoader.tryLoadPatchFilesInternal 方法中来看。

tryLoadPatchFilesInternal

1
2
3
4
5
6
7
8
9
10
11
//check resource
final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag);
Log.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource);
if (isEnabledForResource) {
boolean resourceCheck = TinkerResourceLoader.checkComplete(app, patchVersionDirectory, securityCheck, resultIntent);
if (!resourceCheck) {
//file not found, do not load patch
Log.w(TAG, "tryLoadPatchFiles:resource check fail");
return;
}
}
阅读更多