一口气读完《Android进阶解密》

608 阅读1小时+

说明:本文是对刘望舒的《Android进阶解密》的摘录和总结。

第1章 Android系统架构

1.1 Android系统架构

Android 系统架构分为5层,从上到下一次是应用层、应用框架层、系统运行库层、硬件抽象层和Linux内核层。

  1. 应用层(System Apps) 系统内置应用程序和非系统的应用,通常是java和kotlin开发
  2. 应用框架层(Java API Framework) 为开发应用程序提供API,是Java代码写的。 应用框架层提供的组件有:
名称功能描述
ActivityManager管理各个应用生命周期,常用的导航回退
LocationManager提供地理位置及定位功能服务
PackageManager管理所有安装在Android系统中的应用程序
NotificationManager状态栏显示应用的自定义提示信息
ResourceManager提供字符串、图片、布局、颜色等资源
TelehonyManager管理所有的移动设备功能
WindowManager管理所有开启的窗口程序
ContentProvider使得不同的程序间可共享数据
ViewSystem构建应用程序的基本组件
  1. 系统运行库层(Native) 分为C/C++程序库和Android运行时库。
  • C/C++程序库包括:
名称功能描述
OpenGL ES3D绘图数库
Libc从BSD继承来的标准C系统函数库,专门为基于嵌入式Linux的设备定制
Media Framework多媒体库
SQLite轻型的关系型数据库引擎
SGL底层的2D图形渲染引擎
SSL安全套接字
FreeType可移植的字体引擎
  • Android运行时库又分为核心库和ART
    • Dalvik虚拟机(即DVM)中的应用每次运行时,字节码都需要通过即时编译器(Just In Time,JIT)转换为机器码,使得应用的运行效率降低;
    • ART中,系统在安装应用时预编译(Ahead Of Time,AOT),将字节码预先编译成机器码并存储本地,这样运行时不需要执行编译,提高了运行效率
  1. 硬件抽象层(HAL) 位于操作系统内核与硬件电路之间的接口层,目的是硬件抽象化,保护硬件厂商的知识产权,隐藏了硬件平台的接口细节。

  2. Linux内核层(Linux Kernel) 基于Linux内核,增加了Android专用驱动。系统安全、内存管理、进程管理、网络协议栈和驱动模型均依赖此内核。

1.2 系统源码目录

1.3 源码阅读

1.3.1 在线阅读

推荐Android官方源码阅读网站 cs.android.com ,或老的 androidxref.com

1.3.2 使用Source Insight

Window平台推荐Source Insight,Mac可用Sublime或Android Studio

1.4 本章小结

本章主要介绍了Android系统架构、源码目录和如何阅读源码。

第2章 Android系统启动

2.1 init进程过程

init进程是Android系统中用户空间的第一个进程,进程号是1。

2.1.1 引入init进程

  1. 启动电源以及系统启动 当电源按下时,引导芯片代码从预定的地方(固化在ROM)开始执行,加载引导程序BootLoader到RAM,然后执行。
  2. 引导程序BootLoader 引导程序BootLoader作用是把系统OS拉起来并运行。
  3. Linux内核启动 当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。完成设置后在系统文件中找init.rc文件,启动init进程。
  4. init进程启动 初始化和启动属性服务,启动Zygote进程。

2.1.2 init进程的入口函数

main函数,先创建和挂载启动所需的运行时所需的文件目录(tmpfs、devpts、proc、sysfs、selinuxfs),property_init函数初始化属性服务,start_property_service启动服务,sign_handler_init函数设置子进程的信号处理,防止init进程的子进程成为僵尸进程。 假设子进程Zygote终止了,signal_handle_init会调用handle_signal函数最终找到Zygote进程并移除相关信息,再重启Zygote服务的启动脚本(如init.zygote64.rc)中带有onrestart选项到服务。

2.1.3 解析init.rc

init.rc是由Android初始化语言(Android Init Languange)编写的脚本。 Android8.0对init.rc文件进行了拆分,Zygote启动脚本在init.ZygoteXX.rc中定义,64位处理器为例:

// system/core/rootdir/init.zygote64.rc 

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server 
class main 
priority - 20 
user root 
group root readproc 
socket zygote stream 660 root system 
onrestart write /sys/android_power/request_state wake 
onrestart write /sys/power/state on 
onrestart restart audioserver 
onrestart restart cameraserver 
onrestart restart media 
onrestart restart netd 
onrestart restart wificond 
writepid /dev/cpuset/foreground/tasks

Service通知init进程创建名为zygote的进程,并传递相应的路径和参数。

2.1.4 解析Service类型语句

解析过程是根据参数创建出Service对象,根据选项域的内容填充Service对象,最后加入到vector类型的Service链表中。

2.1.5 init启动Zygote

首先判断Service是否已经运行,若是直接返回false,若否,就调用fork函数创建子进程,并返回pid值。若该Service是Zygote,就会进入app_main.cpp的main函数,然后调用runtime的start函数启动Zygote。

2.1.6 属性服务

init进程启动时会启动属性服务并为其分配内存,用来存储这些属性,若需要用,直接读取就可以。 属性服务分为两种类型:普通属性和控制属性。

2.1.7 init进程启动总结

init进程启动做了以下三件事:

  1. 创建和挂载启动所需的文件目录;
  2. 初始化和启动属性服务;
  3. 解析init.rc配置文件并启动Zygote进程。

2.2 Zygote进程启动过程

2.2.1 Zygote概述

Android系统中,DVM(Dalvik虚拟机)和ART、应用程序进程以及运行系统的关键服务的SystemServer进程都是Zygote进程来创建的,称为孵化器。它通过fock(复制进程)的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动时会创建DVM或者ART,因此通过fock创建的进程可以在内部获取对应的实例副本。

2.2.2 Zygote启动脚本

64位的脚本中会启动两个Zygote进程,一个名为zygote,执行程序为app_process32,作为主模式,另一个名为zygote_secondary,执行程序为app_process64,作为辅模式。

2.2.3 Zygote进程启动过程介绍

ZygoteInit的main方法主要做了4件事:

  • 创建一个Server端的Socket;
  • 预加载类和资源;
  • 启动SystemServer进程;
  • 等待AMS请求创建新的应用程序进程。

2.2.4 Zygote进程启动总结

  1. 创建AppRuntime并调用其start方法,启动Zygote进程;
  2. 创建Java虚拟机并为Java虚拟机注册JNI方法;
  3. 通过JNI调用ZygoteInit的main函数进入Zygote的Java框架层;
  4. 通过registerZygoteSocket方法创建服务端Socket,并通过runSelectLoop方法等待AMS的请求来创建新的应用程序进程;
  5. 启动SystemServer进程。

2.3 SystemServer处理过程

2.3.1 Zygote处理SystemServer进程

2.3.2 解析SystemServer进程

2.3.3 SystemServer进程总结

systemServer进程被创建后,做了如下工作:

  1. 启动Binder线程池,这样就可以与其他进程进行通信;
  2. 创建SystemServiceManager,其用于对系统服务进行创建、启动和生命周期管理;
  3. 启动各种系统服务。

2.4 Launcher启动过程

2.4.1 Launcher概述

Launcher就是Android系统的桌面,作用有:

  • 作为Android系统的启动器,启动应用程序;
  • 作为Android系统的桌面,用于显示和管理应用程序的快捷图标或者其他桌面组件。

2.4.2 Launcher启动过程介绍

2.4.3 Launcher应用图标显示过程

Launcher是用工作区的形式来显示系统安装的应用程序的快捷图标的,每个工作区都是用来描述一个抽象桌面的,它由n个屏幕组成,每个屏幕又分为n个单元格,每个单元格用来显示一个应用程序的快捷图标。用loadWorkspace和bindWorkspace方法来加载和绑定工作区信息,用loadAllApps方法来加载系统已经安装的应用程序信息,loadAllApps内调用bindAllApplications给AllAppsContainerView类型的对象传入包含应用信息的列表apps,最后用RecyclerView展示。

2.5 Android系统启动流程

  1. 启动电源以及系统启动 当电源按下时引导芯片代码从预定义的地方(固化在ROM)开始执行。加载引导程序BootLoader到RAM,然后执行。
  2. 引导程序BootLoader 引导程序BootLoader是在Android操作系统开始运行前的一个小程序,作用是把系统OS拉起来运行。
  3. Linux内核启动 当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当内核完成系统设置时,它首先在系统文件中寻找init.rc文件并启动init进程。
  4. init进程启动 初始化和启动属性服务,并启动Zygote进程。
  5. Zygote进程启动 创建Java虚拟机并为Java虚拟机注册JNI方法,创建服务端Socket,启动SystemServer进程。
  6. SystemServer进程启动 启动Binder线程池和SystemServiceManager,并且启动各种系统服务。
  7. Launcher启动 被SystemServer进程启动的AMS会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到界面上。

以下是Android系统启动流程图

2.6 本章小结

为了理解Android系统启动流程,本章学习了init进程启动过程、Zygote进程启动过程、SystemServer进程处理过程和Launcher启动过程。

第3章 应用程序进程启动过程

3.1 应用程序进程简介

要想要启动一个应用程序,首要保障所需要的应用程序进程已经启动。AMS在启动应用程序时会检查这个应用程序所需要的进程是否存在,不存在就会请求Zygote进程启动需要的应用程序进程。在Zygote的Java框架层中会创建一个Server端的Socket,这个Socket用来等待AMS请求Zygote来创建新的应用程序进程。Zygote进程通过fock自身创建应用程序进程,这样获得Zygote在启动时创建的虚拟机实例,同时创建了Binder线程池和消息循环,这样运行在应用进程的应用程序就可以使用Binder进行进程间通信及处理消息了。

3.2 应用程序启动过程介绍

分两部分讲解,分别是AMS发送启动应用程序请求以及Zygote接受请求兵创建应用程序进程。

3.2.1 AMS发送启动应用程序进程请求

3.2.2 Zygote接收请求并创建应用程序进程

3.3 Binder线程池启动过程

应用程序进程创建过程中会启动Binder线程池。 ZygoteInit类的zygoteInit方法调用nativeZygoteInit,这JNI方法,最终调用的是AndroidRuntime.cpp的com_android_internal_os_ZygoteInit_nativeZygoteInit函数,内部调用gCurRuntime->onZygoteInit(),gCurRuntime是AndroidRuntime类型的指针,onZygoteInit函数内调用ProcessState的startThreadPool函数来启动Binder线程池,然后调用IPCThreadState的joinThreadPool函数将当前线程注册到Binder驱动程序中,这样我们创建的线程就加入了Binder线程池中,新创建的应用程序进程就支持Binder进程间通信了,我们只需要创建当前进程的Binder对象,并将它注册到ServiceManager中就可以实现Binder进程间通信,而不用关心进程间是如何通过Binder进行通信的。

3.4 消息循环创建过程

Zygote接收请求并创建应用程序进程,启动后会创建消息循环。 RuntimeInit的invokeStaticMain方法最后一行会抛出MethodAndArgsCaller异常,这个异常被ZygoteInit的main方法捕获,捕获后执行caller的run方法,内部调用mMethod.invoke(null, new Object[] {mArgs}),mMethod指的是ActivityThread的main方法,mArgs指的是应用程序进程的启动参数,

// frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) { 
// 创建主线程Looper
Looper.prepareMainLooper(); //l 
ActivityThread thread= new ActivityThread(); //2 
thread.attach(false) ; 
if (sMainThreadHandler == null) { //3 
// 创建主线程
sMainThreadHandler = thread.getHandler (); //4 
if (false) { 
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, ActivityThread);
Trace.traceEnd(Trace.TRACE TAG ACTIVITY MANAGER); 
// Looper开始工作
Looper.loop (); //5 
throw new RuntimeException(Main thread loop unexpectedly exited");

ActivityThread类用于管理当前应用程序进程的主进程,在注释1出创建主线程的消息循环Looper,2处创建ActivityThread。3处判断Handler类型的是否为null,是则获取H类病复制给sMainThreadHandler,这个H类继承自Handler,是ActivityThread的内部类,用于处理主线程的消息循环。5处调用Looper的loop方法,使得Looper开始处理消息。可以看出,系统在应用程序进程启动完成后,就会创建一个消息循环,这样运行在应用程序进程中多应用程序就可以方便的使用消息处理机制。

3.5 本章小结

本章学习了应用的进程如何创建,Binder线程池和消息循环如何创建,他们是进程和线程间通信的重要手段。

第4章 四大组件的工作过程

4.1 根Activity的启动过程

根Activity的启动过程比较复杂,分三部分讲,分别是Launcher请求AMS过程、AMS到ApplicationThread的调用过程和ActivityThread启动Activity。

4.1.1 Launcher请求AMS的过程

Launcher启动后,桌面展示了应用程序快捷图标就是启动根Activity的入口,点击时,通过Launcher请求AMS来启动该应用程序。

4.1.2 AMS到ApplicationThread的调用过程

Launcher请求AMS后,代码逻辑进入AMS中,接着是AMS到ApplicationThread的调用流程。

最后走到ActivityStackSupervisor的realStartActivityLocked函数内的app.thread.scheduleLaunchActivity,这里的app.thread指的是IApplicationThread,它的实现是ActivityThread的内部类ApplicationThread,其中ApplicationThread继承了IApplicationThread.Stub。app指的是传入的要启动的Activity所在的应用程序进程,因此这段代码就是要在目标应用程序进程启动Activity。当前代码逻辑运行在AMS所在的进程(SystemServer进程)中,通过ApplicationThread来与应用程序进程进行Binder通信,换句话说,ApplicationThread是AMS所在进程(SystemServer进程)和应用程序进程的通信桥梁,如下图所示。

4.1.3 ActivityThread启动Activity的过程

由上节可知目前的代码逻辑运行在应用程序进程中。

4.1.4 根Activity启动过程中涉及的进程

根Activity启动过程中会涉及到4个进程,分别是Zygote进程、Launcher进程、AMS所在进程(SystemServer进程)、应用程序进程,它们的关系如图。

首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动后,AMS会请求创建应用程序并启动根Activity。上图步骤2采用的是Socket通信,步骤1和4是Binder通信。下面是4个进程的调用时序图。

4.2 Service的启动过程

Service的启动过程分两部分讲解,分别是ContextImpl到ActivityManagerService的调用过程和ActivityThread启动Service。

4.2.1 ContextImpl到AMS的调用过程

4.2.2 ActivityThread启动Service

4.3 Service的绑定过程

Service绑定过程分为两个部分讲解:ContextImpl到AMS的调用过程和Service的绑定过程。

4.3.1 ContextImpl到AMS的调用过程

4.3.2 Service的绑定过程

前半部分

后半部分

4.4 广播的注册、发送和接收过程

4.4.1 广播的注册过程

4.4.2 广播的发送和接收过程

广播的发送和接收过程分为两个部分来进行讲解,分别是Contextlmpl到AMS的调用过程和AMS到BroadcastReceiver的调用过程。

4.4.2.1 ContextImpl到AMS的调用过程

广播可以发送多种类型,包括无序广播(普通广播)、有序广播和粘性广播,这里以无序为例。

4.4.2.2 AMS到BroadcastReceiver的调用过程

4.5 Content Provider的启动过程

Content Provider的启动过程分为两个部分讲解,分别是query方法到AMS的调用过程和AMS启动Content Provider的过程。

4.5.1 query方法到AMS的调用过程

4.5.2 AMS启动Content Provider的过程

4.6 本章小结

本章介绍了Android8.0四大组件的启动过程,与Android7.0主要区别是,与AMS进行进程间通信时采用的AIDL技术,而不是此前的ActivityManagerProxy和ApplicationThreadProxy等代理类。

第5章 理解上下文Context

5.1 Context的关联类

Context意为上下文,是一个应用程序环境信息的接口。 Context是一个抽象类,内部定义了很多方法及静态常量,他的具体实现类是ContextImpl。

从上图看出,ContextImpl和ContextWrapper继承自Context,ContextWrapper内部包含Context类型的mBase对象,具体指向ContextImpl。外界需要使用并扩展ContextImpl的功能,因此使用装饰器模式,ContextWrapper作为装饰类对ContextImpl进行包装,ContextWrapper主要起了方法传递作用,几乎所有方法都调用ContextImpl方法实现。ContextThemeWrapper、Service和、application都继承自ContextWrapper,都可以通过mBase使用Context的方法,同时他们是装饰类,在ContextWrapper的基础上添加了不同功能。 Context的关联类采用了装饰模式,主要优点有:

  • 使用者(如Service)能方便的使用Context。
  • 如果ContextImpl发生变化,装饰类ContextWrapper不需做任何改动。
  • 通过组合而非继承的方式,扩展ContextImpl的功能,在运行时选择不同的装饰类,实现不同的功能。

5.2 Application的Context的创建过程

5.3 Application的Context的获取过程

5.4 Activity的Context的创建过程

5.5 Service的Context的创建过程

5.6 本章小结

本章讲解了Context的关联类、Application、Activity和Service的Context创建过程。

第6章 理解ActivityServiceManager

6.1 AMS家族

6.1.1 Android7.0的AMS家族

AMP是AMN的内部类,他们都实现了IActivityManager接口,这样它们实现了代理模式,具体来讲是远程代理:AMS和AMN是运行在两个进程中的,AMP是Client端,AMN是Server端,而Server端中的具体功能都是AMN的子类AMS来实现的,因此AMP就是AMS在Client端的代理类。AMN又实现了Binder类,这样AMP和AMS就可以通过Binder来进行进程间通信。ActivityManager通过AMN得getDefault方法得到AMP,通过AMP就可以和AMS进行通信。除ActivityManager外,有些想要与AMS通信的类也需要通过AMP。例如第五章介绍的ContextImpl想要与AMS通信也是通过AMP。

6.1.2 Android8.0的AMS家族

Android 8.0的AMS家族要简单得多,ActivityManager得getService方法会得到IActivityManager, AMS只需要继承IActivityManager.Sub类,就可以和ActivityManager实现进程间通信了。

6.2 AMS的启动过程

AMS的启动是在SystemServer进程中启动的,SystemServer进程的启动过程在2.3节讲过,这里从SystemServer的main方法讲起。

//frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) { 
    new SystemServer().run();
}

main方法只调用了SystemServer的run方法,如下:

//frameworks/base/services/java/com/android/server/SystemServer.java
private void run() {
    try {
        ...
        // 创建消息Looper
        Looper.prepareMainLooper();
        //加载了动态库libandroid_servers.so
        System.loadLibrary("android_servers");//1
        performPendingShutdown();
        // 创建系统的Context
        createSystemContext();
        // 创建SystemServiceManager
        mSystemServiceManager = new SystemServiceManager(mSystemContext);//2
        mSystemServerManager.setRuntimeRestarted(mRuntimeRestart);
        LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
        SystemServerInitThreadPool.get();
    } finnally {
        traceEnd();
    }
    try {
        traceBeginAndSlog("StartServices");
        //启动引导服务
        startBootstrapServices();//3
        //启动核心服务
        startCoreServices();//4
        //启动其他服务
        startOtherServices();//5
        SystemServerInitThreadPool.shutdown();
    } catch (Throwable ex) {
        Slog.e("System", "**********************************")
        Slog.e("System", "******** Failure starting system services", ex);
        throw ex;
    } finally {
        traceEnd();
    }
}

SystemServer的run方法已经在2.3.2讲过,这里再复习一遍。在注释1处加载了动态库libandroid_servers.so。2处创建SystemServiceManager,它会对系统的服务进行创建、启动和生命周期管理。3处的startBootstrapService方法中用SystemServiceManager启动了ActivityManagerService、PowerManagerService、PackageManagerService等服务。4处的startCoreService启动了DropBoxManagerServcie、BatterService、UsageStatsService和WebViewUpdateService。在5处的startOtherServices方法启动了CameraService、AlarmManagerService、VrManagerService等服务。这些服务的父类均为SystemService。官方把系统服务分为了3种类型,分别是引导服务、核心服务和其他服务,其他服务是一些非紧要和不需要立即启动的服务。我们主要看引导服务里的AMS是如何启动的,3处的startBootstrapService方法如下

//frameworks/base/services/java/corri/android/server/SystemServer.java
private vo startBootstrapServices () { 
    traceBeginAndSlop("StartActivityManager");
    mActivityManagerService = mSystemServiceManager.startService(
    ActivityManagerService.Lifecycle.class).getService();//1 
    mActivityManagerService.setSystemServiceManager(mSystemServiceManager);;
    mActivityManagerService.setInstaller(installer);
    traceEnd() ;
    ...
}

在注释1处调用了SystemServiceManager的startService方法,方法的参数是ActivityManagerService.Lifecycle.class:

//frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
public void startService(@NonNull final SystemService service) { 
    mServices.add(service);//1
    long time = System.currentTimeMillis(); 
    try { 
        service.onStart();/2 
    } catch (RuntimeException ex) { 
        throw new RuntimeException("Failed to start service ” + service . getClass () . 
        getName () + ”: onStart threw an exception", ex)
    }
    warnIfLong(System.currentTimeMillis() - time, service, "onStart");
}

传入的SystemService类型的service对象的值为ActivityManagerService.Lifecycle.class。 在注释1处将service对象添加到ArrayList类型的mService种来完成注册。在2处调用service的onStart方法来启动service对象,这个service具体指啥,接着往下看,Lifecycle是AMS的内部类,如下所示:

//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public static final class Lifecycle extends SystemService { 
    private final ActivityManagerService mService ; 
    public Lifecycle(Context context) { 
        super(context) ; 
        mService = new ActivityManagerService(context);//1 
    }
    @Override 
    public void onStart() { 
        mService .start ();//2 
    }
    public ActivityManagerService getService () {//3 
        return mService ;
    }
}    

上面代码接口SystemServiceManager的startService方法来分析。注释1处,在LifeCycle的构造方法中创建了AMS实例。当调用SystemService类型的service的onStart方法时,实际上是调用了注释2处AMS的start方法。注释3的Lifecycle的getService方法返回AMS实例,所以SystemServer的startBootstrapService的mSystemServiceManager.startService(ActivityManagerService.Lifecycle.class)。getService()实际得到的也是AMS实例。

6.3 AMS与应用程序进程

在2.2.3节中,讲到Zygote的Java框架层中,会创建一个Service端的socket,这个Socket用来等待AMS请求Zygote来创建新的应用程序进程。要启动一个应用程序进程,首先要保证这个应用程序所需的进程已经存在。 AMS与应用程序进程的关系主要有两点:

  • 启动应用程序时,AMS会检查这个应用程序所需要的应用程序进程是否存在。
  • 如果需要的应用程序进程不存在,AMS会请求Zygote进程创建需要的应用程序进程。

6.4 AMS重要的数据结构

AMS设计了很多数据结构,如ActivityRecord、TaskRecord和ActivityStack,是Activity任务栈模型的基础。

6.4.1 解析ActivityRecord

ActivityRecord是在启动Activity时被创建,具体是ActivityStarter的startActivity方法。它内部存储了Activity的所有信息,包括AMS的引用、AndroidManifests节点信息、Activity状态、资源信息和Activity进程相关信息。需要注意的是含有该ActivityRecord所在的TaskRecord,将ActivityRecord和TaskRecord关联在一起。

6.4.2 解析TaskRecord

TaskRecord的作用:其内部存储了任务栈的所有信息,包括任务栈的的唯一标识符、任务栈的倾向性、任务栈中的Activity记录和AMS的引用等,需要注意的是其中含有ActivityStack,也就是当前Activity任务栈所归属的ActivityStack,我们接着来查看ActivityStack。

6.4.3 解析ActivityStack

ActivityStack个管理类,用来管理系统所有Activity,其内部维护了Activity的所有状态、特殊状态的Activity以及和Activity相关的列表等数据。

6.5 Activity栈管理

6.5.1 Activity栈模型

ActivityRecord用来记录Activity的所有信息,TaskRecord中包含了一个或多个Activity Record, TaskRecord用来表示Activity的任务栈,用来管理栈中的ActivityRecord, ActivityStack 又包含了一个或多个TaskRecord,它是 TaskRecord的管理者。 Activity栈管理就是建立在Activity任务栈模型之上的,有了栈管理,我们可以对应用程序进行操作,应用可以复用自身应用中以及其他应用的 Activity,节省了资源。

6.5.2 Launch Mode

Launch Mode是用于设定Activity的启动方式,共有4种:

  • standard:默认模式,每次启动Activity都会创建一个新的Activity实例。
  • singleTop:如果要启动的Activity已经在栈顶,则不会重新创建Activity,同时该Activity的onNewIntent方法会被调用。如果要启动的Activity不在栈顶,则重新创建该Activity的实例。
  • singleTask:如果要启动的Activity已经存在于它想要归属的栈中,那么不会创建该Activity实例,将栈中位于该Activity上所有的Activity出栈,同时该Activity的onNewIntent方法会被调用。如果要启动的Activity不存在于它想要归属的栈中,并且该栈存在,则会重新创建该Activity的实例。如果要启动的Activity想要归属的栈不存在,则首先要创建一个新站,然后创建该Activity实例并压入新栈中。
  • singleInstance:和singleTask基本类似,不同的是启动Activity时,首先要创建一个新栈,然后创建该Activity实例并压入新栈中,新栈中智慧存在这一个Activity实例。

6.5.3 Intent的FLAG

Intent的FLAG有:FLAG_ACTIVITY_SINGLE_TOP、FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP等,启动Activity时ActivityStarter.java中,根据传入参数及要加入的栈的情况计算出最终的启动FLAG,并进行栈的操作。

6.5.4 taskAffinity

我们可以在AndroidManifest.xml设置android:taskAffinity,用来指定Activity希望归属的栈。

6.6 本章小结

本章介绍了AMS的家族、AMS的启动、AMS重要的数据结构和Activity栈管理等知识。

第7章 理解WindowManager

7.1 Window、WindowManager和WMS

Window是一个抽象类,具体实现是PhoneWindow,它对View进行管理。WindowManager是一个接口类,继承自接口ViewManager,它是用来管理Window的,实现类是WindowManagerImpl。如果想要对Window(View)进行添加、更新、删除操作,就可以使用WindowManager,它会将具体的工作交由WMS来处理,WindowManager和WMS通过Binder来进行跨进程通信,WMS作为系统服务又很多API事不会暴露给WindowManager的,这一点与ActivityManager和AMS的关系类似。 Window、WindowManager和WMS的关系如下图表示:

7.2 WindowManager的关联类

PhoneWindow继承自Window,Window通过setWindowManager方法与WindowManager发生关联。WindowManager继承自接口ViewManager,WindowManagerImpl是WindowManager接口的实现类。但具体的功能都会委托给WindowManagerGlobal来实现。

7.3 Window的属性

Window的属性有很多种,与应用开发最密切的三种分别是Type(Window的类型)、Flag(Window的标志)和SoftInputMode(软键盘相关模式),下面将分别介绍。

7.3.1 Window的类型和显示次序

总的来说Window分为3大类型,分别是Application Window(应用程序窗口)、Sub Window(子窗口)、System Window(系统窗口),每个大类又分很多类型,都定义在WindowManager的静态内部类LayoutParams。

  1. 应用程序窗口 Type值范围为1~99,Activity就是典型的应用程序窗口。
  2. 子窗口 Type值范围为:1000~1999,不能独立存在,需要附着在其他窗口才可以,如PopupWindow。
  3. 系统窗口 Type值范围为2000~2999,例如Toast、输入法窗口、系统音量条窗口、系统错误窗口。
  4. 窗口显示次序 Type值越大,则Z-Order排序越靠前,越靠近用户。如果Type相同,则WMS结合个情况给出最总Z-Order。

7.3.2 Window的标志

设置Window的Flag有3种方法:

//方法1
getWindow().addFlag(WindowMaanger.LayoutParams.FLAG_FULLSCREEN);

//方法2
Window mWindow = getWindow()
mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREN)


