Android图形架构总览

图形渲染流程

​ 在Android中,所有的渲染都是将内容绘制到对应的Surface上,Surface作为BufferQueue的生产方,每当发生入队操作时,都会通知BufferQueue创建者进行消费,也就是SurfaceFlinger,由SurfaceFlinger进行合并,然后发送到显示器进行显示。

下面这张图描述了关键组件的工作流程:

工作流程

  1. 当前一个可见的界面被创建时,会通过WindowManager去创建一个Window对象,而每一个Window对象中都会包含一个Surface对象,该对象由WindowManager向SurfaceFlinger请求创建。Surface中包含一个BufferQueue,它由SurfaceFlinger向HAL层调用Gralloc对其进行内存分配。

了解cmake

什么是cmake

cmake是一个跨平台的构建工具,主要是用来解决不同平台上Makefile不同的问题,通过编写CMakeLists.txt文件,然后通过cmake命令自动生成对应平台的Makefile。

编写CMakeLists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 指定运行此配置文件所需的 CMake 的最低版本
cmake_minimum_required(VERSION 3.4.1)

// 该指令的主要作用就是将指定的源文件生成链接文件,然后添加到工程中去。
// native-lib表示生成的链接文件的名字。
// SHARED表示生成的文件类型:STATIC类型在链接其它目标的时候使用。SHARED库会被动态链接,在运行时会被加载。MODULE库是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。
// native-lib.cpp 表示源文件,可以有多个。
add_library(native-lib SHARED native-lib.cpp)

// 查找一个库,log库已经包含在NDK中。
// log-lib用来存储查找结果
// log 表示查找库的名字
find_library(log-lib log)

// 为了确保CMake可以在编译时定位您的标头文件,使用该命令来指定包含标头文件的路径
include_directories(${openCVPath}/native/jni/include)

// set用来设置一些变量
// CMAKE_CURRENT_SOURCE_DIR 表示当前CMakeLists所在的目录
set(openCVPath ${CMAKE_SOURCE_DIR}/../../../../opencv-sdk )

// 使用static来添加依赖库,IMPORTED表示您只希望将库导入到项目中,
// 然后需要通过set_target_properties来指定库的路径
add_library(lib_opencv STATIC IMPORTED)

// lib_opencv就是上面导入的库的变量
// PROPERTIES 表示设置属性
// IMPORTED_LOCATION 设置导入库的位置
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION
${openCVPath}/native/libs/${ANDROID_ABI}/libopencv_java4.so)

// 将预构建库关联到您自己的原生库
// native-lib库就是原生库,将上面查找的系统库和添加的依赖库都链接到目标库上。
target_link_libraries(native-lib ${log-lib} lib_opencv)

运行cmake

运行cmake只需要执行cmake命令,后面跟上CMakeLists所在的目录位置,运行后在当前目录中生成Makefile。

1
cmake .

然后再在当前目录执行make命令,就可以生成对应的项目编译文件。

Android-ReactNative通信流程

ReactAndroid

ReactActivity

一个ReactActivity对应着一个RN组件(通过AppRegistry.registerComponent注册),而ReactActivity的功能都包装在ReactActivityDelegate中。

ReactActivity实际上类似于一个浏览器窗口,在一个窗口内对一个网页访问,并进行页面的跳转和回退。

ReactRootView

ReactRootView是ReactNative组件的根布局,是一个ViewGroup(继承自FrameLayout),所有的RN视图都会被添加到该布局下,然后该布局会被添加到Activity上,因此每个ReactActivity都有着一个ReactRootView。RN组件内的页面跳转实际上就是在该ReactRootView上重绘。

ReactNativeHost

每个RN组件的配置,设置jsbundle文件的文件名和js模块名称以及开发模式等。

  • getJSMainModuleName : 远程服务获取bundle时,获取的模块名称
  • getBundleAssetName : 本地加载bundle时,获取asset文件的文件名
  • getUseDeveloperSupport : 是否开启debug模式
  • getPackages : 配置js和native通信module,必须包含MainReactPackage
  • ReactInstanceManager : 与js通信的管理类,CatalystInstance是一个异步JSC桥梁,提供了调用js方法的JavaAPI

每个ReactActivity都需要一个Host配置,对应不同的jsbundle配置。在ReactActivity中默认会去获取Application中的ReactNativeHost,默认Application需要继承ReactApplication接口,并实现其getReactNativeHost方法。如果我们不想在Application中实现该接口(必须我们有多个RN组件需要多个ReactNativeHost对象),就必须重写ReactActivity中getReactNativeHost方法。

1
2
3
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}

加载RN组件

启动ReactActivity

