美文网首页
插件化 -- DroidPlugin -- 2

插件化 -- DroidPlugin -- 2

作者: TomyZhang | 来源:发表于2019-10-28 21:09 被阅读0次

广播的管理(三星C7100 8.1.0 OS)

//插件模块广播接收器类
public class PluginReceiver1 extends BroadcastReceiver {
    private static final String TAG = "PluginReceiver1";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "zwm, onReceive action: " + intent.getAction());
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("PLUGIN_BROADCAST_ACTION");
        Log.d(TAG, "zwm, register PluginReceiver2");
        context.registerReceiver(new PluginReceiver2(), intentFilter); //PluginReceiver2采用动态注册
    }
}

public class PluginReceiver2 extends BroadcastReceiver {
    private static final String TAG = "PluginReceiver2";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "zwm, onReceive action: " + intent.getAction());
    }
}

//插件模块manifest内容
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tomorrow.plugin">
    <application
        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>
        <receiver android:name=".PluginReceiver1"> //PluginReceiver1采用静态注册
            <intent-filter>
                <action android:name="PLUGIN_BROADCAST_ACTION" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

//主项目模块MainActivity
public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private static final String ACTION = "PLUGIN_BROADCAST_ACTION";
    public Map<ActivityInfo, List<? extends IntentFilter>> mCache = new HashMap<ActivityInfo, List<? extends IntentFilter>>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, onCreate");
        Button btn = new Button(this);
        btn.setText("Send broadcast to plugin");
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "zwm, onClick, sendBroadcast action: " + ACTION);
                sendBroadcast(new Intent(ACTION));
            }
        });
        setContentView(btn);

        extractAssets("plugin-debug.apk"); //加载assets目录下的插件APK
        File testPlugin = getFileStreamPath("plugin-debug.apk");
        Log.d(TAG, "zwm, file path: " + testPlugin.getPath());
        try {
            preLoadReceiver(testPlugin); //解析插件manifest内容,将插件模块静态注册的广播接收器在主项目模块进行动态注册
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "zwm, preLoadReceiver error: " + e.getMessage());
        }
    }

    private void extractAssets(String sourceName) {
        AssetManager am = getAssets();
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            is = am.open(sourceName);
            File extractFile = getFileStreamPath(sourceName);
            fos = new FileOutputStream(extractFile);
            byte[] buffer = new byte[1024];
            int count;
            while ((count = is.read(buffer)) > 0) {
                fos.write(buffer, 0, count);
            }
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeSilently(is);
            closeSilently(fos);
        }
    }

    private void closeSilently(Closeable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (Throwable e) {
            // ignore
        }
    }

    private void preLoadReceiver(File apk) throws Exception {
        Log.d(TAG, "zwm, preLoadReceiver");
        parserReceivers(apk);
        ClassLoader classLoader = null;
        for (ActivityInfo activityInfo : mCache.keySet()) {
            Log.i(TAG, "zwm, plugin preload receiver:" + activityInfo.name);
            List<? extends IntentFilter> intentFilters = mCache.get(activityInfo);
            if (classLoader == null) {
                classLoader = new DexClassLoader(apk.getPath(), null, null, getClassLoader()); //创建类加载器
                Log.d(TAG, "zwm, classLoader: " + classLoader);
            }

            // 把插件模块解析出来的每一个静态广播接收器都在主项目模块动态注册
            for (IntentFilter intentFilter : intentFilters) {
                BroadcastReceiver receiver = (BroadcastReceiver) classLoader.loadClass(activityInfo.name).newInstance();
                registerReceiver(receiver, intentFilter);
            }
        }
        Log.d(TAG, "zwm, preLoadReceiver end");
    }

    private void parserReceivers(File apkFile) throws Exception {
        Log.d(TAG, "zwm, parserReceivers");
        Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
        Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
        Object packageParser = packageParserClass.newInstance();

        //首先调用parsePackage获取到apk对象对应的Package对象
        Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_RECEIVERS);

        //读取Package对象里面的receivers字段,注意这是一个 List<Activity> (底层把<receiver>当作<activity>处理)
        //接下来要做的就是根据这个List<Activity>获取到Receiver对应的ActivityInfo (依然是把receiver信息用activity处理了)
        Field receiversField = packageObj.getClass().getDeclaredField("receivers");
        List receivers = (List) receiversField.get(packageObj);

        Class<?> packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity");
        Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
        Class<?> userHandler = Class.forName("android.os.UserHandle");
        Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
        int userId = (Integer) getCallingUserIdMethod.invoke(null);
        Object defaultUserState = packageUserStateClass.newInstance();

        Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
        Field intentsField = componentClass.getDeclaredField("intents");

        //需要调用android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int)
        Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo",
                packageParser$ActivityClass, int.class, packageUserStateClass, int.class);

        // 解析出 receiver以及对应的 intentFilter
        for (Object receiver : receivers) {
            ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, receiver, 0, defaultUserState, userId);
            List<? extends IntentFilter> filters = (List<? extends IntentFilter>) intentsField.get(receiver);
            mCache.put(info, filters);
        }
        Log.d(TAG, "zwm, parserReceivers end");
    }
}

