转载自:http://blog.csdn.net/yzzst/article/details/48093567
github地址为: https://github.com/Qihoo360/DroidPlugin
之前,我们也讨论过,一个Android的apk插件是个什么形式。主要是三点,我总结如下:
- DexLoader动态的加载dex文件进入vm
- AndroidManifest.xml中预注册一些将要使用的四大组件(方法很low,其实有更好的)
- 针对四大组件,分别进行抽象代理,proxy
- Apk安装还需要单独处理native的,so文件。
- 宿主需要处理好Resource,保证R文件的正确使用。
360这个Android插件系统是否和我们之前讨论的类似呢?这里我们从IPluginManagerImpl.java文件中,我们能够看到其真正的安装apk插件的方法——installPackage。具体方法详解如下所示:
代码块语法遵循标准markdown代码,例如:
/** * @param filepath 插件Apk的路径 * @param flags Flag * */
@Override
public int installPackage(String filepath, int flags) throws RemoteException {
//install plugin
String apkfile = null;
try {
// 验证合法性
PackageManager pm = mContext.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(filepath, 0);
if (info == null) {
return PackageManagerCompat.INSTALL_FAILED_INVALID_APK;
}
apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName);
if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) {
// 新安装的插件
// 停止,删除插件的缓存
forceStopPackage(info.packageName);
if (mPluginCache.containsKey(info.packageName)) {
deleteApplicationCacheFiles(info.packageName, null);
}
new File(apkfile).delete();
Utils.copyFile(filepath, apkfile);
PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
parser.collectCertificates(0);
PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
// 验证申请的permission,宿主中是否存在,不存在不让安装。
if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
for (String requestedPermission : pkgInfo.requestedPermissions) {
boolean b = false;
try {
b = pm.getPermissionInfo(requestedPermission, 0) != null;
} catch (NameNotFoundException e) {
}
if (!mHostRequestedPermission.contains(requestedPermission) && b) {
Log.e(TAG, "No Permission %s", requestedPermission);
new File(apkfile).delete();
return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
}
}
}
// 保存签名
saveSignatures(pkgInfo);
// if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
// for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
// Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
// }
// }
// 拷贝插件中的native库文件。
copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
// opt Dex文件,并使用DexClassLoader动态的载入类代码。
dexOpt(mContext, apkfile, parser);
mPluginCache.put(parser.getPackageName(), parser);
// 回调函数,通知插件安装
mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
sendInstalledBroadcast(info.packageName);
return PackageManagerCompat.INSTALL_SUCCEEDED;
} else {
// 以下是插件,覆盖安装的过程。即更新的过程。与新安装类似。不做解释了。
if (mPluginCache.containsKey(info.packageName)) {
return PackageManagerCompat.INSTALL_FAILED_ALREADY_EXISTS;
} else {
forceStopPackage(info.packageName);
new File(apkfile).delete();
Utils.copyFile(filepath, apkfile);
PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
parser.collectCertificates(0);
PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
for (String requestedPermission : pkgInfo.requestedPermissions) {
boolean b = false;
try {
b = pm.getPermissionInfo(requestedPermission, 0) != null;
} catch (NameNotFoundException e) {
}
if (!mHostRequestedPermission.contains(requestedPermission) && b) {
Log.e(TAG, "No Permission %s", requestedPermission);
new File(apkfile).delete();
return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
}
}
}
saveSignatures(pkgInfo);
// if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
// for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
// Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
// }
// }
copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
dexOpt(mContext, apkfile, parser);
mPluginCache.put(parser.getPackageName(), parser);
mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
sendInstalledBroadcast(info.packageName);
return PackageManagerCompat.INSTALL_SUCCEEDED;
}
}
} catch (Exception e) {
if (apkfile != null) {
new File(apkfile).delete();
}
handleException(e);
return PackageManagerCompat.INSTALL_FAILED_INTERNAL_ERROR;
}
}
以下是dexOpt方法的具体内容,简单的贴一下,不做说明了。
private void dexOpt(Context hostContext, String apkfile, PluginPackageParser parser) throws Exception {
String packageName = parser.getPackageName();
String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, packageName);
String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, packageName);
ClassLoader classloader = new PluginClassLoader(apkfile, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader());
// DexFile dexFile = DexFile.loadDex(apkfile, PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName()), 0);
// Log.e(TAG, "dexFile=%s,1=%s,2=%s", dexFile, DexFile.isDexOptNeeded(apkfile), DexFile.isDexOptNeeded(PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName())));
}
占坑——预注册四大组件
360 Android插件DroidPlugin中,主要的四大组件代理方式还是占坑。这里所说的占坑就是指,在AndroidManifest.xml中预注册以下可能会用到的信息。
ps.这个方式不是特别好。如果大家对为什么要预注册这个问题不是很了解,或者不了解为什么不好。可以看一下我的博客,我会持续更新。 http://blog.csdn.net/yzzst/article/details/45582315
OK,下面是我们从DroidPlugin的Test项目中,截取出来的AndroidManifest.xml代码,我们看看什么叫占坑,怎么占坑。代码如下所示:
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MyActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ServiceTest1"
android:label="服务测试" />
<activity
android:name=".ContentProviderTest"
android:label="ContentProvider测试" />
<activity
android:name=".BroadcastReceiverTest"
android:label="广播测试" />
<activity
android:name=".NotificationTest"
android:label="通知测试" />
<activity
android:name=".ServiceTest2"
android:label="多进程服务测试" />
<activity
android:name=".ContentProviderTest2"
android:label="多进程ContentProvider测试" />
<!-- 预声明不同process的Service -->
<service android:name=".Service1" />
<service android:name=".Service2" />
<service
android:name=".Service3"
android:process=":Process2" />
<service
android:name=".Service4"
android:process=":Process2" />
<!-- 预声明两种process的Provider -->
<provider
android:name=".MyContentProvider1"
android:authorities="com.example.ApiTest.MyContentProvider1" />
<provider
android:name=".MyContentProvider2"
android:authorities="com.example.ApiTest.MyContentProvider2"
android:process=":Process2" />
<receiver android:name=".StaticBroadcastReceiver" >
<intent-filter>
<action android:name="com.example.ApiTest.StaticBroadcastReceiver" />
</intent-filter>
</receiver>
<!-- 预声明各种launchMode的Activity -->
<activity
android:name=".StandardActivity"
android:label="@string/title_activity_standard" />
<activity
android:name=".SingleTopActivity"
android:label="@string/title_activity_single_top"
android:launchMode="singleTop" />
<activity
android:name=".SingleTaskActivity"
android:label="@string/title_activity_single_task"
android:launchMode="singleTask" />
<activity
android:name=".SingleInstanceActivity"
android:label="@string/title_activity_single_instance"
android:launchMode="singleInstance" />
<activity
android:name=".ActivityTestActivity"
android:label="@string/title_activity_activity_test" >
</activity>
</application>
Hook 系统API方法
DroidPlugin中最重要的就是,为了让插件中的方法能够正确的调用。DroidPlugin把所有的,方法都在内部Hook并替换掉了。
等等,为什么不能够正确的调用?
因为,这个是一个插件系统,Activity是虚拟Proxy出来的。四大组件,并不是真正的,即Context拿到不一定是真的。系统也不会有此类的回调。所以,很多单例都需要Context才能使用。
比如:InputMethodManager.getInstance(Context context);
这时,插件中的代码将不能够直接运行。当然,还有其他原因。
具体的替换和操作方法,我们能够在 HookFactory.java文件中installHook方法中,看到整个DroidPlugin的具体操作如下所示:
public final void installHook(Context context, ClassLoader classLoader) throws Throwable {
installHook(new IClipboardBinderHook(context), classLoader);
//for INotificationManager
installHook(new INotificationManagerBinderHook(context), classLoader);
installHook(new IMountServiceBinder(context), classLoader);
installHook(new IAudioServiceBinderHook(context), classLoader);
installHook(new IContentServiceBinderHook(context), classLoader);
installHook(new IWindowManagerBinderHook(context), classLoader);
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) {
installHook(new IGraphicsStatsBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
installHook(new IMediaRouterServiceBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
installHook(new ISessionManagerBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
installHook(new IWifiManagerBinderHook(context), classLoader);
}
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
installHook(new IInputMethodManagerBinderHook(context), classLoader);
}
installHook(new IPackageManagerHook(context), classLoader);
installHook(new IActivityManagerHook(context), classLoader);
installHook(new PluginCallbackHook(context), classLoader);
installHook(new InstrumentationHook(context), classLoader);
installHook(new LibCoreHook(context), classLoader);
installHook(new SQLiteDatabaseHook(context), classLoader);
}
Markdown Extra 定义列表语法:
Ok,说到最后,还是得称赞一下360。国内大公司开源的使用框架原来越少了。他们的这一举动确实是造福了目前的Android开发者。
但是,对于DroidPlugin来说,目前还存在一下几种不足和缺点:
无法在插件中发送具有自定义资源的Notification, 例如: a. 带自定义RemoteLayout的Notification b.图标通过R.drawable.XXX指定的通知(插件系统会自动将其转化为Bitmap)
无法在插件中注册一些具有特殊Intent 。Filter的Service、Activity、BroadcastReceiver、ContentProvider等组件以供Android系统、已经安装的其他APP调用。
对Activity的LaunchMode支持不够好,Activity Stack管理存在一定缺陷。Activity的onNewIntent函数可能不会被触发。 (此为BUG,未来会修复)
缺乏对Native层的Hook,对某些带native代码的apk支持不好,可能无法运行。比如一部分游戏(cocos2d、unity3d开发的游戏估计都用不了)无法当作插件运行。