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


相关文章
|
5天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
10天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
12天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
14天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
12天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
13天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
24 2
|
14天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
22天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
20天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
30 5
|
19天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!