首先启动ReactActivity,创建ReactActivity时会同时创建ReactActivityDelegate,ReactActivity的getMainComponentName方法对应的是js组件的注册名称(js中AppRegistry.registerComponent注册时的appKey),在ReactActivity的onCreate回调中,开始加载该js组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected void onCreate(Bundle savedInstanceState) {
if (mMainComponentName != null) {
loadApp(mMainComponentName);
}
}

protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
// 创建根布局
mReactRootView = createRootView();
// 启动该js组件,加载jsbundle文件,并通过jsc来构建virtualDom来解析js组件
// 将js控件解析成对应的View,并添加到根布局上,组成视图树。
mReactRootView.startReactApplication(
// 创建js通信管理器
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
// 将解析完成的布局放到Activity中渲染
getPlainActivity().setContentView(mReactRootView);
}
创建ReactRootView

ReactRootView继承FrameLayout,是整个RN组件的根布局,解析出来的View都会添加到该布局上。该View主要处理了测量和事件的分发拦截,包括触摸、手势、焦点等。还设置了OnGlobalLayout监听,该方法会在布局重新layout或者系统UI发生改变时触发(具体在ViewRootImpl.performTraversals中),在该监听中将发生的改变通知给js模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void onGlobalLayout() {
if (mReactInstanceManager == null || !mIsAttachedToInstance ||
mReactInstanceManager.getCurrentReactContext() == null) {
return;
}
// 键盘状态改变
checkForKeyboardEvents();
// 屏幕方向改变
checkForDeviceOrientationChanges();
// 屏幕分辨率改变
checkForDeviceDimensionsChanges();
}
创建ReactInstanceManager

ReactInstanceManager是一个CatalystInstance实例的管理者,而CatalysInstance是一个异步的JSC桥梁,负责与js进行通信,调用js方法。

Android-ReactNative渲染流程

注册组件

在RN项目中必须使用AppRegistry.registerComponent(appkey,component)来注册组件。在上一篇中的runJSBundle里面,加载js文件后,交给JSC运行(evaluateSourceCode函数),就会开始执行index.js,执行里面的代码,在index.js里面就会调用AppRegistry来注册组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
registerComponent(
appKey: string,
componentProvider: ComponentProvider,
section?: boolean,
): string {
// 为该appkey准备了一个runnable
runnables[appKey] = {
componentProvider,
run: appParameters => {
// appParameters启动参数,来自原生调用传入的
// 开始渲染
renderApplication(
componentProviderInstrumentationHook(componentProvider),
appParameters.initialProps,
appParameters.rootTag,
wrapperComponentProvider && wrapperComponentProvider(appParameters),
appParameters.fabric,
);
},
};
if (section) {
sections[appKey] = runnables[appKey];
}
return appKey;
},

启动组件

在上一篇说到,当Context创建完毕后,会启动js组件(defaultJSEntryPoint函数),通过调用js函数AppRegister.runApplication来启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
runApplication(appKey: string, appParameters: any): void {
invariant(
runnables[appKey] && runnables[appKey].run,
'Application ' +
appKey +
' has not been registered.\n\n' +
"Hint: This error often happens when you're running the packager " +
'(local dev server) from a wrong folder. For example you have ' +
'multiple apps and the packager is still running for the app you ' +
'were working on before.\nIf this is the case, simply kill the old ' +
'packager instance (e.g. close the packager terminal window) ' +
'and start the packager in the correct app folder (e.g. cd into app ' +
"folder and run 'npm start').\n\n" +
'This error can also happen due to a require() error during ' +
'initialization or failure to call AppRegistry.registerComponent.\n\n',
);

// 运行该appkey注册的runnable
runnables[appKey].run(appParameters);
}

解析组件

通过ReactNativeRenderer来解析组件视图,然后会调用组件的render方法获取渲染内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function renderApplication<Props: Object>(
RootComponent: React.ComponentType<Props>,
initialProps: Props,
rootTag: any,
WrapperComponent?: ?React.ComponentType<*>,
fabric?: boolean,
) {
let renderable = (
<AppContainer rootTag={rootTag} WrapperComponent={WrapperComponent}>
<RootComponent {...initialProps} rootTag={rootTag} />
</AppContainer>
);
if (
RootComponent.prototype != null &&
RootComponent.prototype.unstable_isAsyncReactComponent === true
) {
const AsyncMode = React.unstable_AsyncMode;
renderable = <AsyncMode>{renderable}</AsyncMode>;
}
if (fabric) {
require('ReactFabric').render(renderable, rootTag);
} else {
//
require('ReactNative').render(renderable, rootTag);
}
}

Android-ReactNative通信流程

JNI注册

Android中注册JNI方法有两种方式,一种是静态注册,一种是动态注册。

动态注册

因为JNI允许我们提供一个函数映射表(native函数和jni函数对应表)。而在执行System.loadLibrary加载so库时,会执行该so的JNI_OnLoad函数,利用这个时机可以动态注册JIN方法。具体实现的话是通过JNINativeMethod结构保存映射关系,然后通过RegisterNatives函数来将该映射关系注册。

