动态加载、插件化开发很重要 
当今360手机助手(DroidPlugin),个人开源(VirtualApp)、百度DL、携程DynamicAPK都用到了该技术

本例的大概思路是: 
1、apk1初始化就一个主界面MainActivity,主界面只有一个Button按钮,点击后,弹出Toast,然后我们把编译好的apk1放到手机根目录SD卡下 
2、apk2有一个MainActivity界面,界面上也有一个Button,点击按钮后,去加载SD目录下的apk1,调起来apk1,点击apk1中的button,弹出Toast即可

以上就是一个简单的逻辑?其实呢这里面问题好多,这里先简单说下问题点。 
1、其实点击apk2的button启动的不是apk1的界面,而是将apk1的界面托管给一个静态代理类Activity,然后以静态代理Activity去构建类似于apk1的button,继而在静态代理Activity的上下文环境下,弹出Toast 
2、这个例子只是在静态代理Activity类里,进行了简单的反射调用apk1的onCreate方法,被反射的apk1的主界面类,其实本质是一个java类,它没有Activity里面的逻辑,比如你拿不到里面的layout等资源,所以这个demo也就是一个对动态加载的一个小小的理解,没有涉及到Activity4大组建的动态代理、binder机制等

首先来看下apk的代码把 
MainActivity

package com.example.targetproject;
import android.annotation.SuppressLint;
import android.app.ActionBar.LayoutParams;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;public class MainActivity extends Activity {public static final String KEY_APK_PATH = "apkPath";public static final String KEY_CLASS = "class";public static final String DEX_PATH = android.os.Environment.getExternalStorageDirectory().getPath() + "/TargetProject.apk";protected Activity mProxyActivity;public void setProxy(Activity proxyActivity) {mProxyActivity = proxyActivity;}@SuppressLint("NewApi")@Overrideprotected void onCreate(Bundle savedInstanceState) {if (mProxyActivity == null) {super.onCreate(savedInstanceState);mProxyActivity = this;}Button button = new Button(mProxyActivity);button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));button.setText("按我");button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(mProxyActivity, "你点击了按钮啦!", Toast.LENGTH_SHORT).show();}});if (mProxyActivity == this) {super.setContentView(button);} else {mProxyActivity.setContentView(button);}}
}

简单说下里面的逻辑关系 
这个界面做了什么呢?就是创建了一个以某个Activity为环境的Button,然后点击button,会产生一个以某个Activity为环境的Toast,我们先抛开让测试的apk进行通过动态加载的方式,以代理Activity调起的情况,首先它是一个可以独立运行编译的apk文件,所以说,我们先来分析下它需要的Activity,我们先定义一个Activity类

protected Activity mProxyActivity;

在onCreate方法中

if (mProxyActivity == null) {super.onCreate(savedInstanceState);mProxyActivity = this;}

意思就是如果没有托管的Activity类,就使用原生的Activity,那么如果有托管的Activity呢?我们就进行如下的设置

public void setProxy(Activity proxyActivity) {mProxyActivity = proxyActivity;}

在apk的主界面,加入一个代理类Activity,然后在代理类Activity环境下去创建button,Toast 
以下就是创建Button的代码 

 Button button = new Button(mProxyActivity);button.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));button.setText("按我");button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(mProxyActivity, "你点击了按钮啦!", Toast.LENGTH_SHORT).show();}});

创建Button\Toast完毕呢,就是需要设置到某个Activity环境下,这里还得需要判断是原生的Activity环境,还是代理Activity环境?

if (mProxyActivity == this) {super.setContentView(button);} else {mProxyActivity.setContentView(button);}

然后编译后,运行apk没问题,就放到手机的sd卡根目录下,以下是我手机nexus5的路径目录

然后我们就来看测试apk的代码逻辑把

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.test.MainActivity" ><Button android:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/load" /></RelativeLayout>

MainActivity

package com.example.test;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button btn = (Button) this.findViewById(R.id.btn);btn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {String root = android.os.Environment.getExternalStorageDirectory().getPath()+ "/TargetProject.apk";Intent intent = new Intent(MainActivity.this,ProxyActivity.class);intent.putExtra(ProxyActivity.KEY_APK_PATH, root);startActivity(intent);}});}
}