//输出log
2019-10-25 19:11:43.891 D/MainActivity: zwm, onCreate
2019-10-25 19:11:44.060 D/MainActivity: zwm, file path: /data/user/0/com.weishu.upf.receiver_management.app/files/plugin-debug.apk
2019-10-25 19:11:44.060 D/MainActivity: zwm, preLoadReceiver
2019-10-25 19:11:44.060 D/MainActivity: zwm, parserReceivers
2019-10-25 19:11:44.075 D/MainActivity: zwm, parserReceivers end
2019-10-25 19:11:44.076 I/MainActivity: zwm, plugin preload receiver:com.tomorrow.plugin.PluginReceiver1
2019-10-25 19:11:44.087 D/MainActivity: zwm, classLoader: dalvik.system.DexClassLoader[DexPathList[[zip file "/data/user/0/com.weishu.upf.receiver_management.app/files/plugin-debug.apk"],nativeLibraryDirectories=[/system/lib, /system/vendor/lib]]]
2019-10-25 19:11:44.090 D/MainActivity: zwm, preLoadReceiver end
2019-10-25 19:11:45.743 D/MainActivity: zwm, onClick, sendBroadcast action: PLUGIN_BROADCAST_ACTION
2019-10-25 19:11:45.754 D/PluginReceiver1: zwm, onReceive action: PLUGIN_BROADCAST_ACTION
2019-10-25 19:11:45.754 D/PluginReceiver1: zwm, register PluginReceiver2
2019-10-25 19:11:51.129 D/MainActivity: zwm, onClick, sendBroadcast action: PLUGIN_BROADCAST_ACTION
2019-10-25 19:11:51.144 D/PluginReceiver1: zwm, onReceive action: PLUGIN_BROADCAST_ACTION
2019-10-25 19:11:51.144 D/PluginReceiver1: zwm, register PluginReceiver2
2019-10-25 19:11:51.147 D/PluginReceiver2: zwm, onReceive action: PLUGIN_BROADCAST_ACTION