静态注册

如果没有在JNI_OnLoad中将JNI方法注册(将方法在进程中的地址增加到ClassObject->directMethods中),则在调用的时候解析javah风格的函数(比如Java_com_example_hellojni_HelloJni_stringFromJNI),进行静态注册。静态注册根据函数名来建立java方法和jni函数的对应关系。静态注册需要根据方法名本地搜索,比较耗时。

RN中的JNI注册
JNI_OnLoad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# src/main/jni/react/jni/OnLoad.cpp
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// 调用各个类的注册方法
return initialize(vm, [] {
gloginit::initialize();
JSCJavaScriptExecutorHolder::registerNatives();
ProxyJavaScriptExecutorHolder::registerNatives();
CatalystInstanceImpl::registerNatives();
CxxModuleWrapperBase::registerNatives();
CxxModuleWrapper::registerNatives();
JCxxCallbackImpl::registerNatives();
NativeArray::registerNatives();
NativeDeltaClient::registerNatives();
ReadableNativeArray::registerNatives();
WritableNativeArray::registerNatives();
NativeMap::registerNatives();
ReadableNativeMap::registerNatives();
WritableNativeMap::registerNatives();
ReadableNativeMapKeySetIterator::registerNatives();

#ifdef WITH_INSPECTOR
JInspector::registerNatives();
#endif
});
}
CatalystInstanceImpl.registerNatives

先拿CatalystInstanceImpl来举例子,先准备好映射数据,然后构建映射表,再通过FindClass获取对应java类的引用,通过这些参数使用RegisterNatives函数来注册。

1
2
3
4
5
6
7
8
9
10
11
# src/main/jni/react/jni/CatalystInstanceImpl.h 
class CatalystInstanceImpl : public jni::HybridClass<CatalystInstanceImpl> {
// 该类对应的Java类,通过该路径来加载java类的class引用
public:
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/CatalystInstanceImpl;";

static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jclass>);
~CatalystInstanceImpl() override;

static void registerNatives();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# src/main/jni/react/jni/CatalysInstanceImpl.cpp 
void CatalystInstanceImpl::registerNatives() {
registerHybrid({
// 返回{java方法名,java类命名空间(CatalysInstanceImpl.kJavaDescriptor),JNIMethod指针}
makeNativeMethod("initHybrid", CatalystInstanceImpl::initHybrid),
makeNativeMethod("initializeBridge", CatalystInstanceImpl::initializeBridge),
makeNativeMethod("jniExtendNativeModules", CatalystInstanceImpl::extendNativeModules),
makeNativeMethod("jniSetSourceURL", CatalystInstanceImpl::jniSetSourceURL),
makeNativeMethod("jniRegisterSegment", CatalystInstanceImpl::jniRegisterSegment),
makeNativeMethod("jniLoadScriptFromAssets", CatalystInstanceImpl::jniLoadScriptFromAssets),
makeNativeMethod("jniLoadScriptFromFile", CatalystInstanceImpl::jniLoadScriptFromFile),
makeNativeMethod("jniLoadScriptFromDeltaBundle", CatalystInstanceImpl::jniLoadScriptFromDeltaBundle),
makeNativeMethod("jniCallJSFunction", CatalystInstanceImpl::jniCallJSFunction),
makeNativeMethod("jniCallJSCallback", CatalystInstanceImpl::jniCallJSCallback),
makeNativeMethod("setGlobalVariable", CatalystInstanceImpl::setGlobalVariable),
makeNativeMethod("getJavaScriptContext", CatalystInstanceImpl::getJavaScriptContext),
makeNativeMethod("jniHandleMemoryPressure", CatalystInstanceImpl::handleMemoryPressure),
});

JNativeRunnable::registerNatives();
}

RePlugin之插件Activity启动

Host中启动插件Activity

为什么同一配置要多个坑?同一坑多次启动不行?

启动插件Activity

开启一个占坑的插件Activity,需要使用RePlugin.startActivity来启动,为了避免Hook更多的API,所以无法在原生启动过程中(Instrumentation)动态替换,因此在进入原生启动流程之前,先做好准备工作和替换。

框架预处理

PluginLibraryInternalProxy.startActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
  public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
...
// 是否启动下载
// 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
// 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
if (download) {
// 如果在PluginTable.PLUGINS中找不到该插件,则尝试下载
if (PluginTable.getPluginInfo(plugin) == null) {
...
// 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
// 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
// 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
// NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
// 如果插件文件存在,则不需要再次下载,直接加载。如果文件不存在,则需要下载
if (isNeedToDownload(context, plugin)) {
// 通知上层插件不存在
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}
}
}
...
// 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
// Added by Jiongxuan Zhang
if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
...
// 通知上层插件不存在
return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
}

