原文地址:https://www.jianshu.com/p/5d4fc9cc12ac
1. 插件定义?
插件可以提供一种动态扩展的能力,让app在运行时候可以加载原本不属于该应用的功能,可以做到动态更新和替换;
2. 插件化定义?
把核心业务模块封装成独立的插件,根据不同业务需求进行不同组合,动态进行替换,可以对插件进行管理、更新;
3. 插件化架构主流框架?
1>:Small;
2>:DL动态加载框架;
3>:360的RePlugin;
3>:360的DroidPlugin;
4>:滴滴的VirtualAPK;
4. 插件化架构
插件化架构就是:点击一个 Button按钮,然后从服务器中下载一个 功能的apk,保存到本地,它是单独的一个apk并且是没有运行的,我们需要把它启动起来,并且做到参数传递。比如像早期的微信里边的一些功能:比如 摇一摇、漂流瓶、附近的人、等等其他功能,如下图所示:
插件化架构简介.png
这里我们为了演示方便,就直接 写一个 YaoYIYao的demo,然后运行把它打包,直接放到我们的手机存储目录中,就表示我们已经从服务器中下载了一个 摇一摇的apk,然后我们还需要做的就是:
1>:启动一个 Activity,这个 插件Activity是没有在 清单文件中注册的;
2>:
5. 拦截启动
进入 AndroidPluginDemo项目中,真正执行的类其实是 Singleton里边的 mInstance属性;
1>:获取 ActivityManagerNative里面的 gDefault;
2>:获取gDefault中的 mInstance属性;
3>:这个时候会报错,报错是因为TestActivity没有注册,这个时候我们重新写一个 ProxyActivity先占一个坑,待会就让 ProxyActivity代替 TestActivity去过检测,意思就是让 ProxyActivity在清单文件中代替 TestActivity注册,此时从MainActivity已经可以跳转过来到TestActivity;
4>:最后还需要换回来,hook ActivityManager里面的 mH是一个 Handler:
4.1>>:获取ActivityThread的实例;
4.2>>:获取ActivityThread中的mH;
4.3>>:hook 使用 handleLaunchActivity
流程就是:
从MainActivity中 点击跳转 然后跳转到 TestActivity界面,但是在 清单文件中不需要配置 TestActivity,直接使用hook启动流程 LaunchActivity即可:
效果如下图所示:
图片.png
图片.png
6. 代码如下
1>:MainActivity代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view){
Intent intent = new Intent(MainActivity.this , TestActivity.class) ;
startActivity(intent);
}
}
2>:TestActivity代码如下:
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/4/30 17:34
* Version 1.0
* Params:
* Description:
*/
public class TestActivity extends Activity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
3>:代理过安检的 ProxyActivity代码如下:
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/4/30 17:44
* Version 1.0
* Params:
* Description: 只是代理过检测的Activity
*/
public class ProxyActivity extends Activity{
}
4>:HookStartActivityUtil代码如下:
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/4/30 10:34
* Version 1.0
* Params:
* Description:
*
* 对于 源码中的某个类 如果现实 {hide},就表示只能系统去new ,如果自己我们想要创建对象,只能通过下边方式获取
* Class<?> amnClass = Class.forName("android.app.ActivityManagerNative") ; forName("该类最上边的包名 + 类名")
*/
public class HookStartActivityUtil {
private Context mContext ;
private Class<?> mProxyClass ;
private final String EXTER_ORIGIN_INTENT = "EXTER_ORIGIN_INTENT";
public HookStartActivityUtil(Context context , Class<?> proxyClass){
this.mContext = context.getApplicationContext() ; // 防止内存泄露
this.mProxyClass = proxyClass ;
}
public void hookLaunchActivity() throws Exception{
// 1:获取ActivityThread的实例;
Class<?> atClass = Class.forName("android.app.ActivityThread") ;
// 获取ActivityThread中的属性
Field scatThread = atClass.getDeclaredField("sCurrentActivityThread");
scatThread.setAccessible(true);
Object sCurrentActivityThread = scatThread.get(null) ; // 静态的可以传递 null
// 2:获取ActivityThread中的mH;
Field mHField = atClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mHandler = mHField.get(sCurrentActivityThread);
// 3:hook 使用 handleLaunchActivity
// 给handler设置 CallBack回调,也通过反射
Class<?> handlerClass = Class.forName("android.os.Handler") ;
Field mCallBackField = handlerClass.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mHandler , new HandlerCallBack());
}
private class HandlerCallBack implements Handler.Callback{
@Override
public boolean handleMessage(Message msg) {
// 每发一次消息,都会执行一次这个CallBack方法
if (msg.what == 100){ // 根据Handler源码可知
handleLaunchMessage(msg) ;
}
return false;
}
}
/**
* 开始启动创建Activity拦截
*/
private void handleLaunchMessage(Message msg) {
try {
Object record = msg.obj ;
// 1. 从ActivityClientRecord中获取过安检的 intent
Field intentField = record.getClass().getDeclaredField("intent") ;
intentField.setAccessible(true);
Intent safeIntent = (Intent) intentField.get(record);
// 2. 获取到过安检的intent之后 ,从safeIntent中获取原来的 originIntent
Intent originIntent = safeIntent.getParcelableExtra(EXTER_ORIGIN_INTENT) ;
// 3. 重新设置回去
if (originIntent != null){
intentField.set(record , originIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void hookStartActivity() throws Exception{
// 1>:获取 ActivityManagerNative里面的 gDefault;
Class<?> amnClass = Class.forName("android.app.ActivityManagerNative") ;
// 通过 ActivityManagerNative 类 获取 gDefault属性
Field gDefaultField = amnClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true); // 设置权限
Object gDefault = gDefaultField.get(null) ;
// 2>:获取gDefault中的 mInstance属性;
Class<?> singletonClass = Class.forName("android.util.Singleton") ;
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iamInstance = mInstanceField.get(gDefault);
Class<?> iamClass = Class.forName("android.app.IActivityManager") ;
iamInstance = Proxy.newProxyInstance(HookStartActivityUtil.class.getClassLoader(),
new Class[]{iamClass} ,
// InvocationHandler:必须有一个执行者,就是谁去执行这个方法
new StartActivityInvocationHandler(iamInstance)) ;
// 3>:重新指定
mInstanceField.set(gDefault , iamInstance);
}
private class StartActivityInvocationHandler implements InvocationHandler{
// 这个才是方法的执行者
private Object mObject ;
// 通过构造方法把mObject传递进来
public StartActivityInvocationHandler(Object object){
this.mObject = object ;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里可以 hook到 IActivityManager中所有的方法
Log.e("TAG" , method.getName()) ;
// 替换intent ,过AndroidManifest.xml 检测
if (method.getName().equals("startActivity")){
// 1. 首先获取原来的intent
Intent originIntent = (Intent) args[2];
// 2. 创建一个安全的intent
Intent safeIntent = new Intent(mContext , mProxyClass) ;
// 3. 替换第二个参数
args[2] = safeIntent ;
// 4. 绑定原来的Intent
safeIntent.putExtra(EXTER_ORIGIN_INTENT , originIntent) ;
}
return method.invoke(mObject , args);
}
}
}
5>:BaseApplication代码如下:
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/4/30 8:41
* Version 1.0
* Params:
* Description:
*/
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookStartActivityUtil hookStartActivityUtil = new HookStartActivityUtil(this , ProxyActivity.class) ;
try {
hookStartActivityUtil.hookStartActivity();
hookStartActivityUtil.hookLaunchActivity();
} catch (Exception e) {
e.printStackTrace();
}
}
}
6>:AndroidManifest.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.novate.plugin">
<application
android:name=".BaseApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ProxyActivity"/>
</application>
</manifest>
以上代码就可以实现不需要在 清单文件中注册 TestActivity,直接通过代理过安检的 ProxyActivity 就可以 从MainActivity 跳转到 TestActivity,但是有一个问题,上边的 TestActivity是继承 自Activity的,如果让 TestActivity继承 AppCompatActivity就会报错,下边就来解决这个问题。
7. 解决 TestActivity继承自 AppCompatActivity绕不过去的问题
ActivityThread源码中的 getPackageManager()方法如下:
图片.png
思路就是:
因为上边的 getPackageManager()方法是静态的方法,可以在 开始启动创建Activity拦截 handleLaunchActivity方法中先调用一次,这个时候 sPackageManager就会有实例,那么我再次进来就获取的是已经实例好的 sPackageManager,代码如下:
/**
* 兼容AppCompatActivity报错问题
*/
Class<?> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThread = field.get(null);
// 我自己执行一次那么就会创建PackageManager,系统再获取的时候就是下面的iPackageManager
Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
Object iPackageManager = getPackageManager.invoke(activityThread);
PackageManagerHandler handler = new PackageManagerHandler(iPackageManager);
Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iPackageManagerIntercept}, handler);
// 获取 sPackageManager 属性
Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
iPackageManagerField.setAccessible(true);
iPackageManagerField.set(activityThread, proxy);
class PackageManagerHandler implements InvocationHandler {
private Object mActivityManagerObject;
public PackageManagerHandler(Object iActivityManagerObject) {
this.mActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Log.e("TAG", "methodName = " + method.getName());
if (method.getName().startsWith("getActivityInfo")) {
ComponentName componentName = new ComponentName(mContext, mProxyClass);
args[0] = componentName;
}
return method.invoke(mActivityManagerObject, args);
}
}
下一节会讲解360开源框架DroidPlugin的使用介绍,代码会在下一节中上传至github。