android 动态加载jar/dex/apk

  前段时间到阿里巴巴参加支付宝技术分享沙龙,看到支付宝在Android使用插件化的技术,挺好奇的。正好这几天看到了农民伯伯的相关文章,因此简单整理了下,有什么错误希望大神指正。

        

        核心类

      1.1      DexClassLoader类 
可以加载jar/apk/dex,可以从SD卡中加载为安装的apk。 
 1.2      PathClassLoader类 
只能加载已经安装到Android系统中的apk文件。

    一、正文

       1.1 

    类似于eclipse的插件化实现, 首先定义好接口, 用户实现接口功能后即可通过动态加载的方式载入jar文件, 以实现具体功能。 注意 , 这里的jar包需要经过android dx工具的处理 , 否则不能使用。

首先我们定义如下接口 :

package com.example.interf;  
/** 
 * @Title: ILoader.java 
 * @Package com.example.loadjardemo 
 * @Description:  通用接口, 需要用户实现
 * @version V1.0 
 */
public interface ILoader {
   public String sayHi();
}
用户需实现,该接口, 并且将工程导出为jar包的形式。

示例如下 :

public class JarLoader implements ILoader {

  public JarLoader() {
  }

  @Override
  public String sayHi() {
    return "I am jar loader.";
  }

}

最后, 实现功能的代码打包成jar包 :

首先选中工程, 右键后选择“导出”, 然后选择“java”-----“jar文件”, 然后将你的具体功能实现类导出为jar,文件名为loader.jar,如下图所示 : 


将打包好的jar拷贝到SDK安装目录android-sdk-windows\platform-tools下,DOS进入这个目录,执行如下命令: 

dx --dex --output=loader_dex.jar loader.jar

然后将loader_dex.jar放到android手机中, 这里我们放到SD卡根目录下。

动态加载代码 : 
/**
   * 
   * @Title: loadJar 
   * @Description: 项目工程中必须定义接口, 而被引入的第三方jar包实现这些接口,然后进行动态加载 。
   *          相当于第三方按照接口协议来开发, 使得第三方应用可以以插件的形式动态加载到应用平台中。
   * @return void    
   * @throws
   */
  private void loadJar(){
        final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString()
                + File.separator + "loader_dex.jar");
            
            BaseDexClassLoader cl = new BaseDexClassLoader(Environment.getExternalStorageDirectory().toString(),
            		optimizedDexOutputPath, optimizedDexOutputPath.getAbsolutePath(), getClassLoader());
            Class libProviderClazz = null;
            
            try {
                // 载入JarLoader类, 并且通过反射构建JarLoader对象, 然后调用sayHi方法
                libProviderClazz = cl.loadClass("com.example.interf.JarLoader");
                ILoader loader = (ILoader)libProviderClazz.newInstance();
                Toast.makeText(MainActivity.this, loader.sayHi() , Toast.LENGTH_SHORT).show();
            } catch (Exception exception) {
                // Handle exception gracefully here.
                exception.printStackTrace();
            }
  }

效果如下图所示 :  
请等待...

1.2 加载未安装的apk

     首先新建一个Android项目, 定义如下接口 :  

    

public interface ISayHello {
  public String sayHello()  ;
}
    

定义一个Activity实现该接口, 如下:

package com.example.loaduninstallapkdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

/**
 * 
 * @ClassName: UninstallApkActivity 
 * @Description: 这是被动态加载的Activity类
 *
 */
public class UninstallApkActivity extends Activity implements ISayHello{

  private View mShowView = null ;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    mShowView = findViewById(R.id.show) ;
    mShowView.setOnClickListener(new OnClickListener() {
      
      @Override
      public void onClick(View v) {
        Toast.makeText(UninstallApkActivity.this, "这是已安装的apk被动态加载了", Toast.LENGTH_SHORT).show();
      }
    }) ;
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
  }

  @Override
  public String sayHello(){
    return "Hello, this apk is not installed";
  }
}
然后将该编译生apk, 并且将该apk拷贝到SD卡根目录下。

动态加载未安装的apk

/**
   * 
   * @Title: loadUninstallApk 
   * @Description: 动态加载未安装的apk
   * @return void    
   * @throws
   */
  private void loadUninstallApk(){
        String path = Environment.getExternalStorageDirectory() + File.separator;
        String filename = "LoadUninstallApkDemo.apk";

        // 4.1以后不能够将optimizedDirectory设置到sd卡目录, 否则抛出异常.
        File optimizedDirectoryFile = getDir("dex", 0) ;
        DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(),
                											null, getClassLoader());

        try {
        	// 通过反射机制调用, 包名为com.example.loaduninstallapkdemo, 类名为UninstallApkActivity
            Class mLoadClass = classLoader.loadClass("com.example.loadunstallapkdemo.UninstallApkActivity");
            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();
        }
  }

DexClassLoader 注意点 :

A class loader that loads classes from  .jar  and  .apk  files containing a classes.dex  entry. This can be used to execute code not installed as part of an application.

This class loader requires an application-private, writable directory to cache optimized classes. Use  Context.getDir(String, int)  to create such a directory:

   File dexOutputDir = context.getDir("dex", 0);
 

Do not cache optimized classes on external storage.  External storage does not provide access controls necessary to protect your application from code injection attacks.


效果如图 : 


1.3 加载已安装的apk

将1.2中的apk安装到手机中,我的例子中,该apk的包名为“com.example.loaduninstallapkdemo”,Activity名为"UninstallApkActivity". 加载代码如下 :

  /**
   * 
   * @Title: loadInstalledApk 
   * @Description: 动态加载已安装的apk    
   * @return void    
   * @throws
   */
  private void loadInstalledApk() {
    try {
      String pkgName = "com.example.loaduninstallapkdemo";
      Context context = createPackageContext(pkgName,
              Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE) ;
      
      // 获取动态加载得到的资源
      Resources resources = context.getResources() ;
      // 过去该apk中的字符串资源"tips", 并且toast出来,apk换肤的实现就是这种原理
      String toast = resources.getString(resources.getIdentifier("tips", "string", pkgName) ) ;
      Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show() ;
      
      Class cls = context.getClassLoader().loadClass(pkgName + ".UninstallApkActivity") ;
      // 跳转到该Activity
      startActivity(new Intent(context, cls)) ;
    } catch (NameNotFoundException e) {
      e.printStackTrace();
    }catch (ClassNotFoundException e) {
      Log.d("", e.toString()) ;
    }
  }
效果如图:

消息被Toast出来, 并且跳转到了目标Activity.  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值