// 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
// Added by Jiongxuan Zhang
if (!RePlugin.isPluginDexExtracted(plugin)) {
PluginDesc pd = PluginDesc.get(plugin);
if (pd != null && pd.isLarge()) {
// 加载大插件,通知上层启动等待弹窗
return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
}
}

// WARNING:千万不要修改intent内容,尤其不要修改其ComponentName
// 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中
// 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题

// 缓存打开前的Intent对象,里面将包括Action等内容
Intent from = new Intent(intent);

// 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
from.setComponent(new ComponentName(plugin, activity));
}
// 分配坑位
ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
if (cn == null) {
// 坑位获取失败
return false;
}

// 将Intent指向到“坑位”。这样:
// from:插件原Intent
// to:坑位Intent
intent.setComponent(cn);
// 启动坑位Activity
context.startActivity(intent);
// 通知外界,已准备好要打开Activity了
// 其中:from为要打开的插件的Intent,to为坑位Intent
RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);
return true;
}
分配坑位Activity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {

ActivityInfo ai = null;
String container = null;
PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);

try {
// 获取 ActivityInfo(可能是其它插件的 Activity,所以这里使用 pair 将 pluginName 也返回)
ai = getActivityInfo(plugin, activity, intent);
if (ai == null) {
// 从插件的PackageInfo中找不到该插件Activity的ActivityInfo
return null;
}
// 存储此 Activity 在插件 Manifest 中声明主题到 Intent
intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);
...
// 根据 activity 的 processName,选择进程 ID 标识
if (ai.processName != null) {
process = PluginClientHelper.getProcessInt(ai.processName);
}
// 容器选择(启动目标进程)
IPluginClient client = MP.startPluginProcess(plugin, process, info);
if (client == null) {
return null;
}

// 插件所在进程远程分配坑位
container = client.allocActivityContainer(plugin, process, ai.name, intent);
...
} catch (Throwable e) {
...
}
// 分配失败
if (TextUtils.isEmpty(container)) {
return null;
}
PmBase.cleanIntentPluginParams(intent);
...
// 就坑位Activity信息返回
return new ComponentName(IPC.getPackageName(), container);
}
获取插件Activity的ActivityInfo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) {
// 获取插件对象
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
// 获取插件对象失败
return null;
}

ActivityInfo ai = null;
// activity 不为空时,从插件声明的 Activity 集合中查找
if (!TextUtils.isEmpty(activity)) {
// Plugin.mLoader.mComponents中保存了该插件所有的组件列表,该组件信息从插件的PackageInfo中获取
ai = p.mLoader.mComponents.getActivity(activity);
} else {
// activity 为空时,根据 Intent 匹配
ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent);
}
return ai;
}
解压插件