//方法3
给LaoutParams设置Flag,并同构WindowManager的addView方法进行添加

7.3.3 软键盘相关模式

7.4 Windw的操作

WindowManager对Window进行管理,说到管理那就离不开对Window的添加、更新和删除操作,统称为Window的操作,它们最终交由WMS进行处理,分为两部分,一是WindowManager处理部分,二是WMS处理部分。

本节只讲Window操作的Windowmanager处理部分,第8章讲解WMS处理的部分。

7.4.1 系统窗口的添加过程

此处以系统窗口Statusbar的添加为例,首先添加时调用了WindowManager的addView方法,此方法定义在WindowManager的父类接口ViewMangager中,实现是在WindowManagerImpl实现,接着调用WindowManagerGlobal的addView方法,最终将窗口和参数通过setView设置到ViewRootImpl中,可见添加窗口这一操作是通过ViewRootImpl进行的。ViewRootImpl身负了很多职责,主要有:

  • View树的根并管理View树;
  • 触发View的测量、布局和绘制;
  • 输入时间的中转站;
  • 管理Surface;
  • 负责与WMS进行进程间通信。 ViewRottImpl的setView调用了mWindowSession的addToDisplay,mWindowSession是IWindowSession类型,是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端实现是Session,此前的代码逻辑都运行在本地进程,而Session的addToDisplay方法运行在WMS所在的进程(SystemServer进程),如下图。

上图所示,本地进程ViewrootImpl要通过Session才能与WMS进行通信。 在Session的addToDisplay调用了WMS的addWindow方法。WMS会为这个窗口分配Surface并确定次序,进而绘制到屏幕上。

7.4.2 Actiivty的添加过程

Activity作为最典型的应用程序窗口,以它为例子讲解。Activity在启动过程中若所在进程不存在则创建新的应用程序进程,之后会运行代表主线程的ActivityThread,它管理着当前应用的进程的线程。当界面与用户交互式,调用ActivityThread的handleResmeActivity,最终调用Activity的的onResume,此方法里,得到ViewManager类型的wm对象,addView方法在WindowManagerImpl实现。

7.4.3 Window的更新过程

Window的更新过程与Window的添加过程类似,需调用ViewManager的updateViewLayout,此方法在WindowManagerImpl实现,接着调用WindowManagerImpl的updateViewLayout,最后是WindowManagerGlobal的updateViewLayout。 最终调用WMS的relayoutWindow方法,除此之外,还调用了performManager、performLayout、performDraw,他们内部会调用view的measure、layout和draw。

7.5 本章小结

本章学习了Windowmanager的关联只是,如WindowManager的表。

第8章理解WindowManagerServer

8.1 WMS的职责

WMS的职责用简洁的语言介绍主要有以下几点。

  1. 窗口管理 WMS是窗口的管理者,它负责窗口的启动、添加和删除,另外窗口的大小和层级页是有WMS进行管理的。窗口管理的核心成员有DisplayContent、WindowToken和WindowState.

  2. 窗口动画 窗口动画又WMS的动画子系统负责,动画子系统的管理者为WindowAnimator。

  3. 输入系统的中转站 InputManagerService(IMS)会对触摸事件进行处理,它需要寻找一个最合适的窗口处理触摸反馈,WMS作为输入系统的中转站最合适不过。

  4. Surface管理 窗口不具备绘制的功能,每个都需要有一块Surface来供自己绘制,为每个窗口分配Surface是由WMS来完成。

WMS的职责简单总结如下图所示。

8.2 WMS的创建过程

WMS是在SystemServer进程中创建的,SystemServer进程的启动在2.3和6.2节讲过,请回看详情,在SystemServer的main-->run方法中调用的startOtherService是启动其他服务,WMS就是其中一种。 其中涉及到3个线程,分别是system_server、android.display和android.ui,下面是关系图。

由上图看出,三个线程之间的关系分为三个步骤实现:

  1. 首先在system_server线程执行SystemServer的startOtherServices方法,在startOtherServices方法中调用WMS的main方法,main方法会 创建WMS,创建的过程在android.display线程中实现,创建WMS的优先级更高,因此system_server线程要等WMS创建完成后,处于瞪大及状态的system_server线程才会被唤醒从而 继续执行下面的代码。
  2. 在WMS的构造方法中会调用WMS的initPolicy方法,在initPolicy方法中又会调用PWM的init方法,PWM的init方法在android.ui线程中运行,它的优先级要高于android.display线程,因此“andorid.display”线程要等PWM的init方法执行完毕后,处于等待的android.display线程才会被唤醒继续执行。
  3. PWM的init方法执行完毕后,android.display线程就完成了WMS的创建,比如WMS的displayReady方法用来初始化屏幕显示信息。

8.3 WMS的重要成员

  1. mPolicy: WindowManagerPolicy WindowManagerPolicy是窗口管理策略的接口类,用来定义一个窗口策略所需要遵循的通用规范,提供的WindowManager所有的特定UI行为,它的具体实现类为PhoneWindowManager,这个实现类是在WMS创建是被创建。WMP允许定制窗口层级和特殊窗口类型及关键的调度和布局。
  2. mSessions: ArraySet mSessions是ArraySet类型的变量,元素类型为Session,它主要用于进程间通信,其他的应用程序进程想要与WMS通信都要经过Session,每个应用程序进程都对应一个Session,WMS保存这些Session来记录所有向WMS提供窗口管理服务的客户端。
  3. mWindowMap: WindowHashMap
  4. mFinishedStarting: ArrayList 元素是WindowToken的子类。 WindowToken主要有俩作用:
  • 窗口令牌,当应用程序想要想WMS申请新创建一个窗口,则需要想WMS出示有效的WindowToken。AppWindowToken作为windowToken的子类,主要用来描述应用程序的WindowToken结构,应用程序中每个Activity都对应一个AppWindowToken。
  • WindowToken会将同一个组件(比如同一个Activity)的窗口(WindowState)集合在一起,方便管理。
  1. mResizingWindows: ArrayList 用来存储正在调整大小的窗口的列表。类似的还有mDestorySurface等,窗口保存这些事因为窗口需要一直保持旧surface直到新的Surface的第一帧绘制完成。
  2. mAnimator: WindowAnimator 用于管理窗口的动画和特效动画。
  3. mH: H mH是H类型的变量,系统的Handler类,用于将任务加入到主线程的消息队列中,这样代码的逻辑就会在主线程执行。
  4. mInputManager: InputManagerServer 输入系统的管理者。InputManagerService(IMS)会对触摸事件进行处理,通过WMS这个窗口的管理者来寻找一个最合适的窗口来处理触摸反馈信息。

8.4 Window的添加过程(WMS处理部分)

addWindow方法分为3个部分进行讲解,主要是做了下面4件事:

  • 对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会再执行下面的代码逻辑。
  • WindowToken相关的处理,比如有的窗口类型需要提供WindowToken,没有提供的话就不会执行下面的代码,有的窗口类型则需要WMS隐式创建WindowToken。
  • WindowState的创建和 相关处理,将WindowToken和WindowState相关联。
  • 创建和配置DisplayContent,完成窗口添加到系统前的准备工作。

8.5 Window的删除过程

Window的删除总结为:

  • 检查删除线程的正确性,不正确就抛出异常。
  • 从ViewRootImpl列表、布局参数列表和View列表中删除与Window(View)对应的元素。
  • 判断是否可以直接执行删除操作,如果不能就推迟操作。
  • 执行删除操作,清理和释放与Window(View)相关的一切资源。

第9章 JNI原理

JNI是Java Native Interface的缩写,译为Java本地接口,是Java与其他语言通信的桥梁。一般有以下情况需要用到JNI技术。

  • 需要调用Java语言不支持的依赖操作系统平台特性的功能。
  • 为了整合一些以前的非Java语言开发的系统。
  • 为了节省程序的运行时间,必须采用其他语言(比如C/C++语言)来提升运行效率。例如游戏、音视频开发设计的音视频编解码和图形绘制需要更快的处理速度。 JNI不之应用与Android开发。在Android中主要用于音视频开发、热修复、插件化、逆向开发和系统源码调用等。Android提供了NDK工具集合方便实用JNI。

9.1 系统源码中的JNI

Android系统按语言来划分的话由两个世界组成:Java世界和Native世界。JNI就是它们之间的桥梁,如下图所示。

9.2 MediaRecorder框架中的JNI

9.2.1 Java Framework层的MediaRecorder

image

9.2.1 JavaFramework层的MediaRecord 9.2.2 JNI层的MediaRecorder 9.2.3 Native方法注册 Native方法注册分为静态和动态注册。

9.3 数据类型的转换

9.4 方法签名

Java是有重载方法的,可以定义方法名相同,但参数不同的方法,正因为如此,在JNI中仅仅通过方法名是无法找到Java中对应的具体方法的,JNI为了解决这一问题就将参数类型和 返回值类型组合在一起作为方法签名。通过方法签名和方法名就可以找到对应的Java方法。

9.5 解析JNIEnv

JNIEnv是Native世界中Java环境的代表,通过JNIEnv *指针就可以砸Native世界文Java

9.6 引用类型

JNI也有引用类型,它们分别是本地引用(Local References )、全局引用(Global References)和弱全局引用(Weak Global References)

9.6.1 本地引用

JNIEnv提供的函数所返回的引用基本上都是本地引用,因此本地引用也是JNI最常见的引用类型,本地引用的特点主要有以下几点:

  • 当Native函数返回时,这个本地引用就会被自动释放。
  • 只在创建它的线程中有效,本能够跨线程使用。
  • 局部引用是JVM负责的引用类型,受JVM管理。

9.6.2 全局引用

全局引用的特点:

  • 在Native函数返回时,不会被自动释放,需要手动释放而且不会被GC回收。
  • 全局引用是可以跨线程使用的。
  • 全局引用不受到JVM管理。

9.6.3 弱全局引用

它和全局引用的特点相似,但它可以被GC回收,回收后会指向NULL。因此使用之前要先判断是否被回收。

9.7 本章小结

本章解析了JNI的原理,包括MediaRecorder框架中的JNI的引用、数据类型的转换、方法签名JNI_TRUE的引用是否相等。

第10章 Java虚拟机

10.1 概述

JDK (Java Development Kit)包含了Java语言、Java虚拟机和JavaAPI库这三部分,是Java程序开发的最小环境。而且 (Java Runtime Environment )包含了JavaAPI中的Java SE API子集和 Java 虚拟机这两部分,是Java 程序运行的标准环境。Java 虚拟机是整个Java平台的基石,是Java语言编译代码的运行平台。

10.1.1 Java虚拟机家族

  1. HotSpot VM
  2. J9 VM
  3. Zing VM

10.1.2 Java虚拟机执行流程

当我们执行一个Java程序时,执行流程如图所示。

上图可以发现Java虚拟机的执行流程分为两大部分,分别是编译时环境和运行时换进,当一个Java文件经过Java编译器编译后会生成Class文件,这个文件会有Java寻你急来进行处理。Java虚拟机与Java语言没有必然的联系,它只与特定的二进制文件Class文件有关。因此无论任何语言能编译成Class文件,就可以被Java虚拟机识别并执行。

语言与Java虚拟机

10.2 Java虚拟机结构

这里讲的体系结构,指的是Java虚拟机的抽象行为,不是具体的比如HotSpot VM的实现。

Java虚拟机结构

图中可以看出Java虚拟机结构包括运行时数据区域、执行引擎、本地库接口和本地方法库,其中类加载子系统不属于Java虚拟机的内部结构。方法区和Java堆事所有线程共享的数据区域。

10.2.1 Class文件格式

ClassFile { 
    u4 magic//魔数,固定值为OxCAFEBABE,用来判断当前文件是不是能被Java虚拟机处理的Class文件
    u2 minor_version; //副版本号
    u2 major_version; //主版本号
    u2 constant_pool_count ; //常量池计数器
    cp info_constant_pool[constant_pool_count-1]; //常量池
    u2 access_flags ; //类和接口层次的访问标志
    u2 this_class; //类索引
    u2 super_class; //父类索引
    u2 interfaces_count; //接口计数器
    u2 interfaces[interfaces_count]; //接口表
    u2 fields_count; //字段计数器
    field info_fields[fields count]; //字段表
    u2 methods_count; //方法计数器
    method_nfo_methods[methods_count]; //方法表
    u2 attributes_count; //属性计数器
    attribute_info_attributes[attributes_count]; //属性表
    ...
}

其中的u4、u2表示“基本数据类型”,class文件的基本数据类型如下所示。

  • u1:1字节,无符号类型。
  • u2:2字节,无符号类型。
  • u4:4字节,无符号类型。
  • u8:8字节,无符号类型。

10.2.2 类的生命周期

类的生命周期分别是:加载、链接(验证、准备和解析)、初始化、使用和卸载。 所做的工作:

  1. 加载: 查找并加载Class文件。
  2. 链接:包括验证、准备和解析。
  • 验证:确保被导入类型的正确性。
  • 准备:为类的静态字段分配字段,并用默认值初始化这些字段
  • 解析:虚拟机将常量池内的符号引用替换为直接引用。
  1. 初始化:将类变量初始化为正确初始值。

类的生命周期

根据《深入理解Java虚拟机》的描述,加载阶段(不是嘞的加载)主要做了3件事情:

  • 根据特定名称查找类或借口类型的二进制字节流。
  • 将这个二进制字节流所代表的静态 存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。 第一件事就是又Java虚拟机外部的类加载子系统完成的。

10.2.3 类加载子系统