Service的插件化(三星C7100 8.1.0 OS 以下代码未测试成功!

//AndroidManifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.weishu.upf.service_management.app">
    <application
            android:allowBackup="true"
            android:name="com.weishu.upf.service_management.app.UPFApplication"
            android:label="@string/app_name"
            android:icon="@mipmap/ic_launcher">
        <activity android:name="com.weishu.upf.service_management.app.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service
                android:name="com.weishu.upf.service_management.app.ProxyService"
                android:process=".plugin01"/> //多进程
    </application>
</manifest>

//UPFApplication
public class UPFApplication extends Application {
    private static final String TAG = "UPFApplication";
    private static Context sContext;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Log.d(TAG, "zwm, attachBaseContext");
        sContext = base;
        try {
            //拦截startService等操作,使用代理Service欺騙AMS
            hookActivityManagerNative();
            //启动真正的Service
            hookActivityThreadHandler();

            extractAssets("plugin-debug.apk");
            File apkFile = getFileStreamPath("plugin-debug.apk");
            File odexFile = getFileStreamPath("plugin-debug.odex");
            Log.d(TAG, "zwm, file exist: " + apkFile.exists());

            //Hook ClassLoader, 让插件中的类能够被成功加载
            patchClassLoader(getClassLoader(), apkFile, odexFile);
        } catch (Exception e) {
            Log.e(TAG, "zwm, error: " + e.getMessage());
            throw new RuntimeException("hook failed");
        }
    }

    public static Context getContext() {
        return sContext;
    }

    private void extractAssets(String sourceName) {
        AssetManager am = getAssets();
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            is = am.open(sourceName);
            File extractFile = getFileStreamPath(sourceName);
            fos = new FileOutputStream(extractFile);
            byte[] buffer = new byte[1024];
            int count;
            while ((count = is.read(buffer)) > 0) {
                fos.write(buffer, 0, count);
            }
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeSilently(is);
            closeSilently(fos);
        }
    }

    private void closeSilently(Closeable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (Throwable e) {
            // ignore
        }
    }

    private void hookActivityManagerNative() throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
        Log.d(TAG, "zwm, hookActivityManagerNative");
        Field gDefaultField;
        if (Build.VERSION.SDK_INT >= 26) {
            Class<?> activityManager = Class.forName("android.app.ActivityManager");
            gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
        }else{
            Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
            gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
        }
        gDefaultField.setAccessible(true);
        Object gDefault = gDefaultField.get(null);

        //gDefault是一个android.util.Singleton对象,取出这个单例里面的字段
        Class<?> singleton = Class.forName("android.util.Singleton");
        Field mInstanceField = singleton.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);

        //ActivityManagerNative的gDefault对象里面原始的IActivityManager对象
        Object rawIActivityManager = mInstanceField.get(gDefault);

        //创建动态代理对象
        Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
        mInstanceField.set(gDefault, proxy);
        Log.d(TAG, "zwm, hookActivityManagerNative end");
    }

    private static class IActivityManagerHandler implements InvocationHandler {
        private static final String TAG = "IActivityManagerHandler";
        Object mBase;

        public IActivityManagerHandler(Object base) {
            mBase = base;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("startService".equals(method.getName())) {
                Log.d(TAG, "zwm, method: " + method.getName());
                //找到参数里面的第一个Intent对象
                int index = 0;
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                        break;
                    }
                }
                Log.d(TAG, "zwm, rawIntent: " + args[index]);
                // 代理Service的包名, 也就是我们自己的包名
                String stubPackage = "com.weishu.upf.service_management.app";
                // 这里我们把启动的Service替换为ProxyService, 让ProxyService接收生命周期回调
                ComponentName componentName = new ComponentName(stubPackage, ProxyService.class.getName());
                Intent newIntent = new Intent();
                newIntent.setComponent(componentName);

                //替换掉Intent,达到欺骗AMS的目的
                args[index] = newIntent;
                Log.d(TAG, "zwm, newIntent: " + newIntent);

                Log.v(TAG, "hook method startService success");
                return method.invoke(mBase, args);
            }
            return method.invoke(mBase, args);
        }
    }

    public static void hookActivityThreadHandler() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Log.d(TAG, "zwm, hookActivityThreadHandler");
        //先获取到当前的ActivityThread对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        currentActivityThreadField.setAccessible(true);
        Object currentActivityThread = currentActivityThreadField.get(null);

        //由于ActivityThread一个进程只有一个,我们获取这个对象的mH
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Handler mH = (Handler) mHField.get(currentActivityThread);

        Field mCallBackField = Handler.class.getDeclaredField("mCallback");
        mCallBackField.setAccessible(true);
        mCallBackField.set(mH, new ActivityThreadHandlerCallback(mH));
        Log.d(TAG, "zwm, hookActivityThreadHandler end");
    }

    private static class ActivityThreadHandlerCallback implements Handler.Callback {
        private static final String TAG = "HandlerCallback";
        Handler mBase;

        public ActivityThreadHandlerCallback(Handler base) {
            Log.d(TAG, "zwm, ActivityThreadHandlerCallback base: " + base);
            mBase = base;
        }

        @Override
        public boolean handleMessage(Message msg) {
            Log.d(TAG, "zwm, ActivityThreadHandlerCallback msg.what: " + msg.what);
            switch (msg.what) {
                //ActivityThread里面"CREATE_SERVICE"这个字段的值是114
                //本来使用反射的方式获取最好,这里为了简便直接使用硬编码
                case 114:
                    handleCreateService(msg);
                    break;
            }
            mBase.handleMessage(msg);
            return true;
        }

        private void handleCreateService(Message msg) {
            Log.d(TAG, "zwm, handleCreateService msg.what: " + msg.what);
            //这里简单起见直接取出插件Service
            Object obj = msg.obj;
            try {
                //把替身恢复成真身
                Field infoField = obj.getClass().getDeclaredField("info");
                infoField.setAccessible(true);
                ServiceInfo serviceInfo = (ServiceInfo) infoField.get(obj);
                serviceInfo.name = "com.tomorrow.plugin.PluginService1";
                Log.d(TAG, "zwm, handleCreateService success");
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    private void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
            throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Log.d(TAG,"zwm, hook ClassLoader");
        //获取BaseDexClassLoader : pathList
        Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathListObj = pathListField.get(cl);

        //获取PathList: Element[] dexElements
        Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");
        dexElementArray.setAccessible(true);
        Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);

        //Element类型
        Class<?> elementClass = dexElements.getClass().getComponentType();

        //创建一个数组, 用来替换原始的数组
        Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

        //构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile)这个构造函数
        Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
        //DexFile dexFile = DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0);
        //Object object = constructor.newInstance(apkFile, false, apkFile, dexFile);
        Object object = constructor.newInstance(apkFile, false, null, null);

        Object[] toAddElementArray = new Object[] { object };
        //把原始的elements复制进去
        System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
        //插件的那个element复制进去
        System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);

        //替换
        dexElementArray.set(pathListObj, newElements);
        Log.d(TAG,"zwm, hook ClassLoader end");
    }
}

