前段时间到阿里巴巴参加支付宝技术分享沙龙,看到支付宝在Android使用插件化的技术,挺好奇的。正好这几天看到了农民伯伯的相关文章,因此简单整理了下,有什么错误希望大神指正。
核心类
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;
-
-
-
-
-
-
- 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卡根目录下。
动态加载代码 :
-
-
-
-
-
-
-
-
- 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 {
-
- 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) {
-
- 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;
-
-
-
-
-
-
-
- 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) {
-
- getMenuInflater().inflate(R.menu.activity_main, menu);
- return true;
- }
-
- @Override
- public String sayHello(){
- return "Hello, this apk is not installed";
- }
- }
然后将该编译生apk, 并且将该apk拷贝到SD卡根目录下。
动态加载未安装的apk
-
-
-
-
-
-
-
- private void loadUninstallApk(){
- String path = Environment.getExternalStorageDirectory() + File.separator;
- String filename = "LoadUninstallApkDemo.apk";
-
-
- File optimizedDirectoryFile = getDir("dex", 0) ;
- DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(),
- null, getClassLoader());
-
- try {
-
- Class mLoadClass = classLoader.loadClass("com.example.loadunstallapkdemo.UninstallApkActivity");
- Constructor constructor = mLoadClass.getConstructor(new Class[] {});
- Object testActivity = constructor.newInstance(new Object[] {});
-
-
- 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:
<span class="pln" style="color: rgb(0, 0, 0); "> </span><code style="line-height: 14px; "><span class="typ" style="color: rgb(102, 0, 102); ">File</span><span class="pln" style="color: rgb(0, 0, 0); "> dexOutputDir </span><span class="pun" style="color: rgb(102, 102, 0); ">=</span><span class="pln" style="color: rgb(0, 0, 0); "> context</span><span class="pun" style="color: rgb(102, 102, 0); ">.</span><span class="pln" style="color: rgb(0, 0, 0); ">getDir</span><span class="pun" style="color: rgb(102, 102, 0); ">(</span><span class="str" style="color: rgb(0, 136, 0); ">"dex"</span><span class="pun" style="color: rgb(102, 102, 0); ">,</span><span class="pln" style="color: rgb(0, 0, 0); "> </span><span class="lit" style="color: rgb(0, 102, 102); ">0</span><span class="pun" style="color: rgb(102, 102, 0); ">);</span><span class="pln" style="color: rgb(0, 0, 0); ">
</span></code>
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". 加载代码如下 :
-
-
-
-
-
-
-
- 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() ;
-
- 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") ;
-
- startActivity(new Intent(context, cls)) ;
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }catch (ClassNotFoundException e) {
- Log.d("", e.toString()) ;
- }
- </span>