前段时间到阿里巴巴参加支付宝技术分享沙龙,看到支付宝在Android使用插件化的技术,挺好奇的。正好这几天看到了农民伯伯的相关文章,因此简单整理了下,有什么错误希望大神指正。
核心类
1.1 DexClassLoader类
可以加载jar/apk/dex,可以从SD卡中加载为安装的apk。
1.2 PathClassLoader类
只能加载已经安装到Android系统中的apk文件。
一、正文
1.1 动态加载jar
类似于eclipse的插件化实现,首先定义好接口,用户实现接口功能后即可通过动态加载的方式载入jar文件,以实现具体功能。注意,这里的jar包需要经过android dx工具的处理 , 否则不能使用。
首先我们定义如下接口:
[java] view plaincopy
1. package com.example.interf;
2. /**
3. * @Title: ILoader.java
4. * @Package com.example.loadjardemo
5. * @Description: 通用接口, 需要用户实现
6. * @version V1.0
7. */
8. public interface ILoader {
9. public String sayHi();
10. }
11.
用户需实现,该接口,并且将工程导出为jar包的形式。
示例如下:
[java] view plaincopy
1. public class JarLoader implements ILoader {
2.
3. public JarLoader() {
4. }
5.
6. @Override
7. public String sayHi() {
8. return "I am jar loader.";
9. }
10.
11. }
最后,实现功能的代码打包成jar包:
首先选中工程,右键后选择“导出”,然后选择“java”-----“jar文件”,然后将你的具体功能实现类导出为jar,文件名为loader.jar,如下图所示:
将打包好的jar拷贝到SDK安装目录android-sdk-windows\platform-tools下,DOS进入这个目录,执行如下命令:
[java] view plaincopy
1. dx --dex --output=loader_dex.jar loader.jar
然后将loader_dex.jar放到android手机中,这里我们放到SD卡根目录下。
动态加载代码:
[java] view plaincopy
1. /**
2. *
3. * @Title: loadJar
4. * @Description: 项目工程中必须定义接口, 而被引入的第三方jar包实现这些接口,然后进行动态加载 。
5. * 相当于第三方按照接口协议来开发, 使得第三方应用可以以插件的形式动态加载到应用平台中。
6. * @return void
7. * @throws
8. */
9. private void loadJar(){
10. final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString()
11. + File.separator + "loader_dex.jar");
12.
13. BaseDexClassLoader cl = new BaseDexClassLoader(Environment.getExternalStorageDirectory().toString(),
14. optimizedDexOutputPath, optimizedDexOutputPath.getAbsolutePath(), getClassLoader());
15. Class libProviderClazz = null;
16.
17. try {
18. // 载入JarLoader类, 并且通过反射构建JarLoader对象, 然后调用sayHi方法
19. libProviderClazz = cl.loadClass("com.example.interf.JarLoader");
20. ILoader loader = (ILoader)libProviderClazz.newInstance();
21. Toast.makeText(MainActivity.this, loader.sayHi() , Toast.LENGTH_SHORT).show();
22. } catch (Exception exception) {
23. // Handle exception gracefully here.
24. exception.printStackTrace();
25. }
26. }
效果如下图所示:
1.2 加载未安装的apk
首先新建一个Android项目,定义如下接口:
[java] view plaincopy
1. public interface ISayHello {
2. public String sayHello() ;
3. }
定义一个Activity实现该接口,如下:
[java] view plaincopy
1. package com.example.loaduninstallapkdemo;
2.
3. import android.app.Activity;
4. import android.os.Bundle;
5. import android.view.Menu;
6. import android.view.View;
7. import android.view.View.OnClickListener;
8. import android.widget.Toast;
9.
10. /**
11. *
12. * @ClassName: UninstallApkActivity
13. * @Description: 这是被动态加载的Activity类
14. *
15. */
16. public class UninstallApkActivity extends Activity implements ISayHello{
17.
18. private View mShowView = null ;
19.
20. @Override
21. protected void onCreate(Bundle savedInstanceState) {
22. super.onCreate(savedInstanceState);
23. setContentView(R.layout.activity_main);
24.
25. mShowView = findViewById(R.id.show) ;
26. mShowView.setOnClickListener(new OnClickListener() {
27.
28. @Override
29. public void onClick(View v) {
30. Toast.makeText(UninstallApkActivity.this, "这是已安装的apk被动态加载了", Toast.LENGTH_SHORT).show();
31. }
32. }) ;
33. }
34.
35. @Override
36. public boolean onCreateOptionsMenu(Menu menu) {
37. // Inflate the menu; this adds items to the action bar if it is present.
38. getMenuInflater().inflate(R.menu.activity_main, menu);
39. return true;
40. }
41.
42. @Override
43. public String sayHello(){
44. return "Hello, this apk is not installed";
45. }
46. }
然后将该编译生apk,并且将该apk拷贝到SD卡根目录下。
动态加载未安装的apk
[java] view plaincopy
1. /**
2. *
3. * @Title: loadUninstallApk
4. * @Description: 动态加载未安装的apk
5. * @return void
6. * @throws
7. */
8. private void loadUninstallApk(){
9. String path = Environment.getExternalStorageDirectory() + File.separator;
10. String filename = "LoadUninstallApkDemo.apk";
11.
12. // 4.1以后不能够将optimizedDirectory设置到sd卡目录, 否则抛出异常.
13. File optimizedDirectoryFile = getDir("dex", 0) ;
14. DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(),
15. null, getClassLoader());
16.
17. try {
18. // 通过反射机制调用, 包名为com.example.loaduninstallapkdemo, 类名为UninstallApkActivity
19. Class mLoadClass = classLoader.loadClass("com.example.loadunstallapkdemo.UninstallApkActivity");
20. Constructor constructor = mLoadClass.getConstructor(new Class[] {});
21. Object testActivity = constructor.newInstance(new Object[] {});
22.
23. // 获取sayHello方法
24. Method helloMethod = mLoadClass.getMethod("sayHello", null);
25. helloMethod.setAccessible(true);
26. Object content = helloMethod.invoke(testActivity, null);
27. Toast.makeText(MainActivity.this, content.toString(), Toast.LENGTH_LONG).show();
28.
29. } catch (Exception e) {
30. e.printStackTrace();
31. }
32. }
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 anapplication.
This class loader requires an application-private,writable directory to cache optimized classes. Use Context.getDir(String,int) to create such a directory:
FiledexOutputDir = context.getDir("dex", 0);
Do notcache optimized classes on external storage. External storage does not provide access controls necessary toprotect your application from code injection attacks.
效果如图 :
1.3 加载已安装的apk
将1.2中的apk安装到手机中,我的例子中,该apk的包名为“com.example.loaduninstallapkdemo”,Activity名为"UninstallApkActivity".加载代码如下:
[java] view plaincopy
1. /**
2. *
3. * @Title: loadInstalledApk
4. * @Description: 动态加载已安装的apk
5. * @return void
6. * @throws
7. */
8. rivate void loadInstalledApk() {
9. try {
10. String pkgName = "com.example.loaduninstallapkdemo";
11. Context context = createPackageContext(pkgName,
12. Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE) ;
13.
14. // 获取动态加载得到的资源
15. Resources resources = context.getResources() ;
16. // 过去该apk中的字符串资源"tips", 并且toast出来,apk换肤的实现就是这种原理
17. String toast = resources.getString(resources.getIdentifier("tips", "string", pkgName) ) ;
18. Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show() ;
19.
20. Class cls = context.getClassLoader().loadClass(pkgName + ".UninstallApkActivity") ;
21. // 跳转到该Activity
22. startActivity(new Intent(context, cls)) ;
23. } catch (NameNotFoundException e) {
24. e.printStackTrace();
25. }catch (ClassNotFoundException e) {
26. Log.d("", e.toString()) ;
27. }
28. </span>
效果如图:
消息被Toast出来,并且跳转到了目标Activity.