先来看下主界面的代码,这里就是一个button,点击后带过去一个sd卡跟目录下那个apk1的绝对路径,然后调到ProxyActivity类

String root = android.os.Environment.getExternalStorageDirectory().getPath()+ "/TargetProject.apk";

然后就看我们的代理Activity类

package com.example.test;import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import android.util.Log;
import dalvik.system.DexClassLoader;/*** 代理类* @author safly**/
public class ProxyActivity extends Activity{public static final String KEY_APK_PATH = "apkPath";  public static final String KEY_CLASS = "class";   @Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);//获取指定的apk文件路径和启动类名String mDexPath = getIntent().getStringExtra(KEY_APK_PATH);  String mClass = getIntent().getStringExtra(KEY_CLASS);if (mClass == null) {PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(mDexPath, 1);  if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {  Log.e("ProxyActivity", ""+packageInfo.activities[0].name);mClass = packageInfo.activities[0].name;   }  }launchTargetActivity(mDexPath,mClass); }  /*** 利用ClassLoader,DexClassLoader和反射将apk中的界面启动* @param mDexPath apk 动态加载的apk本地路径* @param className 要打开的动态加载类的类名*/protected void launchTargetActivity(String mDexPath,String className) {Log.e("ProxyActivity", "launchTargetActivity");File dexOutputDir = this.getDir("dex", 0);  final String dexOutputPath = dexOutputDir.getAbsolutePath();  ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();  DexClassLoader dexClassLoader = new DexClassLoader(mDexPath, dexOutputPath, null, localClassLoader);  try {  Class<?> localClass = dexClassLoader.loadClass(className);  Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});  Object instance = localConstructor.newInstance(new Object[] {});//利用反射机制获取到设置代理Activity的方法Method setProxy = localClass.getMethod("setProxy",new Class[] { Activity.class });  setProxy.setAccessible(true);  setProxy.invoke(instance, new Object[] { this });  //利用反射机制调用onCreate方法Method onCreate = localClass.getDeclaredMethod("onCreate",new Class[] { Bundle.class });  onCreate.setAccessible(true);   onCreate.invoke(instance, new Object[] { null });  } catch (Exception e) {  e.printStackTrace();  }  }  }

来说下上面的代码意思 
ProxyActivity类onCreate方法中获取传递过来的KEY_APK_PATH(sd卡下apk1的绝对路径),我们还需要一个类,就是apk1的主界面的全类名,因为我们需要调用里面的onCreate方法,然后去添加button、toast控件

if (mClass == null) {PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(mDexPath, 1);  if ((packageInfo.activities != null) && (packageInfo.activities.length > 0)) {  Log.e("ProxyActivity", ""+packageInfo.activities[0].name);mClass = packageInfo.activities[0].name;   }  }

log输出如下

ProxyActivity(23526): com.example.targetproject.MainActivity

然后就看下launchTargetActivity里面的代码

File dexOutputDir = this.getDir("dex", 0);
dexOutputPath--/data/data/com.example.test/app_dex 

以上是dex解压释放后的目录 ,log输出的目录,以下是截图

然后获取一个DexClassLoader,这里面参数为sd卡apk1的绝对路径、app_dex路径,然后还有一个ClassLoader.getSystemClassLoader()对象 
参数如下

(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

如下方法就是获取apk1中MainActivity的构造然后new一个实例

Class<?> localClass = dexClassLoader.loadClass(className);  Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});  Object instance = localConstructor.newInstance(new Object[] {});

以下就是反射去获取setProxy,onCreate方法,进行设置代理Activity类,然后在代理Activity类中进行设置button\toast控件

 //利用反射机制获取到设置代理Activity的方法Method setProxy = localClass.getMethod("setProxy",new Class[] { Activity.class });  setProxy.setAccessible(true);  setProxy.invoke(instance, new Object[] { this });  //利用反射机制调用onCreate方法Method onCreate = localClass.getDeclaredMethod("onCreate",new Class[] { Bundle.class });  onCreate.setAccessible(true);   onCreate.invoke(instance, new Object[] { null });  

以上就是这个小例子的逻辑,也算是对自己开启动态加载学习的一个小入门理解把