//MainActivity
public class MainActivity extends Activity implements View.OnClickListener {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "zwm, onCreate");
        setContentView(R.layout.main);
        findViewById(R.id.startService).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.startService:
                Log.d(TAG, "zwm, onClick startService");
                Intent intent = new Intent();
                intent.setComponent(new ComponentName("com.tomorrow.plugin", "com.tomorrow.plugin.PluginService1"));
                startService(intent);
                break;
        }
    }
}

//ProxyService
public class ProxyService extends Service {

    private static final String TAG = "ProxyService";

    @Override
    public void onCreate() {
        Log.d(TAG, "zwm, onCreate() called");
        super.onCreate();
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Log.d(TAG, "zwm, onStart() called with " + "intent = [" + intent + "], startId = [" + startId + "]");
        super.onStart(intent, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "zwm, onDestroy() called");
        super.onDestroy();
    }
}

//主项目模块assets目录下的插件模块plugin-debug.apk中的PluginService1:
public class PluginService1 extends Service {
    private static final String TAG = "PluginService1";

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "zwm, onCreate");
    }
}

//输出log
10-28 13:38:09.275 D/UPFApplication: zwm, attachBaseContext
10-28 13:38:09.275 D/UPFApplication: zwm, hookActivityManagerNative
10-28 13:38:09.325 D/UPFApplication: zwm, hookActivityManagerNative end
10-28 13:38:09.325 D/UPFApplication: zwm, hookActivityThreadHandler
10-28 13:38:09.335 D/HandlerCallback: zwm, ActivityThreadHandlerCallback base: Handler (android.app.ActivityThread$H) {317bfe60}
10-28 13:38:09.335 D/UPFApplication: zwm, hookActivityThreadHandler end
10-28 13:38:09.375 D/UPFApplication: zwm, file exist: true
10-28 13:38:09.375 D/UPFApplication: zwm, hook ClassLoader
10-28 13:38:09.375 D/UPFApplication: zwm, hook ClassLoader end
10-28 13:38:09.375 D/HandlerCallback: zwm, ActivityThreadHandlerCallback msg.what: 100
10-28 13:38:09.395 D/MainActivity: zwm, onCreate
10-28 13:38:09.565 D/HandlerCallback: zwm, ActivityThreadHandlerCallback msg.what: 1002
10-28 13:38:09.745 D/HandlerCallback: zwm, ActivityThreadHandlerCallback msg.what: 149
10-28 13:38:09.775 D/HandlerCallback: zwm, ActivityThreadHandlerCallback msg.what: 246
10-28 13:38:14.055 D/MainActivity: zwm, onClick startService
10-28 13:38:14.055 D/IActivityManagerHandler: zwm, method: startService
10-28 13:38:14.065 D/IActivityManagerHandler: zwm, rawIntent: Intent { cmp=com.tomorrow.plugin/.PluginService1 }
10-28 13:38:14.065 D/IActivityManagerHandler: zwm, newIntent: Intent { cmp=com.weishu.upf.service_management.app/.ProxyService }
10-28 13:38:14.205 27566-27566/.plugin01 D/UPFApplication: zwm, attachBaseContext //多进程
10-28 13:38:14.205 27566-27566/.plugin01 D/UPFApplication: zwm, hookActivityManagerNative
10-28 13:38:14.265 27566-27566/.plugin01 D/UPFApplication: zwm, hookActivityManagerNative end
10-28 13:38:14.265 27566-27566/.plugin01 D/UPFApplication: zwm, hookActivityThreadHandler
10-28 13:38:14.265 27566-27566/.plugin01 D/HandlerCallback: zwm, ActivityThreadHandlerCallback base: Handler (android.app.ActivityThread$H) {317bfe60}
10-28 13:38:14.265 27566-27566/.plugin01 D/UPFApplication: zwm, hookActivityThreadHandler end
10-28 13:38:14.315 27566-27566/.plugin01 D/UPFApplication: zwm, file exist: true
10-28 13:38:14.325 27566-27566/.plugin01 D/UPFApplication: zwm, hook ClassLoader
10-28 13:38:14.325 27566-27566/.plugin01 D/UPFApplication: zwm, hook ClassLoader end
10-28 13:38:14.325 27566-27566/.plugin01 D/HandlerCallback: zwm, ActivityThreadHandlerCallback msg.what: 114
10-28 13:38:14.325 27566-27566/.plugin01 D/HandlerCallback: zwm, handleCreateService msg.what: 114
10-28 13:38:14.325 27566-27566/.plugin01 D/HandlerCallback: zwm, handleCreateService success

