}
}
其实就是在这里创建了一个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;
}
}
Intent proxyIntent = new Intent();
proxyIntent.setClassName(“com.kangf.dynamic”,
“com.kangf.dynamic.ProxyActivity”);
proxyIntent.putExtra(“oldIntent”, (Intent) args[index]);
args[index] = proxyIntent;
}
return method.invoke(mInstance, args);
}
});
mInstanceField.set(singleTon, newInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
二、ActivityThread
在API28之后,加入了Lifecycle,所以接收消息的时候加入了很多东西,我们上一篇文章已经讲到了它的流程,而在API28以前,是很简单的,直接通过一个LAUNCH_ACTIVITY(value = 100)常量接收,msg.obj就是一个ActivityClientRecord对象,然后调用了handleLaunchActivity()。所以我们在hook的时候可以加个判断,处理也非常简单:
public static void hookHandler() {
try {
// 获取ActivityThread实例
final Class<?> activityThreadClass = Class.forName(“android.app.ActivityThread”);
Field activityThreadField = activityThreadClass.getDeclaredField(“sCurrentActivityThread”);
activityThreadField.setAccessible(true);
final Object activityThread = activityThreadField.get(null);
// 获取Handler实例
Field mHField = activityThreadClass.getDeclaredField(“mH”);
mHField.setAccessible(true);
Object mH = mHField.get(activityThread);
Class<?> handlerClass = Class.forName(“android.os.Handler”);
Field mCallbackField = handlerClass.getDeclaredField(“mCallback”);
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.e(“kangf”, "handling code = " + msg.what);
switch (msg.what) {
case 100: // API 28 以前直接接收
try {
// 获取ActivityClientRecord中的intent对象
Field intentField = msg.obj.getClass().getDeclaredField(“intent”);
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(msg.obj);
// 拿到插件的Intent
Intent intent = proxyIntent.getParcelableExtra(“oldIntent”);
// 替换回来
proxyIntent.setComponent(intent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
break;
case 159: // API 28 以后加入了 lifecycle, 这里msg发生了变化
try {
Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField(“mActivityCallbacks”);
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < mActivityCallbacks.size(); i++) {
Class<?> itemClass = mActivityCallbacks.get(i).getClass();
if (itemClass.getName().equals(“android.app.servertransaction.LaunchActivityItem”)) {
Field intentField = itemClass.getDeclaredField(“mIntent”);
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(mActivityCallbacks.get(i));
Intent intent = proxyIntent.getParcelableExtra(“oldIntent”);
proxyIntent.setComponent(intent.getComponent());
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 这里必须返回false
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
这样就完成了不同版本API的适配
三 、AppCompatActivity
细心的同学发现,插件的Activity是继承自了Activity,如果想用AppCompatActivity的话是会报错的:
可以看到,错误定位到了AppCompatDelegateImpl.java的第753行,我们点进去看一下:
这里就不点进去看了,在这可以看到加载了一个资源文件,我们知道,资源文件在编译器会生成一个静态常量,在宿主中这个文件生成了一个静态常量,在插件中这个布局文件的常量发生了变化,这时候还是使用宿主中的常量,就会产生冲突,这个问题怎么解决呢?
我们只需要在插件中,去加载自己的资源就可以了。下面我们来修改一下之前的资源加载的代码。
1. 在插件中创建资源加载器
object LoadUtils {
private var mResources: Resources? = null
/*
只有mResources为空时才创建
*/
fun getResources(context: Context): Resources {
if (mResources == null) {
mResources = loadAsset(context)
}
return mResources!!
}
private 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
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
希望大家在今年一切顺利,进到自己想进的公司,共勉!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
mg-aqPsrBlY-1712890290340)]
[外链图片转存中…(img-7S3acGBX-1712890290340)]
[外链图片转存中…(img-PygWlq9u-1712890290341)]
[外链图片转存中…(img-EJcHvPid-1712890290341)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-mw4q3QwB-1712890290341)]
最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
[外链图片转存中…(img-25jeI9lh-1712890290342)]
希望大家在今年一切顺利,进到自己想进的公司,共勉!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-flHc078m-1712890290342)]