zoukankan      html  css  js  c++  java
  • FrameWork内核解析之XMS内核管理(一)上篇

    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680
    本篇文章将继续从以下两个内容来介绍XMS内核管理之AMS:

    • [Activity的管理]
    • [ Android插件化开发之运行未安装apk的activity]

    一、Activity的管理

    1、ActivityRecord是Activity管理的最小单位,它对应着一个用户界面;
    2、TaskRecord也是一个栈式管理结构,每一个TaskRecord都可能存在一个或多个ActivityRecord,栈顶的ActivityRecord表示当前可见的界面;
    3、ActivityStack是一个栈式管理结构,每一个ActivityStack都可能存在一个或多个TaskRecord,栈顶的TaskRecord表示当前可见的任务;
    4、ActivityStackSupervisor管理着多个ActivityStack,但当前只会有一个获取焦点(Focused)的ActivityStack;
    5、ProcessRecord记录着属于一个进程的所有ActivityRecord,运行在不同TaskRecord中的ActivityRecord可能是属于同一个 ProcessRecord。

     
    7719409-382b9dfc9fdac222.png
     
     
    7719409-f4b47d971caf45b2.png
     

    二、Android插件化开发之运行未安装apk的activity

    2.1、介绍

    我们知道PathClassLoader是一个应用的默认加载器(而且他只能加载data/app/xxx.apk的文件),但是我们加载插件一般使用DexClassLoader加载器,所以这里就有问题了,其实如果对于开始的时候,每个人都会认为很简单,很容易想到使用DexClassLoader来加载Activity获取到class对象,在使用Intent启动

    2.2、替换LoadApk里面的mClassLoader

    我们知道我们可以将我们使用的DexClassLoader加载器绑定到系统加载Activity的类加载器上就可以了,这个是我们的思路。也是最重要的突破点。下面我们就来通过源码看看如何找到加载Activity的类加载器。加载Activity的时候,有一个很重要的类:LoadedApk.Java,这个类是负责加载一个Apk程序的,我们可以看一下他的源码:

     
    19956127-bb55570fcb0451af.png
     

    我们知道内部有个mClassLoader成员变量,我们只需要获取它就可以了,因为它不是静态的,所以我们需要先获取LoadApk这个类的对象,我们再去看看ActivityThread.java这个类。

     
    19956127-165ce469cf1c7c0d.png
     


    我们可以发现ActivityThread里面有个静态的成员变量sCurrentActivityThread,然后还有一个ArrayMap存放Apk包名和LoadedApk映射关系的数据结构,我们通过反射来获取mClassLoader对象。

    如果对ActivityThread.java这个类不熟悉的可以看我这篇博客,http://blog.csdn.net/u011068702/article/details/53207039 (Android插件化开发之AMS与应用程序(客户端ActivityThread、Instrumentation、Activity)通信模型分析)非常重要,是app程序的入口处。

    2.3、实现具体代码

    1)我们先需要一个测试apk,然后把这个测试的test.apk,放到手机sdcard里面去,关键代码如下

    package com.example.testapkdemo;
     
    import android.os.Bundle;
    import android.support.v7.app.ActionBarActivity;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Toast;
     
    public class MainActivity extends ActionBarActivity {
        
        public static View parentView;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            if (parentView == null) {
                setContentView(R.layout.activity_main);
            } else {
                setContentView(parentView);
            }
            findViewById(R.id.button).setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, "我是来自插件", Toast.LENGTH_SHORT).show();
                }
            });
        }
        
        public void setView(View view) {
            this.parentView = view;
        }
    }
    

    效果图如下:

     
    19956127-a531d351d01946d7.png
    image.png

    接下来是我宿主代码:

    ReflectHelper.java  这个是的我的反射帮助类
    
    
    package com.example.dexclassloaderactivity;
     
     
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Arrays;
     
    /**
     * 反射辅助函数
     * @author
     *
     */
    public class ReflectHelper {
        public static final Class<?>[] PARAM_TYPE_VOID = new Class<?>[]{};
     
        public static Object invokeStaticMethod(String className, String methodName, Class<?>[] paramTypes, Object...params) {
            try {
                Class<?> clazz = Class.forName(className);
                Method method = clazz.getMethod(methodName, paramTypes);
                method.setAccessible(true);
                return method.invoke(null, params);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
        
        public static Object invokeMethod(String className, String methodName, Class<?>[] paramTypes, Object obj, Object...params) {
            try {
                Class<?> clazz = Class.forName(className);
                Method method = clazz.getMethod(methodName, paramTypes);
                method.setAccessible(true);
                return method.invoke(obj, params);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
     
        public static Object getStaticField(String className, String fieldName) {
            try {
                Class<?> clazz = Class.forName(className);
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
        
        public static Object getField(String className, String fieldName, Object obj) {
            try {
                Class<?> clazz = Class.forName(className);
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
        
        public static void setStaticField(String className, String fieldName, Object value) {
            try {
                Class<?> clazz = Class.forName(className);
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(null, value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        public static void setField(String className, String fieldName, Object obj, Object value) {
            try {
                Class<?> clazz = Class.forName(className);
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(obj, value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
     
        public static Object createInstance(String className, Class<?>[] paramTypes, Object...params) {
            Object object = null;
            try {
                Class<?> cls = Class.forName(className);
                Constructor<?> constructor = cls.getConstructor(paramTypes);
                constructor.setAccessible(true);
                object = constructor.newInstance(params);
            }catch (Exception e) {
                e.printStackTrace();
            }
            return object;
        }
     
        /**
         * Locates a given field anywhere in the class inheritance hierarchy.
         *
         * @param instance an object to search the field into.
         * @param name field name
         * @return a field object
         * @throws NoSuchFieldException if the field cannot be located
         */
        public static Field findField(Object instance, String name) throws NoSuchFieldException {
            for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
                try {
                    Field field = clazz.getDeclaredField(name);
     
     
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
     
                    return field;
                } catch (NoSuchFieldException e) {
                    // ignore and search next
                }
            }
     
            throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
        }
     
        /**
         * Locates a given field anywhere in the class inheritance hierarchy.
         *
         * @param  cls to search the field into.
         * @param name field name
         * @return a field object
         * @throws NoSuchFieldException if the field cannot be located
         */
        public static Field findField2(Class<?> cls, String name) throws NoSuchFieldException {
            Class<?> clazz = null;
            for (clazz = cls; clazz != null; clazz = clazz.getSuperclass()) {
                try {
                    Field field = clazz.getDeclaredField(name);
     
     
                    if (!field.isAccessible()) {
                        field.setAccessible(true);
                    }
     
                    return field;
                } catch (NoSuchFieldException e) {
                    // ignore and search next
                }
            }
     
            throw new NoSuchFieldException("Field " + name + " not found in " + clazz);
        }
     
        /**
         * Locates a given method anywhere in the class inheritance hierarchy.
         *
         * @param instance an object to search the method into.
         * @param name method name
         * @param parameterTypes method parameter types
         * @return a method object
         * @throws NoSuchMethodException if the method cannot be located
         */
        public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
                throws NoSuchMethodException {
            for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
                try {
                    Method method = clazz.getDeclaredMethod(name, parameterTypes);
     
     
                    if (!method.isAccessible()) {
                        method.setAccessible(true);
                    }
     
                    return method;
                } catch (NoSuchMethodException e) {
                    // ignore and search next
                }
            }
     
            throw new NoSuchMethodException("Method " + name + " with parameters " +
                    Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
        }
    }
    

    MyApplication.java 这个类用实现替换mClassLoader

    package com.example.dexclassloaderactivity;
     
    import java.io.File;
    import java.lang.ref.WeakReference;
     
    import dalvik.system.DexClassLoader;
    import android.annotation.SuppressLint;
    import android.app.Application;
    import android.os.Environment;
    import android.util.ArrayMap;
    import android.util.Log;
     
    public class MyApplication extends Application{
        
        public static final String TAG = "MyApplication";
        public static final String AppName = "test.apk";
        public static int i = 0;
        
        public static DexClassLoader mClassLoader;
        
        @Override
        public void onCreate() {
            Log.d(TAG, "替换之前系统的classLoader");
            showClassLoader();
            try {
                String cachePath = this.getCacheDir().getAbsolutePath();
                String apkPath = /*Environment.getExternalStorageState() + File.separator*/"/sdcard/"+ AppName;
                mClassLoader = new DexClassLoader(apkPath, cachePath,cachePath, getClassLoader()); 
                loadApkClassLoader(mClassLoader);
            } catch (Exception e) {
                e.printStackTrace();
            }
            Log.d(TAG, "替换之后系统的classLoader");
            showClassLoader();
        }
        
     
        @SuppressLint("NewApi")
        public void loadApkClassLoader(DexClassLoader loader) {
            try {
                Object currentActivityThread  = ReflectHelper.invokeMethod("android.app.ActivityThread", "currentActivityThread", new Class[] {},new Object[] {});
                String packageName = this.getPackageName();
                ArrayMap mpackages = (ArrayMap) ReflectHelper.getField("android.app.ActivityThread", "mPackages", currentActivityThread);
                WeakReference wr= (WeakReference)mpackages.get(packageName);
                Log.e(TAG, "mClassLoader:" + wr.get());  
                ReflectHelper.setField("android.app.LoadedApk", "mClassLoader", wr.get(), loader);
                Log.e(TAG, "load:" + loader);  
            } catch (Exception e) {
                Log.e(TAG, "load apk classloader error:" + Log.getStackTraceString(e));  
            }
        }
        
        
        /**
         * 打印系统的classLoader
         */
        public void showClassLoader() {
            ClassLoader classLoader = getClassLoader();
            if (classLoader != null){
                Log.i(TAG, "[onCreate] classLoader " + i + " : " + classLoader.toString());
                while (classLoader.getParent()!=null){
                    classLoader = classLoader.getParent();
                    Log.i(TAG,"[onCreate] classLoader " + i + " : " + classLoader.toString());
                    i++;
                }
            }
        }
    }
    

    然后就是MainActivity.java文件,里面包含了下面另外一种方式,打开activity,所以我把函数 inject(DexClassLoader loader)先注释掉。

    package com.example.dexclassloaderactivity;
     
    import java.io.File;
    import java.lang.reflect.Array;
    import java.lang.reflect.Field;
     
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.Environment;
    import android.support.v7.app.ActionBarActivity;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.TextView;
    import dalvik.system.DexClassLoader;
    import dalvik.system.PathClassLoader;
     
    public class MainActivity extends ActionBarActivity{
     
        public static final String TAG = "MainActivity";
        public static final String AppName = "test.apk";
        public static DexClassLoader mDexClassLoader = null;
        public static final String APPName = "test.apk";
        public TextView mText;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            findViewById(R.id.text2).setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View v) {
                    try {
    //                   inject(MyApplication.mClassLoader);
                         String apkPath = Environment.getExternalStorageDirectory().toString() + File.separator + APPName;
                         Class clazz = MyApplication.mClassLoader.loadClass("com.example.testapkdemo.MainActivity");
                         Intent intent = new Intent(MainActivity.this, clazz);
                         startActivity(intent);
                         finish();
                    } catch (Exception e) {
                        Log.e(TAG, "name:" + Log.getStackTraceString(e));
                    }
                }
            });
        }
        
        
        private void inject(DexClassLoader loader){  
          
            PathClassLoader pathLoader = (PathClassLoader) getClassLoader();  
            try {  
                Object dexElements = combineArray(  
                        getDexElements(getPathList(pathLoader)),  
                        getDexElements(getPathList(loader)));  
                Object pathList = getPathList(pathLoader);  
                setField(pathList, pathList.getClass(), "dexElements", dexElements);  
            } catch (IllegalArgumentException e) {  
                e.printStackTrace();  
            } catch (NoSuchFieldException e) {  
                e.printStackTrace();  
            } catch (IllegalAccessException e) {  
                e.printStackTrace();  
            } catch (ClassNotFoundException e) {  
                e.printStackTrace();  
            }  
        }  
          
        private static Object getPathList(Object baseDexClassLoader)  
                throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  
            ClassLoader bc = (ClassLoader)baseDexClassLoader;  
            return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");  
        }  
          
        private static Object getField(Object obj, Class<?> cl, String field)  
                throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {  
            Field localField = cl.getDeclaredField(field);  
            localField.setAccessible(true);  
            return localField.get(obj);  
        }  
          
        private static Object getDexElements(Object paramObject)  
                throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {  
            return getField(paramObject, paramObject.getClass(), "dexElements");  
        }  
        private static void setField(Object obj, Class<?> cl, String field,  
                Object value) throws NoSuchFieldException,  
                IllegalArgumentException, IllegalAccessException {  
          
            Field localField = cl.getDeclaredField(field);  
            localField.setAccessible(true);  
            localField.set(obj, value);  
        }  
          
        private static Object combineArray(Object arrayLhs, Object arrayRhs) {  
            Class<?> localClass = arrayLhs.getClass().getComponentType();  
            int i = Array.getLength(arrayLhs);  
            int j = i + Array.getLength(arrayRhs);  
            Object result = Array.newInstance(localClass, j);  
            for (int k = 0; k < j; ++k) {  
                if (k < i) {  
                    Array.set(result, k, Array.get(arrayLhs, k));  
                } else {  
                    Array.set(result, k, Array.get(arrayRhs, k - i));  
                }  
            }   
            return result;  
        }    
    }
    

    这里一定要注意,我们犯了3个错,

    1、test.apk的路径写错了,下次写文件路径的时候,我们应该需要加上File file = new File(path); 用file.exist()函数来判断是否存在

    2、从sdcard卡里面读取test.apk,没加上权限, <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    3、写了Application,忘了在AndroidManifest.xml文件里面声明。

    我们还需要注意要加上开启activity 在AndroidManifest.xml里面注册,切记,希望下次不要再次犯错。

    AndroidManifest.xml文件如下:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.dexclassloaderactivity"
        android:versionCode="1"
        android:versionName="1.0" >
     
        <uses-sdk
            android:minSdkVersion="11"
            android:targetSdkVersion="21" />
        
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     
        <application
            android:name="com.example.dexclassloaderactivity.MyApplication"
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name=".MainActivity"
                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="com.example.testapkdemo.MainActivity">  
            </activity> 
        </application>
     
    </manifest>
    

    运行结果如下:

     
    19956127-81a3f90ce925ef34.png
     

    然后这里又出现了插件资源和宿主资源冲突问题,我们后面再来研究。

    4、合并PathClassLoader和DexClassLoader中的dexElements数组
    我们首先来看一下PathClassLoader和DexClassLoader类加载器的父类BaseDexClassloader的源码:

    (这里需要注意的是PathClassLoader和DexClassLoader类的父加载器是BaseClassLoader,他们的父类是BaseDexClassLoader)

     
    19956127-15fca924a732c48f.png
     

    这里有一个DexPathList对象,在来看一下DexPathList.java源码:

     
    19956127-8abcf72fd4023934.png
     


    Elements数组,我们看到这个变量他是专门存放加载的dex文件的路径的,系统默认的类加载器是PathClassLoader,本身一个程序加载之后会释放一个dex出来,这时候会将dex路径放到里面,当然DexClassLoader也是一样的,那么我们会想到,我们是否可以将DexClassLoader中的dexElements和PathClassLoader中的dexElements进行合并,然后在设置给PathClassLoader中呢?这也是一个思路。我们来看代码:

    /**
     * 以下是一种方式实现的
     * @param loader
     */
    private void inject(DexClassLoader loader){
        PathClassLoader pathLoader = (PathClassLoader) getClassLoader();
     
        try {
            Object dexElements = combineArray(
                    getDexElements(getPathList(pathLoader)),
                    getDexElements(getPathList(loader)));
            Object pathList = getPathList(pathLoader);
            setField(pathList, pathList.getClass(), "dexElements", dexElements);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
     
    private static Object getPathList(Object baseDexClassLoader)
            throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        ClassLoader bc = (ClassLoader)baseDexClassLoader;
        return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }
     
    private static Object getField(Object obj, Class<?> cl, String field)
            throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field localField = cl.getDeclaredField(field);
        localField.setAccessible(true);
        return localField.get(obj);
    }
     
    private static Object getDexElements(Object paramObject)
            throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
        return getField(paramObject, paramObject.getClass(), "dexElements");
    }
    private static void setField(Object obj, Class<?> cl, String field,
            Object value) throws NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
     
        Field localField = cl.getDeclaredField(field);
        localField.setAccessible(true);
        localField.set(obj, value);
    }
     
    private static Object combineArray(Object arrayLhs, Object arrayRhs) {
        Class<?> localClass = arrayLhs.getClass().getComponentType();
        int i = Array.getLength(arrayLhs);
        int j = i + Array.getLength(arrayRhs);
        Object result = Array.newInstance(localClass, j);
        for (int k = 0; k < j; ++k) {
            if (k < i) {
                Array.set(result, k, Array.get(arrayLhs, k));
            } else {
                Array.set(result, k, Array.get(arrayRhs, k - i));
            }
        }
        return result;
    }
    

    然后运行的时候把MyApplication.java文件里面的函数loadApkClassLoader(mClassLoader);注释掉,然后把MainActivity.java文件里面的inject(MyApplication.mClassLoader)不要注释,运行效果一样。
    阿里P7Android高级架构进阶视频免费学习请点击:https://space.bilibili.com/474380680
    参考https://blog.csdn.net/u011068702/article/details/53523609
    https://www.jianshu.com/p/0bbcffbc7796

  • 相关阅读:
    洛谷 P1941 飞扬的小鸟
    洛谷P2464 [SDOJ2008]郁闷的小J
    [cogs2314][HZOI 2015] Persistable Editor
    [vijos1067]Warcraft III 守望者的烦恼
    【vijos1049】送给圣诞夜的礼品
    [cogs347]地震
    gcc 编译多个源文件
    2_兔子产仔问题
    1_鸡兔同笼问题
    LeetCode(61) Rotate List
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11958352.html
Copyright © 2011-2022 走看看