类加载子系统通过多种类加载器来查找和加载Class文件到Java虚拟机中,Java虚拟机有两种类加载器:系统加载器和自定义加载器。其中系统加载器包括三种:

  1. Bootstart ClassLoader(引导类加载器) 用C/C++代码实现的加载器 ,用于加载指定的jdk的核心类库,比如java.lang、java.util等这些系统类。
  2. Extensions ClassLoader(扩展类加载器) 用于加载Java的扩展类。
  3. Application ClassLoader(应用程序类加载器) 又称为System ClassLoader(系统类加载器),这个类加载器可以通过ClassLoader的getSystemClassLoader方法获取到。 自定义加载器是通过继承java.lang.ClassLoader类的方式来实现自己的类加载器。

10.2.4 运行时数据区域

Java虚拟机在执行Java程序的过程中会把它管理的内存划分为不同的数据区域,分别为程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区。

  1. 程序计数器 Java虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在一个确定的时刻只有一个处理器执行一条线程中的指令,为了在线程切换后能恢复到正确的执行位置,每个线程都有一个独立的程序计数器,因此程序计数器是线程私有的。
  2. Java虚拟机栈 每一条Java虚拟机线程都有一个现成私有的Java虚拟机栈(Java Virtual Machine Stacks)。它的生命周期与线程相同,与线程同时创建。Java虚拟机栈存储线程中的Java方法调用的状态,包括局部变量、参数、返回值以及运算的中间结果等。一个Java虚拟机栈包含了多个栈帧,一个栈帧用来存储局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java虚拟机栈中,完成后,这个栈帧就从虚拟机栈中弹出。平常所说的占内存(Stack)指的就是Java虚拟机栈。有两种异常情况。
  • 如果现成请求分配的站容量超过Java虚拟机所允许的最大容量,会抛出StackOverflowError。
  • 如果虚拟机栈可以动态扩展,但是扩展时无法申请到足够的内存,或创建新的线程时没有足够的内存去创还能对应的Java虚拟机栈,则抛出OutOfMemoryError异常。
  1. 本地方法栈 用来支持Native语言。
  2. Java堆 Java堆(Java Heap)是被所有线程共享的运行时内存区域。Java堆用来存放对象实例,几乎所有的对象实例都是在这里分配及内存。
  3. 方法区 方住区(Method Area)是被所有线程共享的运行时内存区域,用来存储已经被Java虚拟机加载的类的结构信息,包括运行时常量池、字段和方法信息、静态变量等数据。
  4. 运行时常量池 运行时常量池可以理解为是类或接口的常量池的运行时表现形式。

10.3 对象的创建

当虚拟机收到一个new对象指令时,它会做如下操作。

  1. 判断对象对应的类是否加载、链接和初始化
  2. 为对象分配内存 类家长完成后,会在Java对中划分一块内存分配给对象。分配是根据Java堆是否规整,有两种方式。
  • 指针碰撞:如果Java堆内存是规整的,即所有用过的内存在一边,空闲的内存在另一边,则分配内存是将位于中间的指针指示器向空闲的内存移动一段与内存大小相等的距离。
  • 空闲列表:如果不是规整的,则需要虚拟机维护一个列表来记录哪些内存是可用的,这样分配的时候从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。
  1. 处理并发安全问题 解决并发问题有两种方式:
  • 对分配内存空间的动作进行同步处理,比如在虚拟机采用CAS算法并配上失败重试的方式保证更新操作的原子性。
  • 每个线程在Java堆中预先分配一小块内存,称为本地县城分配缓冲(Thread Local Allocation Buffer, TLAB),线程需要分配内存是,就在对应线程的TLAB上分配内存,当TLAB用完兵被分配到新的TLAB时,这时候才需要同步锁定。
  1. 初始化分配到的内存空间 将分配到的内存,除了对象头外都初始化为零值。
  2. 设置对象的对象头 将对象所属的类、对象的HashCode和对象的GC分代年龄等数据存储在对象的对象头中。
  3. 执行init方法进行初始化 执行init方法,初始化对象的成员变量、调用类的构造方法。

10.4 对象的堆内存布局

以Hotspot虚拟机为例,对象在堆内存的布局分为三个区域,分别是对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。

  • 对象头:对象头 包括两部分信息,Mark World和元数据指针。
  • 实例数据:用于存储对象中的歌类型的字段信息。
  • 对其填充:不一定存在,占位符的作用。

对象的堆内存布局

10.5 oop-klass模型

oop-klass模型是用来描述Java对象实例的一种模型,分为两个部分,OOP(Ordinary Object Pointer)指的是普通对象指针,用来表示对象的实例信息。

确定对象具体类型

从上图可看出,Java虚拟机通过栈帧中的对象引用找到Java堆中的instanceOopDesc这样就可以访问到Java对象的实例信息,当需要访问对象的具体类型等信息,可以通过instanceOopDesc中的元数据指针来找到方法区中对应的instanceKlass。

10.6 垃圾标记算法

目前有两种垃圾标记算法,分别是引用计数算法和根搜索算法。

10.6.1 Java的引用

Java将引用分为强引用、软引用、弱引用和虚引用。

  1. 强引用 新建一个对象就是创建了一个具有强引用的对象,垃圾收集器不会回收强引用,Java虚拟机宁愿抛出OutOfMemoryError异常终止程序。

  2. 软引用 若一个对象只有软引用,内存不足时,会回收这些对象的内存,回收后若还是没有足够内存,就会抛出OutOfMemoryError异常,Java用SoftReference类来实现软引用。

  3. 弱引用 垃圾收集器一旦发现只有弱引用的对象,不管当前内存是否足够,都会回收,Java用WeakReference类来实现弱引用。

  4. 虚引用 虚引用于没有任何引用一样,随时可能被垃圾回收期回收,回收时会收到一个系统通知,这是虚引用的主要作用,Java用PhantomReference类来实现虚引用。

10.6.2 引用计数算法

引用计数算法的基本你思想是每个对象都有一个引用计数器,当对象在某处被引用时,引用计数器就加1,引用失效时就减1.值为0时,该对象就不能被使用变成了垃圾。目前主流的Java虚拟机没有选择引用计数算法来为垃圾标记,原因是引用计数算法没有解决对象之间互相循环引用的问题。

10.6.3 根搜索算法

这个算法的基本思想就是选定一些对象作为GC Roots,并组成根对象集合,然后以这GC Roots的对象作为起始点,向下搜索,如果目标对象到GC Roots是连接着的,我们称该目标对象是可达的,如果目标对象不可达则说明目标对象是可以被回收的对象。

可以作为GC Roots的对象主要有几种:

  • Java栈中引用的对象。
  • 本地方法栈中JNI引用的对象。
  • 方法区中运行时常量池引用的对象。
  • 方法区中静态属性引用的对象。
  • 运行中的线程。
  • 由引导类加载器加载的对象。
  • GC控制的对象。

10.7 Java对象在虚拟机中的生命周期

在Java对象被类加载器加载到虚拟机后,Java对象在Java虚拟机中有7个阶段。

  1. 创建阶段(Created) 创建阶段的具体步骤为:
  • 为对象分配存储空间。
  • 构造对象。
  • 从超类到子类对static成员进行初始化。
  • 递归调用超类的构造方法。
  • 调用子类的构造方法。
  1. 应用阶段(In Used)
  2. 不可见阶段(Invisible) 程序的执行已经超出了该对象的作用域。
  3. 不可达阶段(Unreachable) 在程序侯总找不到对象的任何强引用,垃圾收集器发现对象不可达。
  4. 收集阶段(Collected) 垃圾收集器已经准备好对该对象的内存空间重新分配,若该对象重写了finalize方法,则会调用该方法。
  5. 终结阶段(Finalized) 对象执行完finalize方法后仍然处于不可达状态或对象没有重写finalize方法,则对象进入终结阶段,等待垃圾收集器回收该对象空间。
  6. 对象空间重新分配阶段(Deallocated) 对象的空间被被回收或再分配时,对象彻底消失。

10.8 垃圾收集算法

10.8.1 标记-清除算法

标记清除(Mark-Sweep)算法分为两个阶段:标记阶段和清除阶段。缺点是标记和清除的效率不高。,缠身大量不连续的内存碎片,可能会导致后续没有足够的连续内存分配各及较大的对象。从而提前出发垃圾收集动作。

10.8.2 复制算法

它把内存空间划为相等的区域,每次只使用其中一个区域,垃圾收集时,遍历当前区域,把存货的对象复制到另一个区域,最后将当前使用的区域的可回收对象进行回收。 此算法每次对整个半区进行内存回收,不存在内存碎片问题,代价是使用内存为原来的一半。如果存活对象很少,复制算法效率很高,被广泛应用于新生代中。

10.8.3 标记-压缩算法

标记可回收的对象后将有存活的对象压缩到内存的一端,使他们紧凑地排列在一起,然后对边界外的内存进行回收,回收后,已用和未用的内存都各自一边。解决了标记-清除算法效率低和容易产生大量内存碎片的问题,它被广泛应用于老年代中。

10.8.4 分代收集算法

现在主流的Java虚拟机的垃圾回收器都采用分代收集算法(Generational Collection)。Java堆区基于分代的概念,分为新生代(Yong Generation)和老生代(Tenured Generation),新生代又细分为Eden空间、From Survivor空间和To Survivor空间。HotSpot虚拟机默认Eden空间和两个Survivor空间所占的比例为8:1。 根据Java堆区的空间划分,垃圾收集的类型分为两种,如下。

  • Minor Collection: 新生代垃圾收集。
  • Full Collection:对老年代进行收集,又称Major Collection,Full Collection通常会伴随至少一次Minor Collection,它的收集频率较低耗时较长。 当执行一次Minor Collection时,Eden空间的存活对象会被复制到To Survivor空间,并且之前经过一次Minor Collection并在From Survivor空间存货的仍年轻的对象也会复制到To Survivor空间。有两种Eden空间和From Survivor空间存活的对象不会复制到To Survivor空间,而是晋升到老年代。一种是存活的对象的分代年龄超过-XX:MaxTenuringThreshold(用于控制对象经历多少次Minor GC才晋升到老年代)所指定的阈值。另一种是To Survivor空间达到阈值。当所有存活对象被复制到To Survivor空间或晋升到老年代,就意味着Eden空间和From Survivor空间剩下的都是可回收对象。

复制算法在新生代中的应用

这个时候GC执行Minor Collection,Eden空间和From Survivor空间都会被清空,新生代中存活的对象都存放在To Survivor空间。接下来From Survivor空间和To Survivor空间互换位置,每次Survivor的空间互换都要保证To Survivor空间是空的,这就是复制算法在新生代中的应用。在老年代则会采用标记-压缩算法或者标记-清除算法。

10.9

本章介绍了Java虚拟机的一部分包括Java虚拟机的就结构、oop-klass模型、垃圾表计算法和垃圾收集算法。

第11章 Dalvik和ART

11.1 Dalvik虚拟机

Dalvik虚拟机(Dalvik Vitual Machine),简称Dalvik VM或者DVM。DVM是Google专门为Android平台开发的虚拟机,运行在Android运行时库中,DVM并不是一个Java虚拟机。

11.1.1 DVM与JVM的区别

  1. 基于的架构不同 JVM基于栈,DVM基于寄存器。
  2. 执行的字节码不同 JVM通过相应的.class文件和Jar文件获取相应的字节码;DVM是从.dex文件读取指令和数据。

如图.jar 文件里面包含多个.class文件,每个.class 文件里面包含了该类的常量池、类信息、属性等。当JVM加载该.jar文件的时候,会加载里面的所有的.class文件,JVM的这种加载方式很慢,对于内存有限的移动设备并不合适。而在.apk文件中只包含一个.dex 文件,这个.dex 文件将所有的.class 里面所包含的信息全部整合在起了,这样再加载就加快了速度.class文件存在很多的冗余信息,dex会去除冗余信息,并把所有.class 文件整合到.dex 文件中,减少了I/O操作,加快了类的查找速度。 3. DVM允许在有限的内存中同时运行多个进程 在Android中的每一个应用都运行在DVM实例中,每个DVM实例都运行在一个独立的进程空间中,独立的进程可以防止在虚拟机崩愤的时候所有程序都被关闭。 4. DVM由Zygote创建和初始化 所有的DVM实例都会和Zygote共享一块内存区域,节省了内存开销。 5. DVM有共享机制 DVM拥有预加载一共享的机制,不同应用之间在运行时可以共享相同的类,拥有更高的效率。 6. DVM早期没有使用JIT编译器

11.1.2 DVM架构

首先Java编译器编译的.class文件经过DX工具转换为.dex文件,.dex文件由类加载器处理,接着解释器根据指令集对Dalvik字节码进行解释、执行,最后交于Linux处理。

DVM架构

11.1.3 DVM运行时堆

DVM的运行时堆使用标记-清除(Mark Sweep)算法进行GC,它由两个Space以及多个辅助数据结构组成,两个Space分别是Zygote Space(Zygote Heap)和Allocation Space(Active Heap)。 在Zygote进程fork第一个进程之前,会把Zygote Space分为两个部分,原来的已被使用的那部分堆仍旧叫Zygote Space,而未使用的那部分堆就叫Allocation Space,以后的对象都会在Allocation Space上进行分配和释放。

11.1.4 DVM的GC日志

D/dalvikvm: <GC Reason> <Amount freed> , <Heap stats>, <External memory stats>, <Pause time>

11.2 ART虚拟机

ART (Android Runtime)虚拟机是Android 4.4发布的,Android5.0上默认使用ART。

11.2.1 ART与DVM的区别

区别主要有4点:

  1. DVM中的应用每次运行时,字节码都需要通过JIT编译器编译为机器码,这会使得应用程序的运行效率降低。而ART中,系统在安装应用程序时会进行一次AOT (ahead of time compilation,预编译),将字节码预先编译成机器码并存储在本地,这样应用程序每次运行时就不需要执行编译了,运行效率会大大提升。 AOT的缺点有两点,一是安装时间变长,二是需要占用更大空间。Android7.0开始,加入JIT,首次不会编译所有,而是在运行时将热点代码编译成机器码,从而缩短安装时间和减小空间。
  2. DVM支持32位CPU,ART支持64位兼容32位。
  3. ART对垃圾回收算法进行了改进。
  4. ART的运行堆空间与DVM不同。

