Android实践:动态加载

本文介绍了Android中的动态加载机制,详细阐述了Java类加载过程,并探讨了Android特有的DexClassLoader和PatchClassLoader。通过示例展示了如何动态加载dex文件、未安装apk和已安装apk。在实践中,遇到的问题如错误处理和解决方案也被详细讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、动态加载简介
在讲解Android的动态加载之前,先给大家脑补一下Java的类加载机制,这样有利于大家对后面的知识的讲解:
  1.当我们调用Java命令运行某个Java程序时,该命令将会启动一个Java虚拟机进程,不管该Java程序有多复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里;
  2.当程序主动使用某个类时,如果该类还未加载到内存中,则系统会通过加载、连接、初始化3个步骤来对该类进行初始化;
    类加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序使用任何类时,系统都会为之创建一个java.lang.Class对象;
    类连接:负责把类的二进制数据合并到JRE中;
      验证:检验被加载的类是否有正确地内部结构,并和其他类协调一致;
      准备:为类的静态Field分配内存,并设置默认的初始值;
      解析:将类的二进制数据中的符号引用替换成直接引用;
    类初始化:负责对类进行初始化,主要就是对静态Field进行初始化;
      声明静态Field时指定初始值;
      使用静态初始化块为静态Field指定初始值;
  3.在Java中,一个类用其全限定类名(包括包名和类名)作为标识,但在JVM中,一个类使用其全限定类名和其加载器作为其唯一标识;
    当JVM启动的时候,会形成由3个类加载器组成的初始类加载器层次结构:
    Bootstrap ClassLoader:根类加载器,被称为引导(也称为原始或根)类加载器,负载加载Java的核心类;
    Extension ClassLoader:扩展类加载器,非常特殊,不是java.lang.Class的子类,由JVM自身实现的,负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ex或者和由java.ext.dirs系统属性指定的目录)中JAR包中的类;
    System ClassLoader:系统加载器,负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR和类路径;
  4.JVM类加载机制主要由如下3种:
    全盘负责,就是当一个类加载器负责加载某个class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器载入;
    父类委托,则是先让parent(父)类加载器视图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类;
    缓存机制,将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存区中不存在Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入到缓存区中。

那么,在Android也可以动态加载类,但是无法像Java那样在JVM中方便的使用ClassLoader动态加载Jar。因为Android的虚拟机(Dalvik VM)是不认识Java打出的jar的byte code,需要通过dx工具转换成Dalvik byte code才行。在当前Android中,如下API可以用于动态加载:
  1.DexClassLoader:可以加载jar/dex/apk,也可以从SD卡中加载;
  2.PatchClassLoader:只能加载已经安装到Android系统中的apk文件;
二、动态加载实践
在Android系统中,DexClassLoader分别支持对dex,未安装apk和已安装apk的动态加载,下面我们就依次进行实践;
1.创建动态加载的类Test.java;
Text.java
public class Test {
    //动态加载调用方法getText
    public String getText() {
        return "load dex";
    }
}
2.将Test.java-(javac)->Test.class-(jar);
D:\javatest1>javac Test.java
D:\javatest1>jar cvf test.jar Test.class
标明清单(manifest)
增加:Test.class(读入= 274) (写出= 212)(压缩了 22%)
3.下载dx工具,解压至AndroidSDK/platform-tools/目录;

4.将test.jar-(dx)->dex.jar;
D:\DevelopTools\AndroidSdk\platforms>dx --dex --output=dex.jar test.jar

5.将生成的dex.jar包上传到SD卡根目录下,运行Apk执行动态加载;

到这里,我们就完成了动态加载dex文件的一切准备工作。下面我们开始准备未安装apk的准备工作;
6.创建动态加载apk的android项目LoadeUnInstallApk,在MainActivity中创建动态调用方法getText();

MainActivity.java
public class MainActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    //动态加载方法getText
    public String getText(){
        return "load install apk";
    }
}
7.运行gradle build构建,在app/build/outputs/apk目录下找到生成的apk,并使用命令adb push上传到SD卡根目录下;
C:\Users\chengxiang.peng.QUNARSERVERS>adb push Desktop\app-debug.apk /storage/sdcard0
[100%] /storage/sdcard0/app-debug.apk
OK,这里我也就完成了动态加载未安装apk的准备工作。下面开始动态加载已安装apk项目的准备。
8. 创建动态加载已安装Apk项目LoadInstallApk,项目如下:
strings.xml
<resources>
    <string name="app_name">LoadInstallApk</string>
    <!--提供string,给宿主app获取调用-->
    <string name="load_install_apk_string">Load InstallApk</string>
</resources>
MainActivity.java保持不变
构建并安装该项目后,动态加载已安装apk项目完毕。
9.创建宿主项目中,创建动态加载类ClassLoaderActivity.java(别忘了在清单文件中添加SD卡访问权限);

