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


相关文章
|
27天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
53 19
|
27天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
56 14
|
28天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
在数字时代,掌握安卓应用开发技能是进入IT行业的关键。本文将引导读者从零基础开始,逐步深入安卓开发的世界,通过实际案例和代码示例,展示如何构建自己的第一个安卓应用。我们将探讨基本概念、开发工具设置、用户界面设计、数据处理以及发布应用的全过程。无论你是编程新手还是有一定基础的开发者,这篇文章都将为你提供宝贵的知识和技能,帮助你在安卓开发的道路上迈出坚实的步伐。
32 5
|
27天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
28天前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
28天前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
28天前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
35 0
|
8月前
|
Android开发 开发者
Android Split APK介绍
【2月更文挑战第5天】
|
8月前
|
XML API Android开发
android S 上 安装apk出现android.os.FileUriExposedException
android S 上 安装apk出现android.os.FileUriExposedException
95 6
|
7月前
|
Android开发
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
366 0