10-28 13:38:14.325 27566-27566/.plugin01 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: .plugin01, PID: 27566
    java.lang.RuntimeException: Unable to instantiate service com.tomorrow.plugin.PluginService1: java.lang.ClassNotFoundException: Didn't find class "com.tomorrow.plugin.PluginService1" on path: DexPathList[[zip file "/data/app/com.weishu.upf.service_management.app-2/base.apk", dex file "null"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
        at android.app.ActivityThread.handleCreateService(ActivityThread.java:3265)
        at android.app.ActivityThread.access$1900(ActivityThread.java:181)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1565)
        at com.weishu.upf.service_management.app.UPFApplication$ActivityThreadHandlerCallback.handleMessage(UPFApplication.java:204)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:145)
        at android.app.ActivityThread.main(ActivityThread.java:6145)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "com.tomorrow.plugin.PluginService1" on path: DexPathList[[zip file "/data/app/com.weishu.upf.service_management.app-2/base.apk", dex file "null"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
        at android.app.ActivityThread.handleCreateService(ActivityThread.java:3262)
        at android.app.ActivityThread.access$1900(ActivityThread.java:181)?
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1565)?
        at com.weishu.upf.service_management.app.UPFApplication$ActivityThreadHandlerCallback.handleMessage(UPFApplication.java:204)?
        at android.os.Handler.dispatchMessage(Handler.java:98)?
        at android.os.Looper.loop(Looper.java:145)?
        at android.app.ActivityThread.main(ActivityThread.java:6145)?
        at java.lang.reflect.Method.invoke(Native Method)?
        at java.lang.reflect.Method.invoke(Method.java:372)?
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)?
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)?
        Suppressed: java.lang.ClassNotFoundException: com.tomorrow.plugin.PluginService1
        at java.lang.Class.classForName(Native Method)
        at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
        at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
                ... 12 more
     Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available

