从零开始实现一个插件化框架(三) - 完结篇

上面代码可以知道, 通过createBaseContextForActivity方法创建了一个ContextImpl对象,并把它传入了Activity中的attach方法。这个稍后再来看,我们先看一下ContextImpl到底是什么:

// Activity.java

final void attach(Context context // …) {

attachBaseContext(context);

// …

}

@Override

protected void attachBaseContext(Context newBase) {

super.attachBaseContext(newBase);

if (newBase != null) {

newBase.setAutofillClient(this);

}

}

其实就是在这里创建了一个Context,看一下super是怎么实现的,一路往上点:

// ContextWrapper.java

Context mBase;

protected void attachBaseContext(Context base) {

if (mBase != null) {

throw new IllegalStateException(“Base context already set”);

}

mBase = base;

}

到这里是不是就很熟悉了,所有的资源加载都是通过ContextWrapper中的mBase来进行加载的,这个mBase就是在ActivityThread中传入进来的ContextImpl,那好,我们继续返回去,看看ContextImpl是怎么创建的:

// ActivityThread.java

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {

final int displayId;

// … 其他逻辑

ContextImpl appContext = ContextImpl.createActivityContext(

this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

// … 其他逻辑

return appContext;

}

直接通过ContextImpl静态方法创建了自己,继续往下走:

// ContextImpl.java

static ContextImpl createActivityContext(ActivityThread mainThread,

LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) {

// … 其他逻辑

// 直接创建了一个ContextImpl对象

ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader);

// 。。。

final ResourcesManager resourcesManager = ResourcesManager.getInstance();

// 这里将通过 resourcesManager.createBaseActivityResources

// 把resources放入context中

context.setResources(resourcesManager.createBaseActivityResources(activityToken,

packageInfo.getResDir(),

splitDirs,

packageInfo.getOverlayDirs(),

packageInfo.getApplicationInfo().sharedLibraryFiles,

displayId,

overrideConfiguration,

compatInfo,

classLoader));

context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,

context.getResources());

return context;

}

我们发现通过resourcesManager.createBaseActivityResources方法创建了一个Resources,并放入了context中,这个方法中 的参数packageInfo.getResDir()就是资源路径,到这里,是不是就明白了为什么插件的资源无法加载?因为Resources路径是我们宿主的路径~!,那如果加载插件资源就有了思路,可以自己创建一个插件的Resources,来替换掉Context中的Resources

好,继续来看Resources是如何创建的:

// ResourcesManager.java

public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,

@Nullable String resDir,

@Nullable String[] splitResDirs,

@Nullable String[] overlayDirs,

@Nullable String[] libDirs,

int displayId,

@Nullable Configuration overrideConfig,

@NonNull CompatibilityInfo compatInfo,

@Nullable ClassLoader classLoader) {

try {

Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,

“ResourcesManager#createBaseActivityResources”);

final ResourcesKey key = new ResourcesKey(

resDir,

splitResDirs,

overlayDirs,

libDirs,

displayId,

overrideConfig != null ? new Configuration(overrideConfig) : null,

compatInfo);

// … 无关代码

return getOrCreateResources(activityToken, key, classLoader);

} finally {

Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

}

}

在这个方法中,创建了一个ResourcesKey对象,这个对象其实就是资源路径,最后通过getOrCreateResources方法返回,看方法名就可以才出来,如果有Resources直接返回,否则创建返回,看一下它的实现:

// ResourcesManager.java

private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,

@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {

synchronized (this) {

// …

if (activityToken != null) {

// …

if (key.hasOverrideConfiguration()

&& !activityResources.overrideConfig.equals(Configuration.EMPTY)) {

// …

} else {

// …

}

ResourcesImpl resourcesImpl = createResourcesImpl(key);

if (resourcesImpl == null) {

return null;

}

final Resources resources;

if (activityToken != null) {

resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,resourcesImpl, key.mCompatInfo);

} else {

resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);

}

return resources;

}

}

这里有创建了一个ResourcesImpl对象,在创建resources的时候传入了,那ResourcesImpl是什么呢?

// ResourcesManager.java

private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {

final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);

daj.setCompatibilityInfo(key.mCompatInfo);

final AssetManager assets = createAssetManager(key);

if (assets == null) {

return null;

}

final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);

final Configuration config = generateConfig(key, dm);

final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

if (DEBUG) {

Slog.d(TAG, “- creating impl=” + impl + " with key: " + key);

}

return impl;

}

AssetManager是在这里创建的!它的功能不用多说了吧,可以加载任意路径下的资源,那么我们就自己创建一个AssetManager来替换掉原来的,怎么创建呢,继续看它怎么创建的

protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {

final AssetManager.Builder builder = new AssetManager.Builder();

// resDir can be null if the ‘android’ package is creating a new Resources object.

// This is fine, since each AssetManager automatically loads the ‘android’ package

// already.

if (key.mResDir != null) {

try {

// 添加 ApkAssets 对象,加载apk资源

builder.addApkAssets(loadApkAssets(key.mResDir, false /sharedLib/,

false /overlay/));

} catch (IOException e) {

Log.e(TAG, "failed to add asset path " + key.mResDir);

return null;

}

}

// 。。。

return builder.build();

}

可以看到,AssetManager在创建的时候,添加了一个ApkAssets对象,这里好像无从下手。怎么办?其实看过API26的同学都知道,AssetManager中有一个addAssetPath方法,在API26以前,都是通过这个方法创建的,虽然这个方法 现在被标记为过时了,但还是可以反射到的。

插件资源加载

下面我们就来撸码,替换掉插件的资源加载器:

fun loadAsset(context: Context): Resources? {

try {

// 创建AssetManager对象

val assetManager = AssetManager::class.java.newInstance()

// 执行addAssetPath方法,添加资源加载路径

val addAssetPathMethod =

assetManager::class.java.getDeclaredMethod(“addAssetPath”, String::class.java)

addAssetPathMethod.isAccessible = true

addAssetPathMethod.invoke(assetManager, “sdcard/plugin-debug.apk”)

// 创建Resources

return Resources(

assetManager,

context.resources.displayMetrics,

context.resources.configuration

)

} catch (e: Exception) {

e.printStackTrace()

}

return null

}

在这里我们创建一个插件的资源加载器,然后在应用启动的时候替换掉,在Application中:

class DynamicApp : Application() {

// 插件的资源

private var mResources: Resources? = null

override fun onCreate() {

super.onCreate()

// 获取插件的资源

mResources = LoadUtils.loadAsset(this)

}

// 在这里重写getResources方法进行替换

override fun getResources(): Resources {

if (mResources == null) {

return super.getResources()

}

return mResources!!

}

}

在宿主里面替换完成,插件里面要使用宿主的Resources怎么办?很简单,直接使用application的Resources不就可以了吗。因为插件中的类是会合并到宿主的,所以他们的Application是相同的。

所以可以在插件里面创建一个BaseActivity, 重写getResources方法,让它使用application中的:

abstract class BaseActivity : Activity() {

override fun getResources(): Resources {

if (application != null && application.resources != null)

return application.resources

return super.getResources()

}

}

然后插件的MainActivity就可以直接使用了

class MainActivity : BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

Toast.makeText(this, “插件的MainActivity”, Toast.LENGTH_SHORT).show()

}

}

插件布局里面是一个TextView, 显示 “Hello World”。 下面来看看运行效果吧~

不同版本API的适配

到这里一个插件化应用就完成了,下面我们来看一下不同版本API的适配,其实github中的demo是做了的。很简单,我们来看一下每个版本都有哪些不同呢?

一、 ActivityManager


可以看懂, API26以前,用的是ActivityManagerNative, API26以后用的是ActivityManager, 而且两个静态常量名也变了,所以我们Hook的时候可以做一下判断:

public static void hookAms() {

try {

Object singleTon = null;

/*

  • android 26或以上版本的API是一样的

*/

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

Class<?> activityManagerClass = Class.forName(“android.app.ActivityManager”);

Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField(“IActivityManagerSingleton”);

iActivityManagerSingletonField.setAccessible(true);

singleTon = iActivityManagerSingletonField.get(null);

} else {

/*

  • android 26或以下版本的API是一个系列

*/

Class<?> activityManagerClass = Class.forName(“android.app.ActivityManagerNative”);

Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField(“gDefault”);

iActivityManagerSingletonField.setAccessible(true);

singleTon = iActivityManagerSingletonField.get(null);

}

Class<?> singleTonClass = Class.forName(“android.util.Singleton”);

Field mInstanceField = singleTonClass.getDeclaredField(“mInstance”);

mInstanceField.setAccessible(true);

// 获取到IActivityManagerSingleton的对象

final Object mInstance = mInstanceField.get(singleTon);

Class<?> iActivityManagerClass = Class.forName(“android.app.IActivityManager”);

Object newInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),

new Class[]{iActivityManagerClass},

new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().equals(“startActivity”)) {

int index = 0;

for (int i = 0; i < args.length; i++) {

if (args[i] instanceof Intent) {

index = i;

break;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

点击:

Android架构视频+BAT面试专题PDF+学习笔记》即可免费获取~

中…(img-jm67FH0p-1710684541569)]
[外链图片转存中…(img-tZYQ6H7n-1710684541570)]
[外链图片转存中…(img-Cqi6LX4R-1710684541570)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-8kx76kqq-1710684541571)]

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

[外链图片转存中…(img-io2mzRl0-1710684541572)]

[外链图片转存中…(img-aTtDH1fD-1710684541572)]

[外链图片转存中…(img-gonVA9Aa-1710684541572)]

点击:

Android架构视频+BAT面试专题PDF+学习笔记》即可免费获取~

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值