通过代理Activity模式,以移花接木的方式,加载sd卡目录下的apk界面相关推荐

  1. android sd卡列目录文件_Android加载SD卡目录,文件夹遍历,图片设置,设置文件对应打开方式等...

    public class GridViewFile extends Activity implementsView.OnClickListener {privateContext context;pr ...

  2. Android动态加载进阶 代理Activity模式

    基本信息 作者:kaedea 项目:android-dynamical-loading 技术背景 简单模式中,使用ClassLoader加载外部的Dex或Apk文件,可以加载一些本地APP不存在的类, ...

  3. 使用jQuery和YQL,以Ajax方式加载外部内容

    我们来看看怎样使用jQuery,以Ajax方式加载外部(其他域上)的内容.这里的所有代码都可以从GitHub下载,也可以在这个演示页面中获取,因而不用复制粘贴了. OK,Ajax通过jQuery是很容 ...

  4. 《ArcGIS Runtime SDK for Android开发笔记》——(13)、图层扩展方式加载Google地图...

    1.前言 http://mt2.google.cn/vt/lyrs=m@225000000&hl=zh-CN&gl=cn&x=420&y=193&z=9& ...

  5. 使用SDWebImage淡入淡出的方式加载图片

    使用SDWebImage淡入淡出的方式加载图片 效果: 请通过以下方式下载源码: 找到它修改文件的地方: 以下是使用源码: // // ViewController.m // SDWebImageFa ...

  6. 【js】【cornerstone】cornerstone使用url方式加载图像

    [js][cornerstone]cornerstone使用url方式加载图像 引入cornerstoneWebImageLoader loadImage 引入cornerstoneWebImageL ...

  7. post方式加载iframe

    我们平常使用iframe时,直接设定src属性只能是get请求方式 ,get请求的参数大小有限制 如何实现即使用iframe又能通过post请求 两种方式 ajax使用post请求返回页面,直接将返回 ...

  8. Unity两中方式加载图片

    看到草羊发的图片,也是真懒. 想起来当时写天气预报现在都忘干净了,好记性不如烂笔头,还是应该多记.自己打下来好了 using System; using System.Collections; usi ...

  9. layui信息加载流的方式加载数据

    SSM项目中使用layui信息加载流的方式加载数据 这里首先jsp页面来一个div容器,这里数数据显示的地方 //css样式: <style type="text/css"& ...

最新文章

  1. 渥太华大学计算机硕士课程,渥太华大学计算机工程专业解析
  2. 【Android】AsyncTask异步类
  3. 《Python数据分析与挖掘实战》读书笔记
  4. Apache Hadoop 2.4.1 单节点安装
  5. 在mac上搭建octopress+github pages博客
  6. java里面add报错_java.util.Arrays$ArrayList addAll报错
  7. jquery :nth-child()选择器的简单应用
  8. BZOJ2006:[NOI2010]超级钢琴——题解
  9. 免费培训后包就业,还月薪上万,深扒BI数据工程师培训套路
  10. 第三次面试题目 (反省中!!)
  11. 期末知识点复习——概率论与数理统计(5)
  12. 一个很不错的H5动画网站
  13. 不可思议有氧机器人_不思议迷宫奇怪的机器人 不思议迷宫奇怪的机器人获取方式一览...
  14. 基于Android的房屋租赁系统
  15. Python: 向量、矩阵和多维数组(基于NumPy库)
  16. python用 requests 模块从 Web 下载文件
  17. control设备的注册流程
  18. Elastic的Workplace Search如何使用Gmail或Google Drive等数据源
  19. efi格式linux启动u盘启动不了,Grub2 EFI U盘启动
  20. TwinCAT3之Ads通讯——1、控制器和控制器间通讯

热门文章

  1. 1. 大数据 机器学习 深度学习 推荐系统 学习路线
  2. 【学而时习之】SpringCloud技术点
  3. 云原生架构(04)-CNCF
  4. 快速矩阵乘法的算法实现
  5. javascript 按钮点击事件
  6. 知识管理、文档管理两手抓,全靠它!
  7. 前端笔记80——获取随机数
  8. mySQL基本语法 DDL语句
  9. IE浏览器右键清理方法
  10. 二维码纯手工制作(一步一步带你走进二维码的世界,逐渐让它变得越来越清晰,直至你完全了解)