ContentProvider的插件化(三星C7100 8.1.0 OS 以下代码未测试成功!

//UPFApplication
/**
 * 一定要在Application,并且在attachBaseContext里面Hook,
 * 因为provider的初始化非常早,比Application的onCreate还要早,在别的地方hook都晚了。
 */
public class UPFApplication extends Application {
    private static final String TAG = "UPFApplication";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Log.d(TAG, "zwm, attachBaseContext");
        try {
            extractAssets("plugin-debug.apk");
            File apkFile = getFileStreamPath("plugin-debug.apk");
            File odexFile = getFileStreamPath("plugin-debug.dex");
            Log.d(TAG, "zwm, file exist: " + apkFile.exists());

            //Hook ClassLoader, 让插件中的类能够被成功加载
            patchClassLoader(getClassLoader(), apkFile, odexFile);
            installProviders(base, getFileStreamPath("plugin-debug.apk"));
        } catch (Exception e) {
            throw new RuntimeException("Hook fail", e);
        }
    }

    private void extractAssets(String sourceName) {
        AssetManager am = getAssets();
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            is = am.open(sourceName);
            File extractFile = getFileStreamPath(sourceName);
            fos = new FileOutputStream(extractFile);
            byte[] buffer = new byte[1024];
            int count;
            while ((count = is.read(buffer)) > 0) {
                fos.write(buffer, 0, count);
            }
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeSilently(is);
            closeSilently(fos);
        }
    }

    private void closeSilently(Closeable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (Throwable e) {
            // ignore
        }
    }

    private void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
            throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Log.d(TAG,"zwm, hook ClassLoader");
        //获取BaseDexClassLoader : pathList
        Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathListObj = pathListField.get(cl);

        //获取PathList: Element[] dexElements
        Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");
        dexElementArray.setAccessible(true);
        Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);

        //Element类型
        Class<?> elementClass = dexElements.getClass().getComponentType();

        //创建一个数组, 用来替换原始的数组
        Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

        //构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile)这个构造函数
        Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
        //DexFile dexFile = DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0);
        //Object object = constructor.newInstance(apkFile, false, apkFile, dexFile);
        Object object = constructor.newInstance(apkFile, false, null, null);

        Object[] toAddElementArray = new Object[] { object };
        //把原始的elements复制进去
        System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
        //插件的那个element复制进去
        System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);

        //替换
        dexElementArray.set(pathListObj, newElements);
        Log.d(TAG,"zwm, hook ClassLoader end");
    }

    private void installProviders(Context context, File apkFile) throws Exception {
        Log.d(TAG,"zwm, installProviders");
        List<ProviderInfo> providerInfos = parseProviders(apkFile);

        for (ProviderInfo providerInfo : providerInfos) {
            providerInfo.applicationInfo.packageName = context.getPackageName();
        }

        Log.d(TAG, "zwm, providerInfos: " + providerInfos);
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);
        Method installProvidersMethod = activityThreadClass.getDeclaredMethod("installContentProviders", Context.class, List.class);
        installProvidersMethod.setAccessible(true);
        installProvidersMethod.invoke(currentActivityThread, context, providerInfos);
        Log.d(TAG,"zwm, installProviders end");
    }

    private List<ProviderInfo> parseProviders(File apkFile) throws Exception {
        Log.d(TAG,"zwm, parseProviders");
        Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
        Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);

        Object packageParser = packageParserClass.newInstance();

        //首先调用parsePackage获取到apk对象对应的Package对象
        Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_PROVIDERS);

        //读取Package对象里面的services字段
        //接下来要做的就是根据这个List<Provider> 获取到Provider对应的ProviderInfo
        Field providersField = packageObj.getClass().getDeclaredField("providers");
        List providers = (List) providersField.get(packageObj);

        //调用generateProviderInfo 方法, 把PackageParser.Provider转换成ProviderInfo
        Class<?> packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider");
        Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
        Class<?> userHandler = Class.forName("android.os.UserHandle");
        Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
        int userId = (Integer) getCallingUserIdMethod.invoke(null);
        Object defaultUserState = packageUserStateClass.newInstance();

        //需要调用 android.content.pm.PackageParser#generateProviderInfo
        Method generateProviderInfo = packageParserClass.getDeclaredMethod("generateProviderInfo",
                packageParser$ProviderClass, int.class, packageUserStateClass, int.class);

        List<ProviderInfo> ret = new ArrayList<>();
        //解析出intent对应的Provider组件
        for (Object service : providers) {
            ProviderInfo info = (ProviderInfo) generateProviderInfo.invoke(packageParser, service, 0, defaultUserState, userId);
            ret.add(info);
        }

        Log.d(TAG,"zwm, parseProviders end");
        return ret;
    }
}