11.2.2 ART的运行时堆

ART采用了多种垃圾收集方案,每个方案会运行不同的垃圾收集器,默认是采用了CMS(Concurrent Mark-Sweep)方案,该方案主要使用了sticky CMS和partial-CMS。根据不同的CMS方案,ART的运行时堆的空间也会有不同的划分,默认是由Space和多个辅助数据结构组成的,Space分别是Zygote Space、Allocation Space、Image Space、Large Object Space。如下图,深色为共享区域。

采用标记-清除算法的运行时堆空间划分

11.2.3 ART的GC日志

I/art : <GC Reason> <GC Name> <Objects freed>(<Size freed>) AllocSpace Objects, <Large_objects_freed>(<Large_object_s ze freed>) <Heap_stats> LOS objects, <Pause time(s)>

实例分析:

I/art : Expl cit concurrent mark sweep GC freed 104710(7MB) AllocSpace ob] ects, 21 (41 6KB ) LOS objects, 33% free , 25MB/38MB, paused 1.230ms total 67.216ms

这个GC日志的含义为引起GC原因是Explicit;垃圾收集器为CMS收集器;释放对象的数量为104710个,释放字节数为7MB;释放大对象的数21个,释放大对象数为416KB;堆的空闲内存百分比为33% ,已用内存为25MB,堆的总内存为38MB;GC暂停时长为1.230ms, GC总时长为67.216ms。

11.3 DVM和ART的诞生

DVM和ART是在Zygote进程中诞生的,这样Zygote进程就持有了DVM或者ART的实例,此后Zygote fork自身创建应用程序进程时,应用程序进程也得到了DVM或者ART的实例,这样就不需要每次启动应用程序进程都要创建DVM或者ART,从而加快了应用程序进程的启动速度。

11.4 本章小结

本章介绍了DVM和ART的基本原理、如何阅读它们的log和DVM以及ART的诞生。

第12章 理解ClassLoader

12.1 Java中的ClassLoader

在10.2.3节中提到的类加载子系统,主要作用就是通过多种类加载器(ClassLoader)来查找和加载Class文件到Java虚拟机中。

12.1.1 ClassLoader的类型

Java中的类加载器主要有两种类型,即系统类加载器和自定义加载器。系统类加载器包括3中,分别是Bootstrap ClassLoader、Extensions ClassLoader和Application ClassLoader。 在10.2.3节中已有简要说明,这里有部分重复:

  1. Bootstart ClassLoader(引导类加载器) 用C/C++代码实现的加载器 ,用于加载指定的jdk的核心类库,比如java.lang、java.util等这些系统类。 Java虚拟机的启动就是通过Bootstrap ClassLoader创建一个初始类来完成的。由于Bootstrap ClassLoader是使用 C/C++语言实现,所以该加载器不能被java代码访问到。它并不继承java.lang.ClassLoader。
  2. Extensions ClassLoader(扩展类加载器) 用于加载Java的扩展类。
  3. Application ClassLoader(应用程序类加载器) Java中的实现类为AppClassLoader,又称为System ClassLoader(系统类加载器),这个类加载器可以通过ClassLoader的getSystemClassLoader方法获取到。 自定义加载器是通过继承java.lang.ClassLoader类的方式来实现自己的类加载器。
  4. Custom ClassLoader(自定义类加载器) 自定义类加载器通过继承java.lang.ClassLoader类的方式来实现自己的类加载器。

12.1.2 ClassLoader的继承关系

共有5个ClassLoader相关类。

  • ClassLoader是一个抽象类,定义了ClassLoader的主要功能。
  • SecureClassLoader继承了抽象类,扩展加入了权限方面的功能。
  • URLClassLoader继承自SecureClassLoader,通过URL路径从jar文件和文件夹中加载类和资源。
  • ExtClassLoader和AppClassLoader都继承自URLClassLoader,都是Launcher的内部类,并都在Launcher初始化,Launcher是Java虚拟机的入口应用。

12.1.3 双亲委托模式

类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找,如下图。

总的来说就是Class文件加载到类加载子系统后,先沿着图中虚线的方向自下而上进行委托,再沿着实线的方向自上而下进行查找和加载,整个过程就是先上后下。ClassLoader的父子关系并不是使用继承实现,而是使用组合实现代码复用。 采用双亲委托模式主要有如下两点好处。

  • 避免重复加载,如果加载过一次的Class,就不需要再次加载,而是直接读取已经加载的Class。
  • 更加安全,如果不适用双亲委托模式,就可以自定义一个String类来替代系统的String类,这样造成安全隐患,使用双亲委托模式会使得系统的String在Java虚拟机启动时就被加载,就无法子顶一个Sring类来替代系统的。除非我们修改类加载器搜索类的默认算法。

12.1.4 自定义ClassLoader

自定义ClassLoader需要两个步骤:

  1. 定义一个自定义ClassLoader并继承抽象类 ClassLoader。
  2. 复写findClass方法,并在findClass方法中调用defineClass方法。

12.2 Android中的ClassLoader

12.2.1 ClassLoader的类型

Android中的ClassLoader分为两种类型:系统类加载器和自定义加载器。系统类加载器分3种,分别是BootClassLoader、PathClassLoader和DexClassLoader。

  1. BootClassLoader Android系统启动时使用BootClassLoader预加载常用类,它由java实现。我们应用中无法直接调用。

  2. DexClassLoader 用于加载Dex文件或包含Dex的文件(apk或jar)。

  3. PathClassLoader 加载系统类和应用程序的类。通常用来加载已经安装的apk的dex文件。

12.2.2 ClassLoader的继承关系

可以看到图中共有8个ClassLoader相关类,其中有一些和Java中的ClassLoader相关类十分类似,下面简单对它们进行介绍:

  • ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。 BootClassLoader是它的内部类。
  • SecureClassLoader类继承自ClassLoader,不是实现类,只是扩展了ClassLoader类,加入了权限方面的功能,加强ClassLoader的安全性。
  • URLClassLoader继承自SecureClassLoader,用来通过URL路径从jar文件和文件夹中加载类和资源。
  • InMemoryDexClassLoader是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。
  • BaseDexClassLoader继承自ClassLoader,是ClassLoader的具体实现类,PathClassLoder、DexClassLoader和InmemoryDexClassLoader都继承自它。

12.2.3 ClassLoader的加载过程

Android的ClassLoader同样遵循了双亲委托模式。如果委托流程没有检查到此前加载过传入的类,就调用ClassLoader的findClass方法,Java层最终会调用DexFile的defineClassNative方法来执行查找流程。

12.2.4 BootClassLoader的创建

BootClassLoader是在Zygote进程的Zygote入口方法中被创建的,用于加载preloaded-classes文件中存在的预加载类。

12.2.5 PathClassLoader的创建

PathClassLoader是在SystemServer进程中采用工厂模式创建的。

12.3 本章小结

本章分别对Java和Android的ClassLoader进行解析,通过他们的类型和继承关系看出几点差异:

  • Java的引导类加载器是由C++便携的,Android的引导类加载器则是Java编写的。
  • Android的继承关系比Java复杂,功能也多。
  • Android加载的不再是Class文件,因此没有ExtClassLoader和AppClassLoader,替代它们的是PathClassLoader和DexClassLoader。

第13章 热修复原理

13.1 热修复的产生

13.2 热修复框架的种类和对比

13.3 资源修复

13.3.1 Instant Run概述

传统的编译部署需要重新安装App和重启App,这显然很好使,Instant Run就避免这情况。

图中看出,Instant Run的构建和部署都是基于更改的部分的,部署有三种方式。

  • Hot Swap:效率最高的步数方式,修改一个现有的方法 中的代码时,会采用Hot Swap。
  • Warm Swap:修改或删除一个现有的资源文件时,采用Warm Swap。
  • Cold Swap:App需要重启,但不需要重新安装。采用Cold Swap的情况很多,比如添加、删除或修改一个字段或方法,添加类等。

13.3.2 Instant Run的资源修复

Instant Run中的资源修复分为两个步骤:

  1. 创建新的AssetManager,通过反射调用addAssetPath方法加载外部的资源,这样新创建的AssetManager就含有了外部资源。
  2. 将AssetManager类型的mAssets字段的引用全部替换为新创建的AssetManager。

13.4 代码修复

代码修复主要有3个方案,分别是底层替换方案,类加载方案和Instant Run方案。

13.4.1 类加载方案