activity_classloader.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_class_loader"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.qunar.home.ClassLoaderActivity">
    <!--动态加载dex文件按钮-->
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="loadDex"
        android:text="loaddex" />
    <!--动态加载未安装apk按钮-->
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="loadUninstallApk"
        android:text="loaduninstallapk" />
    <!--动态加载已安装apk按钮-->
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="loadInstallApk"
        android:text="loadinstallapk" />
    <!--调用动态加载的方法,获取的文案展示-->
    <TextView
        android:id="@+id/textview1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
ClassLoaderActivity.java
public class ClassLoaderActivity extends AppCompatActivity {
    private TextView textView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_classloader);
        textView = (TextView) findViewById(R.id.textview1);
    }


    public void loadDex(View view) {
        try {
            //动态从SD卡中的dex.jar文件中,加载Test.java类
            File inDexFile = new File(Environment.getExternalStorageDirectory().toString() + File.separator + "dex.jar");
            File outDexFile = this.getDir("dex", Context.MODE_PRIVATE);
            DexClassLoader dexClassLoader = new DexClassLoader(inDexFile.getAbsolutePath(), outDexFile.getAbsolutePath(), null, getClassLoader());
            Class testClass = dexClassLoader.loadClass("Test");


            //创建Test.java类对象,调用getText方法获取字符串,显示在TextView中
            Object object = testClass.newInstance();
            Method method = testClass.getDeclaredMethod("getText");
            String text = (String) method.invoke(object);
            textView.setText(text);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    public void loadUninstallApk(View view) {
        try {
            //动态从SD卡中的app-debug.apk文件中,加载MainActivity.java类
            File dexFile = new File(Environment.getExternalStorageDirectory().toString() + File.separator + "app-debug.apk");
            File optimizedFile = this.getDir("dex", Context.MODE_PRIVATE);
            DexClassLoader dexClassLoader = new DexClassLoader(dexFile.getAbsolutePath(), optimizedFile.getAbsolutePath(), null, getClassLoader());
            Class testClass = dexClassLoader.loadClass("com.example.chengxiangpeng.loaduninstallapk.MainActivity");
           
            //创建MainActivit.java类对象,调用getText方法获取字符串,显示在TextView中
            Constructor constructor = testClass.getConstructor(new Class[]{});
            Object object = constructor.newInstance(new Object[]{});
            Method method = testClass.getDeclaredMethod("getText");
            String text = (String) method.invoke(object);
            textView.setText(text);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }


    public void loadInstallApk(View view) {
        try {
            //获取动态加载已安装apk的上下文对象,Resources对象
            String packageName = "com.example.chengxiangpeng.loadinstallapk";
            Context context = createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
           
            //获取相关资源如字符串
            Resources resources = context.getResources();
            String string = resources.getString(resources.getIdentifier("load_install_apk_string", "string", packageName));
            Toast.makeText(this,string,Toast.LENGTH_SHORT).show();


            //动态加载MainActivit,并启动MainActivity
            Class aClass = context.getClassLoader().loadClass(packageName + ".MainActivity");
            startActivity(new Intent(context, aClass));
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
10.运行宿主app,分别点击LOADDEX,LOADUNINSTALLAPK和LOADINSTALLAPK按钮,效果如下:
  
提示1:在使用dx工具生成dex的时候,报错如下:
D:\javatest1>dx --dex --output=dex.dex test.jar
trouble processing:
bad class file magic (cafebabe) or version (0034.0000)
...while parsing Test.class
...while processing Test.class
1 warning
no classfiles specified
处理1:你使用的java版本是否是1.6,如果不是改为1.6版本;

提示2:运行app的时候动态加载是,如果出现如下报错:
ava.lang.RuntimeException: Unable to start activity ComponentInfo{com.qunar.home/com.qunar.home.ClassLoaderActivity}: java.lang.IllegalArgumentException: Optimized data directory /storage/sdcard0 is not owned by the current user. Shared storage cannot protect your application from code injection attacks.
处理2:DexClassLoader不运行在SD中缓存优化的class,故检查下你的outDexFile是否为SD卡,如果是改为this.getDir("dex", Context.MODE_PRIVATE)即可;

提示3:在动态加载未安装apk时,如果报错如下:
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
     at dalvik.system.DexFile.defineClass(Native Method)
     at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:211)
     at dalvik.system.DexPathList.findClass(DexPathList.java:313)
     at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:51)
处理3:在宿主项目和动态加载app的项目中,是否共同引用了com.android.support:appcompat-v7:23.4.0包或其它包,造成加载相同的类冲突。将gradle scepe改为provided,或者移除项目中调用v7包的类如AppCompatActivity:

新技术,新未来!欢迎大家关注“1024工场”微信服务号,时刻关注我们的最新的技术讯息!(甭客气!尽情的扫描或者长按!)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值