//MainActivity
public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private static Uri URI = Uri.parse("content://com.tomorrow.plugin.provider");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button query = new Button(this);
        query.setText("query");
        query.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "zwm, onClick query");
                getContentResolver().query(URI, null, null, null, null);
            }
        });

        Button insert = new Button(this);
        insert.setText("insert");
        insert.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "zwm, onClick insert");
                ContentValues values = new ContentValues();
                getContentResolver().insert(URI, values);
            }
        });

        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(query);
        layout.addView(insert);
        setContentView(layout);
    }
}

//主项目模块assets目录下的插件模块plugin-debug.apk中的AndroidManifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tomorrow.plugin">
    <application
        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>
        <provider
            android:authorities="com.tomorrow.plugin.provider"
            android:name=".PluginProvider1"
            android:process=":provider"
            android:exported="true"/>
    </application>
</manifest>

//主项目模块assets目录下的插件模块plugin-debug.apk中的PluginProvider1:
public class PluginProvider1 extends ContentProvider {
    private static final String TAG = "PluginProvider1";

    @Override
    public boolean onCreate() {
        Log.d(TAG, "zwm, onCreate");
        return false;
    }

    @Override
    public Cursor query(Uri uri,String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.d(TAG, "zwm, query");
        return null;
    }

    @Override
    public String getType(Uri uri) {
        Log.d(TAG, "zwm, getType");
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.d(TAG, "zwm, insert");
        return null;
    }

    @Override
    public int delete(Uri uri,String selection,  String[] selectionArgs) {
        Log.d(TAG, "zwm, delete");
        return 0;
    }

    @Override
    public int update(Uri uri,ContentValues values,String selection, String[] selectionArgs) {
        Log.d(TAG, "zwm, update");
        return 0;
    }
}

//输出log
2019-10-28 15:43:04.927 D/UPFApplication: zwm, attachBaseContext
2019-10-28 15:43:04.987 D/UPFApplication: zwm, file exist: true
2019-10-28 15:43:04.987 D/UPFApplication: zwm, hook ClassLoader
2019-10-28 15:43:04.988 D/UPFApplication: zwm, hook ClassLoader end
2019-10-28 15:43:04.989 D/UPFApplication: zwm, installProviders
2019-10-28 15:43:04.989 D/UPFApplication: zwm, parseProviders
2019-10-28 15:43:05.016 D/UPFApplication: zwm, parseProviders end
2019-10-28 15:43:05.016 D/UPFApplication: zwm, providerInfos: [ContentProviderInfo{name=com.tomorrow.plugin.provider className=com.tomorrow.plugin.PluginProvider1}]

