//com.qihoo360.loader2.PmBase
final void callAttach() {
//获取ClassLoader
mClassLoader = PmBase.class.getClassLoader();
// 挂载
for (Plugin p : mPlugins.values()) {
p.attach(mContext, mClassLoader, mLocal);
}
// 加载默认插件
if (PluginManager.isPluginProcess()) {
//默认插件不为空
if (!TextUtils.isEmpty(mDefaultPluginName)) {
//获取插件
Plugin p = mPlugins.get(mDefaultPluginName);
if (p != null) {
boolean rc = p.load(Plugin.LOAD_APP, true);
if (!rc) {
}
if (rc) {
mDefaultPlugin = p;
mClient.init(p);
}
}
}
}
}
上一篇中我们hook住系统的ClassLoader之后就调用callAttach()方法加载默认插件。首先通过设置的插件名字从插件表中获取插件。是一个Plugin 对象,然后通过Plugin.load 实现加载。
Plugin.load
//com.qihoo360.loader2.Plugin
final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
//加载插件
boolean rc = loadLocked(load, useCache);
// 尝试在此处调用Application.onCreate方法
if (load == LOAD_APP && rc) {
callApp();
}
// 如果info改了,通知一下常驻
// 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新
if (rc && mInfo != info) {
UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
Tasks.post2Thread(task);
}
return rc;
}
1.这里通过调用loadLocked 方法来加载插件
//com.qihoo360.loader2.Plugin
private boolean loadLocked(int load, boolean useCache) {
//判断插件是否禁用
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;
String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName());
//创建进程锁
ProcessLocker lock = new ProcessLocker(context, lockFileName);
long t1 = System.currentTimeMillis();
//加载插件
boolean rc = doLoad(logTag, context, parent, manager, load);
//解锁
lock.unlock();
if (!rc) {
}
if (rc) {
try {
// 至此,该插件已开始运行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
}
return true;
}
再锁一次
lock = new ProcessLocker(context, lockFileName);
// 删除优化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();
} catch (IllegalArgumentException e2) {
e2.printStackTrace();
}
}
t1 = System.currentTimeMillis();
// 尝试再次加载该插件
rc = tryLoadAgain(logTag, context, parent, manager, load);
lock.unlock();
if (!rc) {
return false;
}
try {
// 至此,该插件已开始运行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.u.2: " + e.getMessage(), e);
}
}
return true;
}
2.在loadLocked方法中先判断插件是否被禁用,然后判断是否被加载过,加载过就返回,再判断缓存中有没有,有的话也返回,都没有就去加载,先创建进程锁,锁住进程加载插件过程,调用doLoad 方法加载,加载成功就调用PluginManagerProxy.addToRunningPluginsNoThrows方法添加插件到"当前进程的正在运行插件列表",并同步到Server端。如果失败就再加载一次插件。
//com.qihoo360.loader2.Plugin
private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
if (mLoader == null) {
// 试图释放文件
PluginInfo info = null;
//内置插件
if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
//判断是否为内置插件,如果是内置插件,在检查是否已经释放了so库到指定位置,如果没有先释放到指定位置,并重新构造PluginInfo
。。。。。。
//p-n 插件
} else if (mInfo.getType() == PluginInfo.TYPE_PN_JAR) {
//判断是否为p-n类型并且未执行安装插件步骤,如果是p-n类型并且未安装,先执行插件的安装操作,并重新构造PluginInfo
。。。。。。。
} else {
//
}
//
if (info != null) {
// 替换
mInfo = info;
}
//插件加载类
mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
//加载插件数据
if (!mLoader.loadDex(parent, load)) {
return false;
}
// 设置插件为“使用过的”
// 注意,需要重新获取当前的PluginInfo对象,而非使用“可能是新插件”的mInfo
try {
PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
} catch (RemoteException e) {
// 同步出现问题,但仍继续进行
if (LOGR) {
e.printStackTrace();
}
}
// 若需要加载Dex,则还同时需要初始化插件里的Entry对象
if (load == LOAD_APP) {
// NOTE Entry对象是可以在任何线程中被调用到
if (!loadEntryLocked(manager)) {
return false;
}
// NOTE 在此处调用则必须Post到UI,但此时有可能Activity已被加载
// 会出现Activity.onCreate比Application更早的情况,故应放在load外面立即调用
// callApp();
}
}
if (load == LOAD_INFO) {
return mLoader.isPackageInfoLoaded();
} else if (load == LOAD_RESOURCES) {
return mLoader.isResourcesLoaded();
} else if (load == LOAD_DEX) {
return mLoader.isDexLoaded();
} else {
return mLoader.isAppLoaded();
}
}
3.这里如果没有加载过,先判断插件类型,如果是内置插件判断so库等是否已经释放。没有的话就释放到指定的位置。如果是p-n插件然后创建加载类Loader 通过loadDex方法加载插件。加载成功后设置插件信息为 "使用过"。然后他通过callApp启动插件
//com.qihoo360.loader2.Loader;
final boolean loadDex(ClassLoader parent, int load) {
try {
//获取PackageManager
PackageManager pm = mContext.getPackageManager();
//查看缓存
mPackageInfo = Plugin.queryCachedPackageInfo(mPath);
if (mPackageInfo == null) {
// 缓存没有PackageInfo 创建一个
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;
}
//指定插件资源路径
mPackageInfo.applicationInfo.sourceDir = mPath;
mPackageInfo.applicationInfo.publicSourceDir = mPath;
// 添加针对SO库的加载
// 此属性最终用于ApplicationLoaders.getClassLoader,在创建PathClassLoader时成为其参数
// 这样findLibrary可不用覆写,即可直接实现SO的加载
PluginInfo pi = mPluginObj.mInfo;
File ld = pi.getNativeLibsDir();
mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
// 缓存表: pkgName -> pluginName
synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
}
// 缓存表: pluginName -> fileName
synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
}
// 缓存表: fileName -> PackageInfo
synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
}
}
。。。。。。
// 创建或获取ComponentList表
mComponents = Plugin.queryCachedComponentList(mPath);
if (mComponents == null) {
// ComponentList
mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
// 动态注册插件中声明的 receiver
regReceivers();
// 缓存表:ComponentList
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 {
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();
}
//获取缓存中插件的ClassLoader
mClassLoader = Plugin.queryCachedClassLoader(mPath);
//没有命中缓存
if (mClassLoader == null) {
// 先获取父类加载器
String out = mPluginObj.mInfo.getDexParentDir().getPath();
if (BuildConfig.DEBUG) {
parent = ClassLoader.getSystemClassLoader();
} else {
// 线上环境保持不变
parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
}
//设置so 文件路径
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
//创建插件自己的PluginDexClassLoader
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));
}
}
//加载dex在这里返回
if (load == Plugin.LOAD_DEX) {
return isDexLoaded();
}
//创建插件apk使用的Context对象
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
} catch (Throwable e) {
return false;
}
return true;
}
4.在loadDex方法中首先获取插件的mPackageInfo判断插件是否存在,并且指定插件apk的资源路径,包括so库的路径最后把pluginName mPath mPackageInfo 缓存起来。
然后创建ComponentList对象 这个对象用来快速获取四大组件和Application的系统Info的List 每个Plugin对象维护一份ComponentList,且在第一次加载PackageInfo时被生成。
/**
* Class类名 - Activity的Map表
*/
final HashMap<String, ActivityInfo> mActivities = new HashMap<>();
/**
* Class类名 - Provider的Map表
*/
final HashMap<String, ProviderInfo> mProvidersByName = new HashMap<>();
/**
* Authority - Provider的Map表
*/
final HashMap<String, ProviderInfo> mProvidersByAuthority = new HashMap<>();
/**
* Class类名 - Service的Map表
*/
final HashMap<String, ServiceInfo> mServices = new HashMap<>();
/**
* Application对象
*/
ApplicationInfo mApplication = null;
/**
* Class类名 - BroadcastReceiver的Map表
* 注意:是的,你没有看错,系统缓存Receiver就是用的ActivityInfo
*/
final HashMap<String, ActivityInfo> mReceivers = new HashMap<>();
ComponentList在创建的时候解析了AndroidManifest.xml文件获取四大组件信息生成组件与 IntentFilter 的对应关系并将这些信息和四大组件生成对应表关系并缓存,缓存插件apk的Application信息对象,接着动态注册插件apk中在AndroidManifest中声明的receiver,调整插件apk中四大组件的自定义进程名称为宿主坑位进程名称和Activity的TaskAffinity
之后先通过pm.getResourcesForApplication创建插件apk的要使用的Resources对象 然后创建插件自己的ClassLoader对象PathClassLoader。这里是PluginDexClassLoader 缓存这个ClassLoader对象
最后创建插件用的Context对象 。到这里加载插件的准备工作都已经做好了,该有的数据都有了,现在再看看第3步中loadEntryLocked方法
//com.qihoo360.loader2.Plugin;
private boolean loadEntryLocked(PluginCommImpl manager) {
if (mDummyPlugin) {
mLoader.mPlugin = new IPlugin() {
@Override
public IModule query(Class<? extends IModule> c) {
return null;
}
};
} else {
//尝试反射调用插件工程中Entry的create方法
if (mLoader.loadEntryMethod2()) {
//执行Entry的create方法
if (!mLoader.invoke2(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod(false)) {
if (!mLoader.invoke(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod3()) {
if (!mLoader.invoke2(manager)) {
return false;
}
} else {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.lel f " + mInfo.getName());
}
return false;
}
}
return true;
}
//com.qihoo360.loader2.Loader
final boolean loadEntryMethod3() {
try {
// PLUGIN_ENTRY_PACKAGE_PREFIX = "com.qihoo360.plugin";
//PLUGIN_ENTRY_CLASS_NAME = "Entry";
String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
Class<?> c = mClassLoader.loadClass(className);
//PLUGIN_ENTRY_EXPORT_METHOD_NAME = "create"
mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
} catch (Throwable e) {
}
return mCreateMethod2 != null;
}
//com.qihoo360.replugin.Entry
public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
// 初始化插件框架,就是反射主工程框架中的类或者方法,方便插件工程之后直接调用
RePluginFramework.init(cl);
// 初始化主工程传递过来的Context和ClassLoader
RePluginEnv.init(context, cl, manager);
//返回插件工程中的服务管理Binder对象
return new IPlugin.Stub() {
@Override
public IBinder query(String name) throws RemoteException {
return RePluginServiceManager.getInstance().getService(name);
}
};
}
5.这边通过反射调用插件工程中的Entry 的create 方法 并执行create 方法。这里create方法反射了主工程框架中的一些方法,让插件可以使用宿主的功能,接着就是缓存持有主工程传递过来的插件Context对象,这个Context是插件工程自己用的,还通过这个Context获取了宿主的Context对象,这个对象主要是为了用来获取一些宿主中的资源,反射类等一些信息的,还缓存了宿主的ClassLoader对象,也是用来方便反射宿主中的一些类的,最后返回了插件工程中管理服务的Binder对象。具体插件初始化过程会在之后详细介绍。
// 确保在UI线程中调用
private void callApp() {
if (Looper.myLooper() == Looper.getMainLooper()) {
callAppLocked();
} else {
// 确保一定在UI的最早消息处调用
mMainH.postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
callAppLocked();
}
});
}
}
6.在load完成之后调用callApp方法创建插件apk 的application 在UI线程中调用callAppLocked方法
private void callAppLocked() {
// 获取并调用Application的几个核心方法
if (!mDummyPlugin) {
// NOTE 不排除A的Application中调到了B,B又调回到A,或在同一插件内的onCreate开启Service/Activity,而内部逻辑又调用fetchContext并再次走到这里
// NOTE 因此需要对mApplicationClient做判断,确保永远只执行一次,无论是否成功
if (mApplicationClient != null) {
// 已经初始化过,无需再次处理
return;
}
//创建插件的application并包装成PluginApplicationClient返回
mApplicationClient = PluginApplicationClient.getOrCreate(
mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);
if (mApplicationClient != null) {
//调用Application的AttachBaseContext方法
mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
//调用Application的OnCreate方法
mApplicationClient.callOnCreate();
}
} else {
}
}
7 创建了插件的appliication 并包装成PluginApplicationClient 。然后执行插件application 的 attach和onCreate方法。到这里插件就加载完毕了,此时虽然没有插件的组件调用,但是插件的application已经启动了,也就是说其实插件已经启动了。
总结
到这里我们讲解了Replugin框架初始化的内容。包含的内容我总结起来画了一个简单的图。