在doLoad方法中,会将插件包解压,将dex和so文件提取出来,通过应用的PackageManager加载该插件apk,然后获取该插件的PackageInfo。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
private boolean loadLocked(int load, boolean useCache) {
// 若插件被“禁用”,则即便上次加载过(且进程一直活着),这次也不能再次使用了
// Added by Jiongxuan Zhang
int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());
if (status < PluginStatusController.STATUS_OK) {
// 加载失败
return false;
}
if (mInitialized) {
if (mLoader == null) {
return false;
}
if (load == LOAD_INFO) {
boolean rl = mLoader.isPackageInfoLoaded();
return rl;
}
if (load == LOAD_RESOURCES) {
boolean rl = mLoader.isResourcesLoaded();
return rl;
}
if (load == LOAD_DEX) {
boolean rl = mLoader.isDexLoaded();
return rl;
}
boolean il = mLoader.isAppLoaded();
// 如果已经初始化过该插件,则直接返回是否被加载
return il;
}
mInitialized = true;
...
// 这里先处理一下,如果cache命中,省了后面插件提取(如释放Jar包等)操作
if (useCache) {
boolean result = loadByCache(load);
// 如果缓存命中,则直接返回
if (result) {
return true;
}
}
Context context = mContext;
ClassLoader parent = mParent;
PluginCommImpl manager = mPluginManager;
// 第一次尝试加载
boolean rc = doLoad(logTag, context, parent, manager, load);
if (rc) {
try {
// 第一次加载成功,至此,该插件已开始运行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
...
}
return true;
}
...
// 清除第一次加载失败的残留
// 清空数据对象
mLoader = null;
// 删除优化dex文件
File odex = mInfo.getDexFile();
if (odex.exists()) {
odex.delete();
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// support for multidex below LOLLIPOP:delete Extra odex,if need
try {
FileUtils.forceDelete(mInfo.getExtraOdexDir());
} catch (IOException e) {
e.printStackTrace();
}
}
// 尝试第二次加载
rc = doLoad(logTag, context, parent, manager, load);
if (!rc) {
// 第二次失败则直接返回失败
return false;
}
...
try {
// 第二次成功,至此,该插件已开始运行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
...
}
return true;
}
处理dex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
 final boolean loadDex(ClassLoader parent, int load) {
try {
PackageManager pm = mContext.getPackageManager();

mPackageInfo = Plugin.queryCachedPackageInfo(mPath);
if (mPackageInfo == null) {
// 通过应用的PackageManager加载插件APK,获得插件的PackageInfo
// getPackageArchiveInfo可以获取没有安装(没有在PMS中注册的)的应用的包信息
mPackageInfo = pm.getPackageArchiveInfo(mPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
mPackageInfo = null;
return false;
}
// 将插件包信息中的资源路径(此处修改applicationInfo资源路径后,就可以通过pm.getResourcesForApplication方法获取资源Resources对象)和so路径指向之前解压的路径
mPackageInfo.applicationInfo.sourceDir = mPath;
mPackageInfo.applicationInfo.publicSourceDir = mPath;
if (TextUtils.isEmpty(mPackageInfo.applicationInfo.processName)) {
mPackageInfo.applicationInfo.processName = mPackageInfo.applicationInfo.packageName;
}
...
mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
}
...
// 创建或获取ComponentList表
// Added by Jiongxuan Zhang
mComponents = Plugin.queryCachedComponentList(mPath);
if (mComponents == null) {
// ComponentList
mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);

// 动态注册插件中声明的 receiver
// 将插件manifest中静态注册的receiver通过常驻进程动态注册,虽然无法实现静态注册,但是由于常驻进程存活时间长,所以即使插件进程退出,也可以接受到通知。
regReceivers();

// 缓存表:ComponentList
// 将PackageInfo中的组件都缓存到列表中,方便查询
synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
}
/* 只调整一次 */
// 调整插件中组件的进程名称
adjustPluginProcess(mPackageInfo.applicationInfo);
// 调整插件中 Activity 的 TaskAffinity
adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
}
if (load == Plugin.LOAD_INFO) {
return isPackageInfoLoaded();
}
mPkgResources = Plugin.queryCachedResources(mPath);
// LOAD_RESOURCES和LOAD_ALL都会获取资源,但LOAD_INFO不可以(只允许获取PackageInfo)
if (mPkgResources == null) {
// Resources
try {
if (BuildConfig.DEBUG) {
// 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个
Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
} else {
// 通过上面获得的PackageInfo和对applicationInfo内资源路径的修改,得到一个指向之前解压的资源的Resources对象
mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
}
} catch (NameNotFoundException e) {
return false;
}
if (mPkgResources == null) {
return false;
}
// 缓存表: Resources
synchronized (Plugin.FILENAME_2_RESOURCES) {
Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
}
}
if (load == Plugin.LOAD_RESOURCES) {
return isResourcesLoaded();
}

mClassLoader = Plugin.queryCachedClassLoader(mPath);
if (mClassLoader == null) {
// ClassLoader
String out = mPluginObj.mInfo.getDexParentDir().getPath();
//changeDexMode(out);

//
Log.i("dex", "load " + mPath + " ...");
if (BuildConfig.DEBUG) {
// 因为Instant Run会替换parent为IncrementalClassLoader,所以在DEBUG环境里
// 需要替换为BootClassLoader才行
// Added by yangchao-xy & Jiongxuan Zhang
parent = ClassLoader.getSystemClassLoader();
} else {
// 线上环境保持不变
parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
}
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;

long begin = 0;
boolean isDexExist = false;
// 通过解压的dex路径和so路径,创建一个PluginDexClassLoader,用来加载该插件的class
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);

if (mClassLoader == null) {
return false;
}
// 缓存表:ClassLoader
synchronized (Plugin.FILENAME_2_DEX) {
Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader));
}
}
if (load == Plugin.LOAD_DEX) {
return isDexLoaded();
}

// 创建插件的上下文对象
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);

} catch (Throwable e) {
return false;
}

return true;
}
创建插件Application

安装完插件后,会检查插件是否需要创建插件application,如果需要,则创建插件的application,并调用其attachBaseContext和onCreate回调,并将上面创建的PluginContext作为mBase。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 private void callAppLocked() {
// 获取并调用Application的几个核心方法
if (!mDummyPlugin) {
// NOTE 不排除A的Application中调到了B,B又调回到A,或在同一插件内的onCreate开启Service/Activity,而内部逻辑又调用fetchContext并再次走到这里
// NOTE 因此需要对mApplicationClient做判断,确保永远只执行一次,无论是否成功
if (mApplicationClient != null) {
// 已经初始化过,无需再次处理
return;
}
// 通过反射创建插件manifest中配置的application
mApplicationClient = PluginApplicationClient.getOrCreate(
mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);
// 将PluginContext作为参数传给application
if (mApplicationClient != null) {
mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
mApplicationClient.callOnCreate();
}
} else {
}
}
启动目标进程