Caused by: java.lang.RuntimeException: Unable to get provider com.tomorrow.plugin.PluginProvider1: java.lang.ClassNotFoundException: Didn't find class "com.tomorrow.plugin.PluginProvider1" on path: DexPathList[[zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/base.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_resources_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_0_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_1_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_2_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_3_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_4_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_5_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_6_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_7_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_8_apk.apk", zip file "/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/split_lib_slice_9_apk.apk", zip file "/data/user/0/com.example.weishu.contentprovider_management/files/plugin-debug.apk"],nativeLibraryDirectories=[/data/app/com.example.weishu.contentprovider_management-VlMhqsLSNp3bw7UQ9SReBA==/lib/arm, /system/lib, /system/vendor/lib]]
    at android.app.ActivityThread.installProvider(ActivityThread.java:6635)
    at android.app.ActivityThread.installContentProviders(ActivityThread.java:6187)
    at java.lang.reflect.Method.invoke(Native Method)?
    at com.example.weishu.contentprovider_management.UPFApplication.installProviders(UPFApplication.java:136)?
    at com.example.weishu.contentprovider_management.UPFApplication.attachBaseContext(UPFApplication.java:45)?
    at android.app.Application.attach(Application.java:213)?
    at android.app.Instrumentation.newApplication(Instrumentation.java:1111)?
    at android.app.Instrumentation.newApplication(Instrumentation.java:1095)?
    at android.app.LoadedApk.makeApplication(LoadedApk.java:1009)?
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6086)?
    at android.app.ActivityThread.-wrap1(Unknown Source:0)?
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1801)?
    at android.os.Handler.dispatchMessage(Handler.java:106)?
    at android.os.Looper.loop(Looper.java:164)?
    at android.app.ActivityThread.main(ActivityThread.java:7025)?
    at java.lang.reflect.Method.invoke(Native Method)?
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:441)?
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1408)?

二、原理

插件中组件如何启动(以Activity为例)

我们知道如果组件不在manifest中声明是无法启动的,但凡插件框架都会面临这个问题。DroidPlugin的解决方法是,启动坑位中定义的Activity,从而达到欺骗AMS的目的,当回到ActivityThread后再将坑位Activity替换成插件目标Activity。

DroidPlugin在manifest中预先定义各种组件,比如Activity的每种LaunchMode都各定义一个Activity节点、每种LaunchMode+声明为独立的进程名各定义一个Activity节点、每种LaunchMode+Theme为Dialog的各定义一个Activity节点等,当启动插件中的Activity时,需要获取这个Activity在插件manifest中的定义信息(LaunchMode、rocess、Theme等),然后找到与主项目模块中坑位信息匹配的Activity进行启动,那么启动的Activity就是坑位中实际存在的Activity,就达到了欺骗AMS的目的。

DroidPlugin主要hook系统接口IActivityManager的startActivity方法,通过拦截这个方法,从而截获intent,根据intent的信息来查找坑位并分配,并将本次要启动的intent中的Activity替换成坑位的,而目标要展示的Activity作为参数存储在了intent中。

插件中类如何加载(以Activity为例)

每个组件的Class都是通过LoadedApk中的ClassLoader来加载的。

Droidplugin拦截ActivityThread#handleLaunchActivity,通过反射修改ActivityClientRecord#intent将坑位替换成目标Activity,然后反射创建插件的LoadedApk,同时创建插件的DexClassloader,并将DexClassLoader注入到LoadedApk中,如果插件的Application没有创建,则通过反射调用LoadedApk#makeApplication创建Application并执行Applicaton#attah及Application#onCreate,那么Application和插件的LoadedApk进行了绑定。经过以上流程之后,在ActivityThread#performLaunchActivity被调用时使用的正是插件的LoadedApk#mClassLoader来加载class,这样就完成了加载插件class的流程。

插件中组件的Context如何与资源绑定(以Activity为例)

Droidplugin中每个插件都有自己的LoadedApk,在通过插件的LoadedApk创建完activity实例之后,会通过ContextImpl#createActivityContext来创建Activity的Context对象,在创建ContextImpl对象时就通过LoadedApk#getResources创建了Resource并且赋值给了ConetextImpl#mResource对象,然后调用Activity#attach方法将Context对象赋值给ContextWrapper#mBase(Activity继承于ContextWrapper),这样插件的Resource就与Activity绑定在了一起。这也是普通应用的Activity与Resource对象绑定的流程。

相关链接

Android插件化入门指南

相关文章

网友评论

      本文标题:插件化 -- DroidPlugin -- 2

      本文链接:https://www.haomeiwen.com/subject/ghrivctx.html