Android插件化开发之运行未安装apk的activity(1)

简介: Android插件化开发之运行未安装apk的activity(1)

1、介绍

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


2、替换LoadApk里面的mClassLoader

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


1.png

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

看看ActivityThread.java这个类

1.png

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


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

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;
  }
}

接下来是我宿主代码:

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>

运行结果如下:

1.png


相关文章
|
4月前
|
Java API 开发工具
如何将python应用编译到android运行
【6月更文挑战第27天】本文介绍在Ubuntu 20上搭建Android开发环境,包括安装JRE/JDK,设置环境变量,添加i386架构,安装依赖和编译工具。并通过`p4a`命令行工具进行apk构建和清理。
67 6
如何将python应用编译到android运行
|
2月前
|
开发工具 Android开发
解决Android运行出现NDK at /Library/Android/sdk/ndk-bundle did not have a source.properties file
解决Android运行出现NDK at /Library/Android/sdk/ndk-bundle did not have a source.properties file
143 4
解决Android运行出现NDK at /Library/Android/sdk/ndk-bundle did not have a source.properties file
|
2月前
|
Java Android开发 Windows
使用keytool查看Android APK签名
本文介绍了如何使用Windows命令行工具和keytool查看APK的签名信息,并提供了使用AOSP环境中的signapk.jar工具对APK进行系统签名的方法。
80 0
使用keytool查看Android APK签名
|
2月前
|
Android开发
将AAB(Android App Bundle)转换为APK
将AAB(Android App Bundle)转换为APK
87 1
|
2月前
|
Android开发 开发者
Android、Flutter为不同的CPU架构包打包APK(v7a、v8a、x86)
Android、Flutter为不同的CPU架构包打包APK(v7a、v8a、x86)
87 1
|
2月前
|
Android开发
解决android apk安装后出现2个相同的应用图标
解决android apk安装后出现2个相同的应用图标
189 2
|
2月前
|
Android开发 iOS开发
[ionic]解决运行Android、IOS出现Could not find the web assets directory
[ionic]解决运行Android、IOS出现Could not find the web assets directory
28 1
|
4月前
|
前端开发 JavaScript 测试技术
|
3月前
|
Android开发
【亲测,安卓版】快速将网页网址打包成安卓app,一键将网页打包成app,免安装纯绿色版本,快速将网页网址打包成安卓apk
【亲测,安卓版】快速将网页网址打包成安卓app,一键将网页打包成app,免安装纯绿色版本,快速将网页网址打包成安卓apk
90 0
|
4月前
|
Android开发
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
172 0