Android动态加载jar、apk的实现

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

    一、正文
       1.1 动态加载jar
    类似于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 
*/  
rivate 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.

猜你喜欢

转载自iaiai.iteye.com/blog/2262333