回到第3步,拿到了已经安装好的插件对象,从插件的组件列表(Plugin.mLoader.mComponents)中获得启动的Activity的信息,通过该Activity的process信息和插件信息从常驻进程服务那获得该插件的PluginClient服务对象,通过该对象和插件进程进行通信,如果插件进程没有启动,则通过provider启动该进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
...
//
PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);

// 获取插件进程在常驻进程中注册的服务代理对象
IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client != null) {
return client;
}

// 分配,如果插件进程没有启动
int index = IPluginManager.PROCESS_AUTO;
try {
index = PluginProcessMain.allocProcess(plugin, process);
} catch (Throwable e) {
}
// 分配的坑位不属于UI、和自定义进程,就返回。
if (!(index == IPluginManager.PROCESS_UI
|| PluginProcessHost.isCustomPluginProcess(index)
|| PluginManager.isPluginProcess(index))) {
return null;
}

// 启动,通过provider启动插件进程
boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
if (!rc) {
return null;
}

// 再次获取该插件进程注册的服务代理
client = PluginProcessMain.probePluginClient(plugin, process, info);
if (client == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "spp pc n");
}
return null;
}
return client;
}
目标进程分配坑位

根据启动Activity的启动模式和样式从注册的坑位中分配一个合适的坑位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
final String bindActivity(String plugin, int process, String activity, Intent intent) {

/* 获取插件对象 */
Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
return null;
}

/* 获取 ActivityInfo */
ActivityInfo ai = p.mLoader.mComponents.getActivity(activity);
if (ai == null) {
return null;
}

if (ai.processName == null) {
ai.processName = ai.applicationInfo.processName;
}
if (ai.processName == null) {
ai.processName = ai.packageName;
}

/* 获取 Container */
String container;

// 自定义进程
if (ai.processName.contains(PluginProcessHost.PROCESS_PLUGIN_SUFFIX2)) {
String processTail = PluginProcessHost.processTail(ai.processName);
container = mACM.alloc2(ai, plugin, activity, process, intent, processTail);
} else {
container = mACM.alloc(ai, plugin, activity, process, intent);
}

if (TextUtils.isEmpty(container)) {
return null;
}

/* 检查 activity 是否存在 */
Class<?> c = null;
try {
c = p.mLoader.mClassLoader.loadClass(activity);
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
}
}
if (c == null) {
return null;
}

return container;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map,
String plugin, String activity, Intent intent) {
// 坑和状态的 map 为空
if (map == null) {
return null;
}

// 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射
for (ActivityState state : map.values()) {
if (state.isTarget(plugin, activity)) {
return state;
}
}

// 新分配:找空白的,第一个
for (ActivityState state : map.values()) {
if (state.state == STATE_NONE) {
state.occupy(plugin, activity);
return state;
}
}

ActivityState found;

// 重用:则找最老的那个
found = null;
for (ActivityState state : map.values()) {
if (!state.hasRef()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
}
if (found != null) {
found.occupy(plugin, activity);
return found;
}

// 强挤:最后一招,挤掉:最老的那个
found = null;
for (ActivityState state : map.values()) {
if (found == null) {
found = state;
} else if (state.timestamp < found.timestamp) {
found = state;
}
}
if (found != null) {
found.finishRefs();
found.occupy(plugin, activity);
return found;
}
// never reach here
return null;
}
启动坑位Activity

回到第2步中,在获取到坑位Activity后,直接将其放到Intent中,然后正常启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
        ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
if (cn == null) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "plugin cn not found: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process);
}
return false;
}

// 将Intent指向到“坑位”。这样:
// from:插件原Intent
// to:坑位Intent
intent.setComponent(cn);

if (LOG) {
LogDebug.d(PLUGIN_TAG, "start activity: real intent=" + intent);
}

// if (RePluginInternal.FOR_DEV) {
// try {
// String str = cn.getPackageName() + "/" + cn.getClassName();
// if (LOG) {
// LogDebug.d(PLUGIN_TAG, "str=" + str);
// }
// new ProcessBuilder().command("am", "start", "-D", "--user", "0", "-n", str).start();
// } catch (IOException e) {
// e.printStackTrace();
// }
// } else {

context.startActivity(intent);
ClassLoader替换坑位

坑位Activity被正常启动,经过AMS和当前进程以及目标进程多次来回后,在performLaunchActivity时,使用CLassLoader来加载该Activity类。使用的是appContext.getClassLoader,而ContextImpl中的classLoader是从PackageInfo中获取的,在上一篇中提到,唯一的Hook点就是在启动的时候将PackageInfo.mClassLoader改成RePluginClassLoader对象,所以最终会进入RePluginClassLoader.loadClass方法。在该方法中会使用插件的PluginDexClassLoader来加载坑位对应的Activity,然后返回到performLaunchActivity中继续启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {

}
...
return activity;
}
加载真正的插件Activity