类加载方案基于Dex分包方案。Dex分包方案为了解决65536限制和LinearAlloc限制。主要做的是打包时将应用代码分成多个Dex,将应用启动时必须用到的类和这些类的直接引用类放到主Dex中,其他代码放到次Dex中。当应用启动时先加载主Dex,应用启动后再动态加载次Dex,缓解了主Dex的65536限制和LinearAlloc限制。 在12.2.3节中学习了ClassLoader的加载过程,一个缓解就是调用DexPathList的findClass的方法,如下所示。

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public Class<?> findClass(String name, List<Throwable> suppressed) { 
    for (Element element : dexElements) {//1 
    Class<?> clazz = element.findClass(name, definingContext, suppressed);//2 
    if (clazz != null) { 
        return clazz;
    }
    if (dexElementsSuppressedExceptions != null) { 
        suppressed.addAll(Arrays asList(dexElementsSuppressedExceptions));
    }
}

Element内部封装了DexFile,DexFile用于加载dex文件,因此每个dex文件一应一个element。多个Element组成了有序的Element数组dexElements。当要查找类时,会在注释1处遍历dexElement,相当于遍历dex文件数组,2处调用Element的findClass方法,内部调用DexFile的loadClassBinaryName方法查找类。如果在Element中(dex文件)找到了该类就返回,如果没有找到就接着下一个Element查找。根据上面的查找流程我们将有bug的类Key.class进行修改,再打包成包含dex的补丁包Patch.jar,放在Element数组的dexElemeents的第一个元素,这样会首先找到Patch.dex中的Key.class去替换之前存在Bug的Key.class,排在数组后面dex文件存在Bug的Key.class根据ClassLoader的双亲委托模式就不会被夹在,这就是累夹在方案,如图所示。

类加载方案需要重启App后让ClassLoader重新加载新的类,因为类时无法被卸载的,想要重新加载新的类就需要重启App。

13.4.2 底层替换方案

底层替换方案不会再次加载新类,而是直接在Native层修改原有类,由于在原有类进行修改的限制比较多,不能增加原有类的方法和字段,如果增加了方法数,那方法索引数也回增加,访问时就会无法通过索引找到正确的方法。Sophoix是采用的替换整个结构体,不存在兼容问题,并且可以立即生效不需要重启。

13.4.33 Instant Run方案

Instant Run在第一次构建APK时,使用ASM在每个方法中注入了类似如下的代码

IncrementalChange localIncrementalChange = $change; //l 
if (localIncrementalChange != null) (//2
    loca1Incrementa1Change.access$dispatch("nCreate.(Landroid/os/Bundle;)V”, new Object[] { this,paramBundle }); 
    return;
}

注释1处是一个成员变量localIncrementalChange,它的值是changechange,实现了IncrementalChange这个抽象接口。当我们点击InstantRun时,如果方法没有变化,则changenull,就调用return不做处理。如果方法有变化,则生成替换类。这个类实现了IncrementalChange接口,同事也会生成一个AppPatchesLoaderImpl类,这个类的getPatchedClasses方法会返回被修改的类的列表(包含了MainActivity),根据列表会将MainActivitychange为null,就调用return不做处理。如果方法有变化,则生成替换类。这个类实现了IncrementalChange接口,同事也会生成一个AppPatchesLoaderImpl类,这个类的getPatchedClasses方法会返回被修改的类的列表(包含了MainActivity),根据列表会将MainActivity的change设置为MainActivityoverride,因此满足了注释2的条件,会执行MainActivityoverride,因此满足了注释2的条件,会执行MainActivityoverride的accessdispatch方法,再根据参数“onCreate.(Landroid/os/Bundle;)V”执行MainActivitydispatch方法,再根据参数“onCreate.(Landroid/os/Bundle;)V”执行MainActivityoverride的onCreate方法,从而实现了onCreate方法的修改。借鉴了InstantRun的原理的热修复框架有Robust和Aceso。

什么是ASM? ASM是一个java字节码操控框架,它能动态生成类或增强现有类的功能。ASM能直接产生class文件,也可以在类的加载到虚拟机之前动态改变类的行为。

13.5 动态链接库的修复

Android平台的动态链接库主要指的是so库,本章动态链接库简称为so。热修复框架的so的修复主要是更新so,也就是重新加载so,因此修复的基础是加载so。

13.5.1 System的load和loadLibrary方法

加载so主要用到了System类的load方法和loadLibrary方法,如下所示:

// libcore/ojluni/src/main/java/java/lang/System.java
public final class System { 
    ...
    @CallerSensitive
    public static void load(String filename) {
        Runtime.getRuntime().loadO(VMStack.getStackClassl(), filename); //1 
    }
    @CallerSensitive 
    public static void loadLibrary(String libname) { 
        Runtime.getRuntime().loadLibraryO(VMStack.getCallingClassLoader(), libname);//2
    }
    ...
}

System的load方法传入的参数是so在磁盘的完整路径,用于加载指定路径的so。System的loadLibrary方法传入的参数是so的名称,用于加载app安装后自动从apk包中复制到/data/data/packagename/lib下的so。目前的so修复都是基于这两个方法。 两个方法最终调用的都是nativeLoad方法,接着来分析nativeLoad方法。

13.5.2 nativeLoad方法分析

nativeLoad方法内部调用了JavaVmExt的LoadNativeLibrary函数来加载so。LoadNativeLibrary函数主要做了以下3方面工作。

  1. 判断so是否被夹在过,两次ClassLoader是否同一个,避免so重复加载。
  2. 打开so并得到so句柄,如果获取so句柄失败就返回false。创建新的SharedLibrary,如果传入的path对应的library为空指针,就将新创建的SharedLibrary赋值给library,并将library存储到libraries_中。
  3. 查找JNI_OnLoad的函数指针,更具不同情况设置was_successfall的值,最终返回该was_successful。 LoadNativeLibrary函数的流程图如下。

这里总结一下so修复主要有两个方案:

  1. 将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先被返回和加载。
  2. 调用System的load方法来接管so的加载入口。

13.6 本章小结

本章主要介绍了热修复的原理。

第14章 Hook技术

逆向工程源于商业及军事领域的硬件分析,目的是在不能轻易获得必要的生产信息的情况下,直接从成品分析,推导出产品的设计原理。逆向分析分为静态分析和动态分析,其中静态分析指的是一种在不执行程序的情况下对程序行为进行分析的技术;动态分析是指在程序运行时对程序进行调试的技术。Hook技术属于动态分析。

14.1 Hook技术概述

从上图看出,Hook可以将自己融入到它要劫持的对象所在的进程中,称为系统集成的一部分,这样可以通过Hook来更改B的行为,被劫持的对象(如对象B)称作Hook点。为了保证Hook的稳定性,Hook点一般选择容易找到并且不易变化的对象,静态变量和单例就符合这一条件。

14.2 Hook技术分类

按Api语言划分,分为Hook Java和Hook Native。

  • Hook Java主要通过反射和代理实现,应用与SDK开发环境中修改Java代码。
  • Hook Native则应用于NDK开发环境和系统开发中修改Native代码。 按Hook的进程划分,分为应用程序进程Hook和全局Hook。
  • 应用程序进程Hook只能Hook当前应用程序进程
  • 应用程序进程时Zygote进程fock出来的,如果对Zygote进行Hook,则可以实现Hook系统所有的应用程序进程,这就是全局Hook。 根据实现方式划分,分为两种:
  • 通过反射和代理实现,只能Hook当前的应用程序进程。
  • 通过Hook框架来实现,比如Xposed,可以实现全局Hook,但需要root。 本章主要学习的是Hook Java。

14.3 代理模式

定义是为其他对象提供一种代理以控制对这个对象的访问称为代理模式。 代理模式中有如下角色:

  • Subject:抽象主题类,声明真实主题与代理的共同接口方法。
  • RealSubject:真实主题类,定义了代理所表示的集体对象,客户端通过代理类简介调用真实主题类的方法。
  • Proxt:代理类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。
  • Client:客户端类。

14.3.1 代理模式简单实现

假设我要买哈尔滨的红肠,但是工作忙也没时间回哈尔滨,就委托在哈尔滨的朋友帮我购买。

  1. 抽象主题类 抽象主题类具有主题类和代理的共同接口方法,我想要代购,那共同的方法也就是购买:
public interface IShop {
    void buy();
}
  1. 真实主题类 这个购买者Liu就是我,实现了IShop接口提供的buy()方法,如下所示:
public class Liu implements IShop {
    @override
    public void buy() {
        System.out.println("购买");
    }
}
  1. 代理类 我找的朋友就是代理类,同样也需要实现IShop接口,并且要持有被代理者,在buy方法中调用被代理者的buy方法:
public class Purchasing implement IShop {
    private IShop mShop;
    pulic Purchasing(IShop shop) {
        mShop = shop;
    }
    @override
    public void buy() {
        mShop.buy();
    }
}
  1. 客户端类
public class Client {
    public static void main(String[] args) {
        //创建Liu
        IShop liu = new Liu();
        IShop purchasing = new Purchasing(liu);
        purchasing.buy();
    }
}

客户端类的代码就是代理类包含了真实主题类(被代理者),最终调用的都是真实主题类(被代理者)实现的方法,所以上面的例子就是Liu类的buy方法,所以运行的结果就是“购买”。

14.3.2 动态代理的简单实现

从编码的角度来说,代理模式分为静态代理和动态代理,14.2.1节的例子是静态代理,在代码运行前就已经存在了代理类的class编译文件,而动态代理则是在代码运行时通过反射来动态的生成代理类的对象,并确定到底代理谁。Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke方法。 下面我们再上面静态代理的例子上做修改,首先创建动态代理类:

public  class DynamicPurchasing implements InvocationHandler {
    private Object obj;
    public DynamicPurchasing(Object obj) {
        this.obj = obj;
    }
    @override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(obj, args);
        if(method.getName().equals("buy")) {
            System.out.println("Liu在买买买");
        }
        return result;
    }
}

在动态代理类中我们声明了一个Object的引用,该引用指向被代理类,我们调用被代理类的具体方法在invoke方法执行。

public class Client {
    public static void main(Striung[] args) {
        //创建Liu
        IShop liu = new Liu();
        //创建动态代理
        DynamicPurchasing mDynamicPurchasing = new DynamicPurchasing(liu);
        //创建Liu的ClassLoader
        ClassLoader loader = liu.getclass().getClassLoader();
        //动态创建代理类
        IShop purchasing = (IShop)Proxy.newProxyInstance(loader, new Class[]{ IShop.class }, mDynamicPurchasing);
        purchasing.buy();
    }
}

调用Proxy.newProxyInstance()来生成动态代理类,调用purchasing的buy方法会调用DynamicPurchasing的invoke方法。

14.4 Hook startActivity方法

这里以Hook常用的startActivity方法来举例,startActivity方法有两个startActivity(intent)getApplicationContext().startActivity(intent),第一个是Activity的startActivity方法,第二个是Context的startActivity方法,这两个调用链不同,分开讲解。

14.4.1 Hook Activity的startActivity方法

Activity的startActivity在4.1.1节提到过,调用链最后调用到startActivityForResult内部,调用了mInstrumentation的execStartActivity方法来启动Activity,剩余的调用在4.1节。这个mInstrumentation是Activity的成员那边两,我们选择Instrumentation为Hook点,用代理Instrumentation来替代原始的Instrumentation来完成Hook。首先我们先写出代理Instrumentataion类,如下所示:

// InstrumentationProxy.java
public class InstrumentationProxy extends Instrumentation { 
    private static final String TAG = "Instrumentation Proxy"; 
    Instrumentation mInstrumentation;
    public InstrumentationProxy (Instrumentation instrumentation) { 
    minstrumentation = instrumentation;
    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { 
        Log.d(TAG, "Hook 成功 who :" + who); 
        try { 
            //通过反射找到Instrumentation execStartActivity方法
            Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActvity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); 
            return (ActivityResult) execStartActivity.invoke(minstrumentatiom, who, contextThread, token, target, intent, requestCode, options);
    } catch (Exception e) { 
        throw new RuntimeException(e);
    }
}

InstrumentationProxy继承了Instrumentation,并包含Instrumentataion的引用。InstrumentationProxy实现了execStartActivity方法,其内部会通过反射找到并调用Instrustation的execStartActivity方法。接下来我们用InstrumentationProxy来替换Instrumentation,代码如下

//MainActivity.java
public void replaceActivityInstrumentation(Activity activity) {
    try {
        //得到Activity的mInstrumentation字段
        Field field = Activity.class.getDeclaredField("mInstrumentation");//1
        //取消Java的权限控制检查
        field.setAccessible(true);//2
        Instrumentation instrumentation = (Instrumentation) field.get(activity);//3
        Instrumentation instrumentationProxy = new InstrumentationProxy(instrumentation);//4
        field.set(activity, instrumentationProxy);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

这样实现了代理Instrumentation替换Instrumentation的目的。最后在MainActivity的onCreate方法中使用replaceActivityInstrumentation方法,如下:

//MainActivity.java
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        replaceActivityInstrumentation(this);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("https://www.baidu.com");
        startActivity(intent);
    }
}

这样就实现了Activity的startActity的Hook。

14.4.2 Hook Context的startActivity方法

第5章讲过Context的实现类时ContextImpl,ContextImpl的startActivity方法如下:

//frameworks/base/core/java/android/app/ContextImpl.java
@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();
    if((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() != -1) {
        throw new AndroidRuntimeException(
        "Calling startActivity() from outside of an Activity"
        + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
        + " Is this really what you want?");
    }
    mMainThread.getInstrumentation().execStartActivity(getOuterContext(), mMainThread.getApplicationThread(), null, (Activity)null, intent, -1, options);
}

最后一行调用了ActivityThread的getInstrumentation方法获取Instrumentation。ActivityThread是主线程的管理类,Instrumentation是ActivityThread的成员变量,一个进程中只有一个ActivityThread,因此仍然选择Instrumentation作为Hook点,用代理Instrumentation来替代Instrumentation。代理类与14.3.1节给出的一样,接下来我们用InstrumentationProxy来替代:

//MainActivity.java
public void replaceContextInstrumentation() { 
    try( 
        //获取ActivityThread
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread"); 
        Field activityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");//1 
        activityThreadField.setAccessible(true);
        Object currentActivityThread = activityThreadField.get(null);//2 
        //获取ActivityThread中的mInstrumentation
        Field mInstrumentationField = activityThreadClazz.getDeclaredField("minstrumentation"); 
        mInstrumentationField.setAccessible(true);
        Instrumentation minstrumentation = (Instrumentation)mInstrumentationField.get(currentActivityThread); 
        Instrumentation minstrurnentationProxy = new InstrurnentationProxy(mInstrurnentation);//3 
        mInstrumentationField.set(currentActivityThread, mInstrumentationProxy);
    } catch(Exception e) {
        e.printStackTrace();
    }

首先我们通过反射获取ActivityThread类,ActivityThread类中有一个静态变量sCurrentActivityThread,用于表示当前的ActivityThread对象,因此注释1处获取ActivityThread中定义的sCurrentActivityThread字段,注释2处获取Field类型的activityThreadField对象的值,这个值就是sCurrentActivityThread对象。同理获取ActivityThread的mInstrumentation对象。 在注释3处创建InstrumentationProxy井传入此前得到的mInstrumeration对象,最后用InstrumentationProxy来替换mInstrumentation。在MainActivity中使用replaceContextlnstrumentation方法即可:

public class MainActivity extends Activity ( 
    @Override 
    protected void onCreate(Bundle savedInstanceState) ( 
    super.onCreate(savedinstanceState); 
    setContentView(R.layout.activity_main); 
    replaceContextinstrumentation();
    Intent intent = new Intent(Intent.ACTION_VIEW); 
    intent.setData(Uri.parse("https://www.baidu.com/")); 
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
    getApplicationContext().startActivity(intent);
}

在onCreate方法中访问了百度的地址,打印日志为:D/InstrumentationProxy:Hook成功---who: android.app.Application@2f2le30

14.4.3 Hook startActivity总结

Hook Context的startActivity方法和Hook Activity的startActivity方法最大的区别就是替换的Instrumentation不同,前者是ActivityThread中的Instrumentation,后者是Activity中的Instrumentation。另外替换的时间点可以在Activity的attachBaseContext方法进行,会先与onCreate方法调用。 讲到这里,知道了如何用代理来Hook startActivity方法,简单说就是找到Hook点,用代理对象来替换Hook点。

14.5 本章小结

第15章 插件化原理

15.1 动态加载技术

在应用程序运行时,动态加载一些程序中原本不存在的可执行文件并运行这些文件里的代码逻辑。可执行文件总得来说分为两种,一种是动态链接库so,另一种是dex相关文件(dex或包含dex的jar/apk文件)。热修复技术是动态加载技术派生出来的。 随着应用开发技术和业务逐步发展,动态加载技术派生出两个技术:热修复技术和插件化技术。热修复主要用来修复bug,插件化技术则用来解决应用庞大和功能模块解耦。

15.2 插件化的产生

15.2.1 应用开发的痛点和瓶颈

  1. 业务负责,模块耦合
  2. 应用间的接入
  3. 65536限制,应用占用大

15.2.2 插件化思想

给系统安装了各式应用其实就是采用的插件化思想。

15.2.3 插件化定义

将一个应用按照插件的方式进行改造的过程叫作插件化。

15.3 插件化框架对比

目前主流的插件化方案有:VirtualApk、Replugin等,加载第三方App推荐使用RePlugin,其他情况推荐VirtualApk。 下面将首先编写简单的例子实现Activity、Service插件化,然后在广播、ContentProvider、资源和so的插件化中讲解VirtualApk是如何实现的。

15.4 Activity插件化

四大组件的插件化是插件化技术的核心知识点,Activity插件化是重中之重。Activity的插件化主要有3种实现方式,分别是反射实现、接口实现和Hook技术实现。目前Hook技术是主流,本章主要介绍这个。 Hook技术实现有两种解决方案,一种是通过Hook IActivityManager来实现,另一种是Hook Instrumentation实现。

15.4.1 Activity的启动过程回顾

首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在并启动,如果不存在就会请求Zygote进程创建应用程序进程。应用程序进程启动后,AMS会请求应用程序进程创建并启动根Activity。普通Activity和根Activity启动大同小异只是不涉及应用程序进程的创建。

15.4.2 Hook IActivityManageter 方案实现

分3步实现:

15.4.2.1 注册Activity进行占坑

在AndroidManifest.xml中注册SubActivity,用来占坑,目标是要启动插件Activity即TargetActivity,这里省略了插件Activity的加载逻辑,这里默认已经加载进来。

15.4.2.2 使用占坑Activity通过AMS验证

为了防止报错,需要将启动的TargetActivity替换为SubActivity,用它来通过ASM的验证。不论Android7.0还是Android8.0以上版本,都需要AMS家族中获取IActivityManager类型的对象,因此IActivityManager是一个比较好的Hook点。 Hook点IActivityManager是一个接口,建议采用动态代理。

public class IActivityManagerProxy implements InvocationHandler { 
    private Object mIActivityManager;
    private static final String TAG = "IActivityManagerProxy";
    public IActivityManagerProxy(Object activityManager) { 
        this.mActivityManager = activityManager
    }
    Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable { 
        if("startActivity".equals(method.getName())) {//l 
            Intent intent = null;
            int index = 0; 
            for (int i = O; i < args.length; i++){
            if (args[i] instanceof Intent) {
                index = i; 
                break; 
            }
            intent = (Intent) args[index];
            Intent subIntent = new Intent(); //2 
            String packageName = com.example.liu.pluginactivity";
            subIntent.setClassName(packageName ,packageName + "StubActivity"; //3 
            subIntent.putExtra(HookHelper.TARGET_INTENT, intent); //4 
            args[index] = subIntent; //5
        }
        return method.invoke(mActivity)

原本要启动插件TargetActivity的Intent。在注释2处、3处新建一个subIntent用来启动subIntent,在注释4处用subIntentgei赋值给参数args,这样启动的目标就变为了SubActivity,用来通过AMS校验。最后用代理类IActivityManatgerProxy来替换IActivtyManager。

// HookHelper.java
public class  HookHelper {
    public static final String TARGET_INTENT = "target_intent";
    public static void hookAMS() throws Excepiton {
        Object defaultSingleton = null;
        if(Build.VERSION_SDK_INT >= 26) { //1
            Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");
            //获取activitymanager中的IActivityManagerSingleton字段
            defaultSingleton = FieldUtil.getField(activityManageClazz, null, "IActivityManagerSingleton");
        } else {
            //获取ActivityManagerNative中的gDefault字段
            Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
            defaultSingleton = FieldUtil.getField(activityManagerNativeClazz, null, "gDefault");
        }
        Class<?> singletonClazz = Class.forName("android.util.Singleton");
        Field mInstanceField = FieldUtil.getField(singletonClazz, "mInstance");//2
        // 获取iActivityManager
        Object iActivityManager = mInstanceField.get(defaultSingleton);//3
        Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] { iActivityManagerClazz }, new IActivityManagerProxy(iActivityManager));
        mInstanceField.set(defaultSingleton, proxy);
    }
}

首先在注释1处,进行系统版本仅区分,最终获取的是Singleton类型的IActivityManagerSingleton 或者gDefault字段。在2处获取Singleton类中的mInstance字段,从前面Singleton类的代码得知mInstance字段的类型为T,在注释3处得到IActivityManagerSingleton或者gDefault字段的T类型,T的类型为IActivityManager。最后动态创建代理类IActivityManagerProxy,用IActivityManagerProxy来替换IActivityManager。自定义一个Activity调用HookHelper的hookAMS方法即可。 此时使用startActivity启动TargetActivity时,启动的并不是TargetActivity,而是SubActivity同时log打印了“Hook 成功”,我们成功用SubActivity通过了AMS验证。

15.4.2.3 还原插件Activity

4.1.3节中讲到了ActivityThread启动Activity的过程,如图所示。

ActivitytThread会通过H类将代码逻辑切换到主线程中,H是ActivityThread内部类,并继承自Handler,用于dispatchMessage用于处理消息,这里用自定义Callback来替代mClassback,作为Hook点,代码如下。

// HCallback.java
public class HCallback implements Handle.Callback {
    public static final int LAUNCH_ACTIVITY = 100;
    Handler mHandler;
    public HCallback(Handler handler) {
        mHandler = handler;
    }
    @override
    public boolean handleMessage(Message msg) {
        if(msg.what = LAUNCH_ACTIVITY) {
            Object r = msg.obj;
            try {
                //得到消息中的Intent(启动SubActivity的Intent) 
                Intent intent = (Intent)FieldUtil.getField(r.getClass(), r, "intent");
                //得到此前保存起来的Intent(启动TargetActivity的Intent)
                Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);
                //将启动SubActivity的Intent替换为启动TargetActivity的Intent
                intent.setComponent(target.getComponent());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        mHandler.handleMessage(msg);
        return true;
    }
}

将启动SubActivity的Intent替换为启动TargetActivity的Intent后,在HookHelper定义一个hookHandler方法如下:

//HookHelper.java
public static void hookHandler() throws Exception {
    Class<?> activityThreadClass = Class.getName("android.app.ActivityThread");
    Object currentActivityThread = FieldUtil.getField(activityThreadClass, null, "mCurrentActivityThread");//1
    Field mHField = FieldUtil.getField(activityThread, "mH");//2
    Handler mH = (Handler)mHField.get(currentActivityThread);//3
    FieldUtil.setField(Handler.class, mH, "mCallback", new HCallback(mH));
}

ActivityThread类中有一个静态变量mCurrentActivityThread, 用于表示当前的ActivityThread,1处获取此对象,2处获取ActivityThread的mH字段,3处获取当前ActivityThread对象中的mH对象,最后用HCallback来替换mH中的mCallback。在MyApplication的attachBaseContext方法中调用HookHelper的hookHandler方法,运行程序后,发现启动的已经是插件的TargetActivity。

15.4.2.4 插件Activity的生命周期

AMS和ActivityThread之间的通信采用了token来对Activity进行表示,并且此后的Activity的生命周期处理都是根据token来对Activity表示的,我们启动Activity时用TargetActivity替换占坑SubActivity,这一过程在performLaunchActivity方法之前调用,因此ActivityClientRecord就是代表了TargetActivity,所以具有生命周期。

15.4.3 Hook Instrumentation方案实现

此方案与HookIActivityManager实现不同的是用占坑Activity替换插件Activity及还原插件Activity地方不同。启动Activity时,是调用了mInstrumentation的newActivity,其内部会用类加载器来创建Activity的实实例,所以我们方案是在Instrumentation的execStartActivity方法中用SubActivity来通过AMS验证,在Instrumentation的newActivity方法中还原TargetActivity,这俩操作都是和Instrumentation有关,因此我们用自定义的Instrumentation来替换掉mInstrumentation。我们自定义一个:

// InstrumentationProxy.java
public class InstrumentationPro

public class InstrumentationProxy extends Instrumentation { 
    private Instrumentat oInstrumentation;
    prinvate PackageManager mPackageManager;
    public instrumentationProxy(Instrumentatiaon instrumentation, PackageManager packageManager) {
        mInstrumentataion = instrumentation;
        mPackageManager = packageManager;
    }
    pulic ActivityResult execStartActivity(Context who, IBInder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
        List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        if(infos == null || infos.size() == 0) {
            intent.putExtra(HookHelper.TARGET_INTENT_NAME, intent.getComponent().getClassName());//1
            intent.setClassName(who, "com.example.liu.pluginactivity.SubActivity");//2
        }
        try {
            Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class) ; 
            return (ActivityResult) execMethod.invoke(minstrumentation, who, contextThread, toke, target, intent, requestCode, options); 
        } catch (Exception e ) { 
            e. printStackTrace () ;
        }
        return null;
    }
}
    

首先查找要启动的Activity是否已经在AndroidManifest.xml注册了,如果没有则在1处将要启动的Activity的ClassName保存起来用于后面还原TargetActivity,接着替换为要启动的StubActivity,通过反射调用execStartActivity方法,这样可以用SubActivity通过AMS验证,然后在InstrumentationProxy的newActivity方法中还原TargetActvity,如下:

//InstrumentationProxy.java
public Activity newActivity (ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { 
    String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT_NAME); 
    if (!TextUtils.isEmpty(intentName)) { 
        return super.newActivity(cl, intentName, intent);
    }
    return suer.newActivity(cl, className, intent);
}

在newActivity方法中创建了此前保存的TargetActivity,完成了还原TargetActivity。编写hooklnstrumentation方法,用InstrumentationProxy替换mInstrumentation:

//HookHelper.java
public static void hookInstrumentation(Context context) throws Exception { 
    Class<?> contextImplClass = Class.forName("android.app.ContextImpl"); 
    Field mMainThreadField =FieldUtil.getField(contextImplClass, "mMainThread"); //1 
    Object activityThread = mMainThreadField.get(context); //2 
    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
    Field mInstrumentationField = FieldUtil.getField(activityThreadClass, "mInstrumentation"); //3 
    FieldUtil.setField(activityThreadClass, activityThread, "mInstrumentation",new InstrumentationProxy ((Instrumentation) mInstrumentationField.get(activityThread), context.getPackageManager()));
}

在注释1处获取ContextImpl类的ActivityThread类型mMainThread字段,在2处获取上下文环境的ActivityThread对象,在3处获取ActivityThread类中的mInstrumentation字段,最后用InstrumentationProxy来替换Instrumentation。在MyApplication的attaachBaseContext方法中调用HookHelper的hookInstrumetnation方法,运行程序则发现启动的是插件TargetActivity。

15.4.4 总结

这一节我们学习了启动插件Activity的原理,主要的方案就是先用一个在AndroidManifest.xml中注册的Activity来进行占坑,用来通过AMS的校验,接着在合适的时机用插件Activity替换占坑的Activity。为了更好地讲解启动插件Activity的原理,本节省略了插件Activity的加载逻辑,直接创建一个TargetActivity来代表已经加载进来的插件Activity。同时这一节使我们更好地理解了 Activity的启动过程。

15.5 Service 插件化

15.5.1 插件化方面Service与Activity不同

由4.2节看到,Service的启动过程与Activity有些不同,在ContextImpl到AMD的调用过程没有交由Instrumentation来处理,在后续的ActivityThread启动Service过程也是,因此Service插件化不能通过Hook Instrumentation来实现。插件化方面Activity和Service有何不同:

  • Activity基于栈管理,一个栈里的Activity数量不会太多,可以生命有限的占坑Activity来实现,但插件化框架处理的Service数量可以近乎无限的,无法用占坑实现。
  • 在Standard模式下多次启动一个占坑Activity可以创建多个Activity实例,但是多次启动占坑Service不能创建多个Service实例。
  • 用户和界面的交互会影响到Activity的生命周期,因此插件Activity的生命周期余由系统管理,Hook IActivityMananger方案还原插件Activity就是为了这一点。Service的生命周期不受用户影响,可以由开发者管理,没必要还原插件。 综上三点,Service插件化不能用Hook IActivityManager方案来实现。

15.5.2 代理分发实现

Activity插件化的中点在于要保证它的生命周期,而Service插件化的重点是保证它的优先级,所以需要用一个真正的Service来实现。当启动插件Service时,就会先启动代理Service,当这个代理Servcie运行起来后,在它的onStartCommand等方法进行分发,执行插件TargetServce的onCreate等方法,这个方案叫作代理分发。

15.5.2.1 启动代理 Servcie

首先在AndroidManifest.xml注册代理ProxyService,可以定义android:process用于支持多进程,在代码某处启动目标服务TargetService。TargetServcie目前只是用来打印Log,确定是否被调用,它用来代表插件Service,不能直接启动,需要先启动代理ProxySerfvice,但为了这个母的我们需要Hook IActivityManager,具体的原理和步骤与15.4.2类似,定义替换IActivityManager的代理类IActivityManagerProxy,如下

public class IActivityManagerProxy implements InvocationHandler {
    private Object mActvityManager;
    private static final String TAG = "IActivityManagerProxy";
    public IActivityManagerProxy(Object activityManager) {
        this.mActivityManager = activityManager;
    }
    @Override
    public Object invoke(Object o, Method method, Object[] args) throws Throwable {
        if("startService".equals(method.getName())) {//1
            Intent intent = null;
            int index = 0;
            for (int i= 0; i< args.length; i++) {
                if(args[i] instanceOf Intent) {
                    index = i;
                    brea;
                }
            }
        }
        intent = (Intent)args[index];
        Intent proxyIntent = new Intent(); //2
        String packageName = "com.example.lliu.pluginservice";
        proxyIntent.setClassName(packageName, packageName + ".ProxyServce");//3
        proxyIntent.putExtra(ProxyServcie.TARGET_SERVICE, intent.getComponent().getClassName());//4
        args[index] = proxyIntent;//5
        Log.d(TAG, "Hook 成功");
    }
    return method.invoke(mActivityManager, args);
}

在注释1处拦截startService方法,获取参数args中第一个Intent对象,它原本要启动插件TargetActivity的Intent,在2处3处新建一个proxyIntent用来启动ProxyService,在注释4处将这个ProxyService的Intent保存到proxyIntent中便于后面进行分发,在5处将proxyIntent赋值给参数args,这样启动的目标就变成了proxyService。接着是用IActivitymanagerProxy来替换系统的IActivitymanager,如下:

// HookHelper.java
public class  HookHelper {
    public static final String TARGET_INTENT = "target_intent";
    public static void hookAMS() throws Excepiton {
        Object defaultSingleton = null;
        if(Build.VERSION_SDK_INT >= 26) { //1
            Class<?> activityManageClazz = Class.forName("android.app.ActivityManager");
            //获取activitymanager中的IActivityManagerSingleton字段
            defaultSingleton = FieldUtil.getField(activityManageClazz, null, "IActivityManagerSingleton");
        } else {
            //获取ActivityManagerNative中的gDefault字段
            Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
            defaultSingleton = FieldUtil.getField(activityManagerNativeClazz, null, "gDefault");
        }
        Class<?> singletonClazz = Class.forName("android.util.Singleton");
        Field mInstanceField = FieldUtil.getField(singletonClazz, "mInstance");//2
        // 获取iActivityManager
        Object iActivityManager = mInstanceField.get(defaultSingleton);//3
        Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] { iActivityManagerClazz }, new IActivityManagerProxy(iActivityManager));
        mInstanceField.set(defaultSingleton, proxy);
    }
}

这一步骤与“15.4.2.2 使用占坑Activity通过AMS验证”一样的,最后在自定义的Application调用这个hookAMS方法,当点击按钮启动插件TargetService时,启动的不是它,是代理ProxyService,接下来我们在ProxyService中进行代理分发。

15.5.2 代理分发

编写ProxyService类,如下:

// ProxyService.java
public class ProxyService extends Service {
    public static final String TARGET_SERVCIE = "target_servce";
    ...
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if(null == intent || !intent.hasExtra(TARGET_SERVICE) {
            return START_STICKY;
        }
        String serviceName = intent.getStringExtra(TARGET_SERVICE);
        if(null == serviceName) {
            return START_STICKY;
        }
        Service targetService = null;
        try {
            Class activityThreadClazz = Class.forName("android.app.ActivityThread");
            Method getActivityThreadMethod = activityThreadClazz.getDeclaredMethod("getApplicatiaonThread");
            ggetActivityThreadMethod.setAccessible(true);
            Object activityThread = FieldUtil.getField(activityThreadClazz, null, "sCurrentActivityThread");//1
            Object applicationThread = getActivityThreadMethod.invoke(activityThread);//2
            Class iInterfaceClazz = Class.forName("android.os.IInterface");
            Method asBinderMethod = iInterfaceClazz.getDeclaredMethod("asBinder");
            asBinderMethod.setAccessible(true);
            Object token = asBinderMethod.invoke(applicationThread);//3
            Class serviceClazz = Class.forName("android.app.Service");
            Method attachMethod = serviceClazz.getDeclaredMethod("attach", Context.class, activityThreadClazz, String.class, IBinder.class, Application.class, Object.class);
            attachMethod.setAccessible(true);
            Object defaultSingleton = null;
            if(Build.VERSION.SDK_INT >= 26) { //4
                Class<?> activityManagerClazz = Class.forName("android.app.ActivityManager");
                defaultSingleton = FieldUtil.getField(activityManageClazz, null, "IActivityManagerSingleton");
            } else {
                Class<?> activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
                defaultSington = FieldUtil.getField(activityManagerNativeClazz, null, "gDefault");
            }
            Class<?> singletonClazz = Class.forName("android.util.Singleton");
            Field mInstanceField = FieldUtil.getField(singletonClazz, "mInstance");
            Object iActivityManager = mInstanceField.get(defaultSingleton);//5
            targetService = (Service)Class.forName(serviceName).newInstance();//6
            attachMethod.invoke(targetService, this, activityThread, intent.getComponent().getClassName(), token, getApplication(), iActivityManager);//7
            targetServce.onCreate();
        } catch (Exception e) {
            e.printlnStackTrace();
            return START_STICKY;
        }
        targetService.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }
}

在onStartCommand 方法中进行代理分发,主要做了3件事:

  • ProxyService需要长时间对Service进行分发处理,所以在参数天剑不满足、出现异常和代码执行完毕时需要返回START_STICKY,这样ProxyService会重新被创建并执行onStartCommand方法。
  • 创建targetServcie并反射调用targetService的attach。
  • 进行代理分发,执行targetService的onCreate方法。 这三件事中第二件的逻辑是:为了反射调用Service的attach方法,除了要反射得到attach方法外还需要参数:ActivityThread、IBinder、IActivityManager等。在1处得到ActivityThread对象,在2处根据ActivityThread得到applicationthread对象,为的是在3处反射调用ApplicationThread的asBinder方法得到token对象,在源码中这个token是IBinder类型的。在4处到5处是为了获取iActivityManager。6处反射得到targetService,这里为了距离用targetService代表已经加载的插件Service,真正的插件化框架会用ClassLoader来加载插件中的Servcie。在7处反射执行targetService的attach方法,并传入此前得到的参数最后执行onCreate方法来完成代理分发。单机按钮,不仅启动了ProxyService,插件TargetServiceye 被启动了。

15.6 ContentProvider 插件化

在讲插件化前回顾一下启动过程。

15.6.1 ContentProvider的启动过程回顾

可以回顾4.5.1节query方法到AMS的调用过程,途中可以看出ContentProvider的query方法会调用ActivityThread的acquireProvider方法,如下所示:

// frameworks/base/core/java/android/app
public final IContentProvider acquireProvider(Content c, String auth, int useId, boolean stable) {
    final IContentProvider provider = acquireExtingProvider(c, auth, userId, stable);//1
    if(provider != null) {
        return provider;
    }
    ContentProviderHolder holder = null;
    try {
        holder = ActivityManager.getService().getContentProvider(getApplicationThread(), auth, userId, stable);//2
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
    if(holder == null) {
        SLog.e(TAG, "failed to find provider info for " + auth);
        return null;
    }
    holder = installProvider(c, holder, holder.info, true, holder.noReleaseNeeded, stable);
    return holder.provider;
}

代码所示,1处查询mProviderMap是否有目标IContentProvider存在,有则返回,没有则调用IActivityManager的getContentProvider方法,最终调用AMS的getContentProvider方法获取ContentProviderHolder(里面包含IContentProvider类型数据)。IContentProvider是一个Binder对象用于进程间通信,contentProvider的共享处理会委托给IContentProvider处理。AMS启动ContentProvider的过程与Activity类似,都是通过ActivityThread向H发送消息,将代码逻辑切换到主线程中,最终调用ContentProvider的onCreate方法,具体请看4.5节。

15.6.2 VirtualApk的实现

ContentProvider插件化的关键在于将ContentProvider插件共享给整个系统。和Service插件化类似,需要注册一个真正的ContentProvider作为代理ContentProvider,并把这个代理ContentProvider共享给整个系统,对于插件ContentProvider的请求会全部交由代理ContentProvider处理并分发给 对应的插件ContentProvider。 下面分析滴滴的VirtualApk的ContentProvider插件化是如何实现的。

15.6.2.1 VirtualApk初始化

初始化是通过调用LoadedPlugin的create方法来创建LoadPlugin对象,在它的构造方法中主要创建了一些类型的对象,如PackageInfo、Resources、ClassLoader等,还有创建存储四大组件相关的数据结构Map等。

15.6.2.2 启动代理ContentProvider

首先Hook IContentProvider,获取代理ContentResolver的Uri,在调用ContentResolver的call方法,mContent是宿主的Context,因此调用的是宿主的ContentResolver的call,用于得到IContentProvider。然后获取ActivityThread的mProviderMap,接下来便利,找到匹配的IContentProvider,用代理IContentProviderProxy替换IContentProvider,完成Hook IContentProvider。IContentProviderProxy的wrapperUri方法替换Uri,这样启动一个插件ContentProvider时会先启动ContentProvider。

15.6.2.3 代理分发

15.7 BroadcastReceiver的插件化

15.7.1 广播插件化思路

静态注册的BroadcaseReceiver全部转换为动态注册来处理。

15.7.2 VirtualApk的实现

//Corelibrary/src/main/java/com/didi/vi tualapk/internal/LcadedPlugin.java
LoadedPlugin (PluginManager pluginManager, Context context, File apk) throws PackageParser.PackageParserException { 
    Map<ComponentName, Activityinfo> receivers = new HashMap<ComponentName, Activityinfo> (); //1 
    for (PackageParser.ActivityReceiver : this.mPackage.receivers) { 
        receivers.put(receiver.getComponentName(), receiver.info); //2 
        try { 
            BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance()); //3 
            for (PackageParser ActivityIntentInfo aii : receiver.intents) { 
                this.mHostContext.registerReceiver(br, aii) ; //4
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
        this.mReceiverInfos = Collections.unmodifiableMap(receivers);
        this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);
    }

注释1处的receivers用于存储插件中静态注册的BroadcastReceiver信息,为啥它的泛型类型是Activityinfo,因为在PackageParser在解析AndroidManifest.xml时把标签当做处理了,因此BroadcastReceiver信息会存储在ActivityInfo中。在注释2处将插件BroadcastReceiver信息存储在receivers中。注释3的代码两层意思,1是根据插件BroadcastReceiver的类名,用ClassLoader加载并创建对象(类型为Object);步骤2是将Object转换为BroadcastReceiver类型。注释4处调用宿主Context的registerReceiver方法来完成插件BroadcastReceiver的注册。

15.8 资源的插件化

资源修复与AssetManager是有关的,同样资源的插件化也是。

15.8.1 系统资源加载

15.8.2 VirtualApk实现

资源的插件化方案主要有两种:一种是合并资源方案,将插件的资源全部添加到宿主的Resources中,这种方案插件可以访问宿主的资源。另一种是构建插件资源方案,每个插件都构造出独立的Resources,这种方案插件不可用访问宿主资源。VirtualApk采用了以上两种方案。

15.9 so的插件化

so的插件化可以结合第13章的so热修复来学习,so热修复方案有两种:

  • 将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先被返回和加载。
  • 调用System的load方法来接管so的加载入口。 so的插件方案与so热修复第一种方案类似,是将so插件插入到NativeLibraryElement数组中,并且将存储so插件的文件添加到nativeLibraryDirectories集合中就可以了。

15.10 本章小结

本章介绍了插件化的产生、插件化框架对比、四大组件的插件化、资源和so的插件化。插件的加载机制没有讲到,加载机制方案有两种,一是Hook ClassLoader,另一种是委托给系统的ClassLoader帮忙加载。

第16章 绘制优化

16.1 绘制性能分析

16.1.1 绘制原理

View的绘制流程有3个步骤,分别是measure、layout和draw,它们主要运行在系统的应用框架层,而真正将数据渲染到屏幕上的则是系统Native层的SurfaceFlinger服务来完成的。 绘制过程主要由CPU来进行Measure、Layout、Record、Execute的数据计算工作,GPU负责栅格化、渲染。CPU和GPU是通过图形驱动层来进行连接的,图形驱动层维护了一个队列,CPU将display list 添加到该队列中,这样GPU就可以从这个队列中取出数据进行绘制。

帧数是在1秒时间内传输图片的量,也可以理解为图形处理器每秒能够刷新几次,通常用FPS(Frames Per Second)表示。如果华民在60fps不会感觉卡顿,低于60就会。

产生卡顿的原因主要有以下几点:

  • 布局Layout过于复杂,无法在16ms内完成渲染。
  • 同一时间动画执行的次数过多,导致CPU或GPU负载过重。
  • View过度绘制,导致某些像素在同一帧时间内被绘制多次。
  • 在UI线程中做了稍微耗时的操作。
  • GC回收时暂停时间过长或者频繁的GC产生大量的暂停时间。

16.1.2 Profile GPU Rendering

16.1.3 Systrace

Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。对于UI现实性能,如动画播放不流畅、渲染卡顿等问题提供了分析数据。

16.1.4 Traceview

Traceview是Android SDK中自带的数据采集和分析工具,通过它可以得到两种数据:单词执行耗时的方法和执行次数多的方法。

16.1.4.1 使用Traceview

代码中添加TraceView监控语句如下:

//开始监控的地方调用
Debug.startMethodTracing(); 
//结束监控的地方调用
Debug.stopMethodTracing();

在新版Android studio已经启用,使用Profiler进行性能分析。

16.2 布局优化

16.2.1 布局优化工具

16.2.2 布局优化方法

主要包括合理运用布局、Include、Merge和ViewStub。

  1. 合理使用LinearLayout、RelativeLayout、ConstraintLayout防止嵌套较多
  2. 使用include标签进行布局复用
  3. 用MergeBi奥群取出多余层级
  4. 使用ViewStub提高加载速度,ViewStub只能加载1次,若要不断显示隐藏,需使用View的visibility;不能嵌套Merge;ViewStub是操作布局文件,若要操作具体View用visibility。

16.2.3 避免GPU过度绘制

  • 移除不需要的background
  • 自定义View的OnDraw中,用canvas.clipRect来指定绘制的区域,防止重叠组件发生过度绘制。

16.3 本章小结

第17章 内存优化

17.1 避免可控的内存泄漏

17.1.1 什么是内存泄漏

内存泄漏就是指没有可用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。 内存泄漏产生的原因主要有三大类:

  • 由开发人员自己编码造成的泄漏
  • 第三方框架造成泄露。
  • 由Android系统或者第三方ROM造成的泄漏。

17.1.2 内存泄漏的场景

  1. 非静态内部类的静态实例
  2. 多线程相关的匿名内部类/非静态内部类 匿名内部类夜壶已持有外部类实例的引用,多线程相关的类有AsyncTask类、Thread类和实现Runnable接口的类等,他们的匿名内部类/非静态内部类如果做耗时操作就可能内存泄漏。
  3. Handler内存泄漏 Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,他们在MessageeQueue中存在的时间会很长,导致handler无法被回收,如果Handler是非静态的,则Handler会导致它的引用的Activity或者Service不能被回收。 解决方法有两个,一是使用一个静态的Handler内部类,Handler持有的对象要使用弱引用;二是在onDestroy方法中将Callbacks和Message全部清除掉,但会可能导致handler的消息没有执行完。推荐方法一,也可以用第三方的badoo/android-weak-handler。
  4. 未正确使用Context 非必须使用Activity的情况(Dialog的Context必须使用Activity的Context),可以考虑使用Application Context来代替Activity的Context,避免Activity泄露。
  5. 静态View 静态的view会持有Activity的引用导致无法被回收,解决方法是在onDestory方法中将静态View置为null。
  6. WebView Web往往使用一次后,内存不会被释放掉,较好的方案是为WebView单独开一个进程,使用AIDL与应用主进程进行通信,可以根据业务需求,在合适的时机对WebView进程进行销毁。
  7. 资源对象未关闭 资源对象比如Cursor、File等,往往使用了缓冲,会造成内存泄漏,在资源对象不使用时,确保他们已经关闭并引用置为null,通常在finnally语句中关闭。
  8. 集合中对象没有清理 把一些对象加入到集合,若不需要该对象时,需及时清理掉。
  9. Bitmap对象 临时创建的某个比较大的Bitmap对象经过变换得到新的Bitmap对象后,尽快释放原始的Bitmap所占空间。避免静态变量持有比较大的Bitmap或大数据对象,若有则尽快置空。
  10. 监听器未关闭 很多系统服务如TelephonyManager、SensorManager需要及时unregister。

17.7 本章小结

本章介绍了如何避免可控的内存泄漏常用的内存分析工具。

本文完结。