zoukankan      html  css  js  c++  java
  • 【Android】Android动态加载Jar、APK的实现

    本文介绍Android中动态加载Jar、APK的实现。而主要用到的就是DexClassLoader这个类。大家都知道Android和普通的Java虚拟机有差别,它只能加载经过处理的dex文件。而加载这个dex文件可以通过DexClassLoader 和 PathClassLoader 两个类来实现这个方法。然而PathClassLoader只能加载已经安装到Android系统中的apk文件。接下来,会介绍在Android中如何动态加载Jar、如何加载未安装的APK,如何加载已经安装的APK。

    1.Android如何动态加载Jar

    动态加载Jar主要是用于在APP的热更新、插件化开发方面,在进行加载之前,首先需要生成Jar文件。测试的jar包定义了一个接口和一个实现类。需要注意定义接口的步骤是必不可少的,在后面利用反射加载的时候就要利用到这个接口。

    定义ILoader接口:

    package com.example.interf;
    
    public interface ILoader {
        public String sayHi();
    }
    ILoader.java

    定义JarLoader类:

    package com.example.interf;
    
    public class JarLoader implements ILoader {
        @Override
        public String sayHi() {
            return "来自动态加载的Jar";
        }
    }
    JarLoader.java

    然后打包为Jar文件

    这里笔者导出为Loader.jar文件。有一点需要注意,就是不要把ILoader.jar接口打包进去,因为后期可能会包错:  java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation ,出现这个错误的原因就是打包的Jar中有接口,然后在下面的文件中又定义了接口,出现了两个接口文件,所以报错。如果这里把ILoader接口打包进去,那么在下面的测试中就不要再定义相同的ILoader接口了。

    到这里我们就把Jar文件打包成功了,接下来了需要把这个Jar文件用dx工具进行处理,dx工具在Android SDK 的tools中已经提供了,一般在android-SDK/build-tools目录下。

    将上面的Loader.jar文件拷贝一份到dx同级的目录下,然后执行如下命令:

    dx --dex --output=Loader_dex.jar Loader.jar  

     然后将生成的Loader_dex.jar文件,拷贝到手机的SD根目录下面(手机SD的根目录就是:/storage/emulated/0,读者也可以使用 Environment.getExternalStorageDirectory() 查看)

    接下来就可以使用如下的代码进行加载:

    其中ILoader.java接口与Loader.jar中的ILoader接口保持一直。

    package com.example.test;
    
    import java.io.File;
    
    import com.example.interf.ILoader;
    
    import dalvik.system.DexClassLoader;
    
    import android.os.Bundle;
    import android.os.Environment;
    import android.app.Activity;
    import android.util.Log;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            loadJar();
        }
        
        /**
         * @Title LoadJar
         * @Description  项目工程中必须定义接口(包名都要一致), 而被引入的第三方jar包实现这些接口,然后进行动态加载 。 
         *                 相当于第三方按照接口协议来开发, 使得第三方应用可以以插件的形式动态加载到应用平台中。 
         * @return void
         */
        private void loadJar(){
            File dexoutputdir = getDir("dex1",0);
            String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Loader_dex.jar";
            DexClassLoader loader = new DexClassLoader(dexPath,dexoutputdir.getAbsolutePath(),null,getClassLoader());
            
            try {
                Class clz = loader.loadClass("com.example.interf.JarLoader");
                ILoader iShowToast = (ILoader) clz.newInstance();
                Toast.makeText(this,iShowToast.sayHi(),Toast.LENGTH_LONG).show();
            } catch (Exception e){
                 Log.d("dd",e.toString());
            }
        }  
    }
    MainActivity.java

    在这个类中定义的核心方法是loadJar()

        private void loadJar(){
            File dexoutputdir = getDir("dex1",0);
            String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Loader_dex.jar";
            DexClassLoader loader = new DexClassLoader(dexPath,dexoutputdir.getAbsolutePath(),null,getClassLoader());
            
            try {
                Class clz = loader.loadClass("com.example.interf.JarLoader");
                ILoader iShowToast = (ILoader) clz.newInstance();
                Toast.makeText(this,iShowToast.sayHi(),Toast.LENGTH_LONG).show();
            } catch (Exception e){
                 Log.d("dd",e.toString());
            }
        }  

    接下来笔者解释一下上面这个方法中核心类DexClassLoader。

    此处需要注意DexClassLoader的四个参数:
    参数1 dexPath:待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限( <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ),否则会报与报错,Android4.4 KitKat及以后的版本需要此权限,之前的版本不需要该权限。

    参数2 optimizedDirectory:解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写(安全性考虑),所以只能放在data/data下。本文getDir(“dex1”, 0)会在/data/data/**package/下创建一个名叫”app_dex1“的文件夹,其内存放的文件是自动生成Loader_dex.dex;需要注意,data/data文件夹只有在手机root之后,才看得到。

    参数3 libraryPath:指向包含本地库(so)的文件夹路径,可以设为null。


    参数4 parent:父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()取到。

    效果图:

    到这里动态加载Jar就结束了。笔者接下来总结一下思路,首先把jar文件经过dx工具处理,然后把处理后的文件放到手机的SD根目录下面,然后利用反射加载调用方法。上面其实只是实现热更新的一半,加载的Jar文件完全可以从服务器下载手机后,然后再在手机端加载,这样可以对手机上的APP进行实时的更新以及防止反编译。

    2.如何加载未安装的APK

     上面介绍了如何动态加载jar文件,接下来介绍如何加载未安装的APK。

    首先新建一个Android项目:

    定义一个接口ISayHello.java

    package com.example.loaduninstallapkdemo;
    
    public interface ISayHello {
          public String sayHello();
    }
    ISayHello.java

    然后新建Activity,实现ISayHello接口:

    package com.example.loaduninstallapkdemo;
    
    import android.os.Bundle;
    import android.app.Activity;
    
    public class MainActivity extends Activity implements ISayHello{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @Override
        public String sayHello() {
            return "Hello, this apk is not installed";
        }
    }
    MainActivity.java

    然后把该工程的APK拷贝到手机的SD根目录下面,

    接下来就可以使用如下的代码进行动态加载了,下面的加载过程和上面的类似,只是不再需要定义接口了。

    package com.example.test;
    
    import java.io.File;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    
    import android.os.Bundle;
    import android.os.Environment;
    import android.app.Activity;
    import android.view.Menu;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            loadUnInstallAPK();
        }
        
        private void loadUnInstallAPK(){
               String path = Environment.getExternalStorageDirectory() + File.separator;  
               String filename = "UninstallApkActivity.apk";  
          
               // 4.1以后不能够将optimizedDirectory设置到sd卡目录, 否则抛出异常.  
               File optimizedDirectoryFile = getDir("dex", 0) ;  
               DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(),  
                                                                null, getClassLoader());  
          
               try {  
                // 通过反射机制调用, 包名为com.example.loaduninstallapkdemo, 类名为MainActivity  
                   Class mLoadClass = classLoader.loadClass("com.example.loaduninstallapkdemo.MainActivity");  
                   Constructor constructor = mLoadClass.getConstructor(new Class[] {});  
                   Object testActivity = constructor.newInstance(new Object[] {});  
                     
                   // 获取sayHello方法  
                   Method helloMethod = mLoadClass.getMethod("sayHello", null);  
                   helloMethod.setAccessible(true);  
                   Object content = helloMethod.invoke(testActivity, null);  
                   Toast.makeText(MainActivity.this, content.toString(), Toast.LENGTH_LONG).show();  
                     
               } catch (Exception e) {  
                   e.printStackTrace();  
               }  
        }
    }
    MainActivity.java

    效果图:

    3.如何加载已经安装的APK

     在介绍了如何动态加载jar,加载未安装的APK后,接下来介绍如何加载已经安装的APK,

    首先将制作一个简单的APK,然后把它安装的手机上面。

    package com.example.installapkdemo;
    
    import android.os.Bundle;
    import android.app.Activity;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    MainActivity.java

     

    将该APK安装到手机后,接下来就可以进行加载了。

    同样和加载未安装的APK类似,项目中也不需要定义接口。

    package com.example.test;
    
    import android.os.Bundle;
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.content.pm.PackageManager.NameNotFoundException;
    import android.content.res.Resources;
    import android.util.Log;
    import android.view.Menu;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            loadInstalledApk();
        }
        private void loadInstalledApk(){
            try {  
                String pkgName = "com.example.installapkdemo";  
                Context context = createPackageContext(pkgName,  
                        Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE) ;  
                  
                // 获取动态加载得到的资源  
                Resources resources = context.getResources() ;  
                // 获取该apk中的字符串资源"hello_world", 并且toast出来,apk换肤的实现就是这种原理  
                String toast = resources.getString(resources.getIdentifier("hello_world", "string", pkgName) ) ;  
                Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show() ;  
                  
                Class cls = context.getClassLoader().loadClass(pkgName + ".MainActivity");  
                // 跳转到该Activity  
                startActivity(new Intent(context, cls)) ;  
            } catch (NameNotFoundException e) {  
                e.printStackTrace();  
            }catch (ClassNotFoundException e) {  
                Log.d("", e.toString()) ;  
            }  
        }
    }
    MainActivity.java

    效果图:

    原文链接:

    Android动态加载jar,apk的实现

  • 相关阅读:
    【spring 注解驱动开发】spring ioc 原理
    目录大纲,欢迎来戳
    进程相关
    网络编程相关知识点
    Django基本知识
    浅谈 Web框架
    浅谈 Flask 框架
    AJAX小示例
    浅谈cookie 和 session
    ORM:对象关系映射
  • 原文地址:https://www.cnblogs.com/HDK2016/p/8306915.html
Copyright © 2011-2022 走看看