在RepluginClassLoader的loadClass方法中,会先通过PMF.loadClass加载类,如果失败,则由原生的PathDexClassLoader来加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
final Class<?> loadClass(String className, boolean resolve) {
// 加载Service中介坑位
if (className.startsWith(PluginPitService.class.getName())) {
return PluginPitService.class;
}

// 如果Activity坑位列表中包含该类,则尝试将坑位替换成原本的Activity
if (mContainerActivities.contains(className)) {
Class<?> c = mClient.resolveActivityClass(className);
if (c != null) {
// 返回插件Activity
return c;
}
return DummyActivity.class;
}

// 如果Service坑位列表中包含该类,则尝试将坑位替换成原本的Service
if (mContainerServices.contains(className)) {
Class<?> c = loadServiceClass(className);
if (c != null) {
return c;
}
return DummyService.class;
}

// 如果Provider坑位列表中包含该类,则尝试将坑位替换成原本的Provider
if (mContainerProviders.contains(className)) {
Class<?> c = loadProviderClass(className);
if (c != null) {
return c;
}
// 输出warn日志便于查看
// use DummyProvider orig=
if (LOGR) {
LogRelease.w(PLUGIN_TAG, "p m hlc u d p o " + className);
}
return DummyProvider.class;
}
return loadDefaultClass(className);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  final Class<?> resolveActivityClass(String container) {
String plugin = null;
String activity = null;

// 先找登记的,如果找不到,则用forward activity
PluginContainers.ActivityState state = mACM.lookupByContainer(container);
if (state == null) {
return ForwardActivity.class;
}
plugin = state.plugin;
activity = state.activity;

Plugin p = mPluginMgr.loadAppPlugin(plugin);
if (p == null) {
return null;
}
// 获取插件的ClassLoader
ClassLoader cl = p.getClassLoader();
Class<?> c = null;
try {
// 使用插件的PluginDexClassLoader来加载插件Activity
c = cl.loadClass(activity);
} catch (Throwable e) {
}
return c;
}
PluginActivity替换上下文

实际上插件的每个Activity在编译的时候都会改成继承RePlugin自己定义的Activity(这些都定义在plugin-lib中),该Activity会在声明周期触发时,通过反射调用host-lib进行一系列的操作。在attach中会将Activity的上下文改成PluginContext,这样当我们使用context.getResources等方法时,得到的就是插件的resources。

1
2
3
4
5
6
7
8
public abstract class PluginActivity extends Activity {
@Override
protected void attachBaseContext(Context newBase) {
// 通过反射创建PluginContext,这样插件Activity的上下文就是PluginContext,其内部的ClassLoader和Resources就是该插件的ClassLoader和Resources。
newBase = RePluginInternal.createActivityContext(this, newBase);
super.attachBaseContext(newBase);
}
}
1
2
3
final Context createBaseContext(Context newBase) {
return new PluginContext(newBase, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
}

流程如下:

graph TD
A[启动Activity_A] --> B{插件是否下载}
B-->|否|C[通知上层插件不存在]
B-->|是|D{插件是否安装}
D-->|否|E[解压插件,处理dex,获得PackageInfo]
D-->|是|F[获取A的ActivityInfo]
E-->F
F-->G[启动目标进程,并获取目标进程Binder服务代理]
G-->H[通知目标进程服务分配坑位,获得坑位Activity]
H-->I[启动坑位Activity]
I-->J[AMS正常启动坑位Activity]
J-->K[目标进程newActivity]
K-->L[RePluginClassLoader将坑位替换]
L-->M[使用目标插件的PluginDexClassLoader加载真正的Activity_A]
M-->N[拿到替换的Activity对象继续和AMS交互]
N-->O[attach时替换Activity的上下文]

RePlugin-分析计划

  1. RePlugin之Host进程启动
  2. RePlugin之插件Activity启动

RePlugin之Host进程启动

常驻进程

RePlugin默认开启常驻进程,所谓常驻进程就是单独启动一个进程来运行插件服务,可以在gradle中配置关闭常驻进程,那么服务会运行在UI进程。

1
2
3
4
repluginHostConfig {
/** 是否使用常驻进程? */
persistentEnable false
}

启用常驻进程下插件服务的启动

UI进程启动

创建RePluginApplication,在attachBaseContext函数中开始初始化RePlugin框架。

初始化RePlugin

通过RePlugin.App.attachBaseContext函数初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void attachBaseContext(Application app, RePluginConfig config) {
...
// 标记当前进程是UI进程还是常驻进程
IPC.init(app);
...
// 通过反射获取gradle中repluginHostConfig中的配置信息
HostConfigHelper.init();
...
// 创建PmBase对象,初始化插件管理框架,并且Hook应用的ClassLoader
PMF.init(app);
// 挂载所有插件
PMF.callAttach();

sAttached = true;
}

创建PmBase对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PmBase(Context context) {
mContext = context;
// 添加客户端进程对应的provider和service的authorities,用来启动对应的组件
// processIndex是子进程个数,不能超过2
if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) {
String suffix;
if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI) {
suffix = "N1";
} else {
suffix = "" + PluginManager.sPluginProcessIndex;
}
// 如果是UI进程则启动ProviderN1和ServiceN1
// 如果是插件进程则启动Provider0、Provider1和Service0、Service1
mContainerProviders.add(IPC.getPackageName() + CONTAINER_PROVIDER_PART + suffix);
mContainerServices.add(IPC.getPackageName() + CONTAINER_SERVICE_PART + suffix);
}

// 4.创建Binder服务PluginClient,每个客户端都拥有一个该服务,常驻进程持有所有客户端进程该服务的代理对象,
// 常驻进程通过mClient服务的代理调用客户端的PluginProcessPer服务
mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities);

//
mLocal = new PluginCommImpl(context, this);

//
mInternal = new PluginLibraryInternalProxy(this);
}
创建PluginClient服务

每个客户端都有一个PluginClient服务,该服务包含PluginServiceServer子服务和一个坑管理容器,因此每个客户端进程都有一个自己的坑容器。

1
2
3
4
5
6
7
8
9
10
11
12
PluginProcessPer(Context context, PmBase pm, int process, HashSet<String> containers) {
mContext = context;
mPluginMgr = pm;
// 在服务中又创建了子服务 PluginServiceServer
mServiceMgr = new PluginServiceServer(context);

// 创建插件容器管理,坑的分配
mACM = new PluginContainers();
// UI进程和自定义子进程初始化给定启动模式的activity对应manifest中占的坑的名称
// 也就是设置坑位
mACM.init(process, containers);
}

Java是解释执行吗?

Java是解释执行吗?

并不完全是,我们编写的Java代码,首先通过javac编译成字节码,然后在运行时被JVM内嵌的解释器将字节码逐行解释为机器码。但是每次都逐行解释效率非常低,所以JVM提供了JIT编译器,用于将热点代码直接编译成机器码并缓存,后续直接调用而不需要再次编译或解释。那么对于部分热点代码就是编译执行了,剩下的非热点代码依旧还是解释执行。

动态编译–JIT编译器

原理

JIT工作原理

对于Java代码,刚开始都是被编译器编译成字节码文件,然后字节码文件会被交给JVM解释执行,而JIT编译技术会将运行频率很高的字节码直接编译成机器指令以提高性能。

server模式和client模式

JIT编译器在运行时有两种模式,server模式相对于client模式来说,启动时速度较慢,但是一旦运行起来,性能会有很大提升。原因是client模式使用的是C1轻量级编译器,而server模式启动的是C2重量级编译器,C2比C1编译的更加彻底,所以启动较慢,但是启动后性能更好。

FastThreadLocal快在哪

ThreadLocal原理

ThreadLocal本质上就是在Thread对象中维护了一个Map对象,Map的key为ThreadLocal自身,value为要存的值。将数据保存在Thread中而不是保存在ThreadLocal中,这一设计使得当线程对象销毁时,在该线程的所有变量都会随之销毁。以ThreadLocal对象为key,使得一个Thread对象可以同时被多个ThreadLocal对象使用,每个ThreadLocal对应一个值,保存在Thread对象的Map中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 该Map在Thread对象中,由ThreadLocal对象维护
ThreadLocal.ThreadLocalMap threadLocals = null;

public void set(T value) {
Thread t = Thread.currentThread();
// 获取该线程的Map
ThreadLocalMap map = getMap(t);
// 将当前ThreadLocal和值存入Map
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

FastThreadLocal优化点

由于ThreadLocal使用的是hash map数据结构,其查找需要通过hash值计算索引,再通过索引查找值,其速度比数组结构要慢。所以Netty提供了一种数组结构的ThreadLocal,也就是FastThreadLocal。其实现原理是在Thread对象中维护了一个InternalThreadLocalMap对象,该InternalThreadLocalMap对象内部维护了一个数组,数组中保存要存的值,不保存对应的FastThreadLocal对象,而是每个FastThreadLocal自己维护该值在数组中对应的索引。一个FastThreadLocal所保存值对应的索引在其创建的时候已经确定,其所保存的所有Thread都应用该索引位置。

1
2
3
4
5
6
// 创建FastThreadLocal,确定其所对应的数组索引。
// 该值是静态原子类型的,所有ThreadLocal对象共享,所以不会出现索引冲突值被覆盖等问题。
static final AtomicInteger nextIndex = new AtomicInteger();
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×