1. 渠道打包
1.1. 概念
一.渠道是什么?
可以下载 apk 的地方(具体讲就是应用商店或者应用市场)。只要把自己打包的应用上传到这些渠道。用
户就可以下载了,自己的应用也就推广出去了。同时这些渠道还可以将 app 下载的数据反馈给开发者。
二.常用见的渠道有哪些?
Google Play : https://play.google.com/apps/publish
应用汇: http://dev.appchina.com
机锋市场 : http://dev.gfan.com/
91 和安卓市场 : http://dev.apk.hiapk.com/
//说明一下,发布在安卓市场也会发布到 91 市场,他们其实同一家了
安智(goapk) : http://dev.anzhi.com/
木蚂蚁 : http://dev.mumayi.com/
N 多网 : http://www.nduoa.com/developer
联想乐商店 : http://developer.lenovomm.com/developer/
十字猫 : http://dev.crossmo.com/
腾讯应用宝 : http://tap.myapp.com/android/index.jsp
飞流 : http://dev.feiliu.com/
智汇云 : https://dev.hicloud.com/indexManageAction.action
小米应用商店 : http://developer.xiaomi.com/
百度 : http://developer.baidu.com/
魅族 : http://developer.meizu.com/
易优 : http://www.eomarket.com/user/login/lang/zh_CN
手机之家 : http://profile.imobile.com.cn
联通沃商店 : http://dev.wo.com.cn/userportal/mapc_login.action
爱卓网 : http://www.iandroid.cn/index.php?app=my_sharegoods&act=add&is_original=1
三.渠道统计的原理
1 为 apk 打标记
一般有两种实现方式 1、在代码中写(硬编码) 2 在 AndroidManifext.xml 中填写(元数据 meta-data)
2 运行 apk 的时候取出标识
3 上传标识给服务器统计
4 开发者使用账号登录查看统计数据
1.2. 基本工作
Umeng App 统计
1 为 app 配置每个渠道的元数据 手动地一个一个打包(相当费时)
例:使用友盟统计获取渠道数据
一.添加依赖
day02demoapk/build.gradle
compile 'com.umeng.analytics:analytics:latest.integration'
二.页面代码编写
day01.itheima.com.day02demoapk.MainActivity
public classMainActivity extends AppCompatActivity {
//...
public void onResume(){
super.onResume();
MobclickAgent.onResume(this);
}
public void onPause() {
super.onPause();
MobclickAgent.onPause(this);
}
}
三.功能清单元数据配置
day02demoapk/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
...>
<meta-data android:value="573e8baae0f55a3532000010"
android:name="UMENG_APPKEY"></meta-data>
<meta-data android:value="wandoujia" android:name="UMENG_CHANNEL"/>
</application>
四.账号登录查看
五小结:
以上是友盟的打包流程。刨去友盟统计的基本使用方法,打包的部分只是步骤 3meta-data 配置。但是一
个一个地去打包 如果只有 2,3 个还好,但如果要 100 个就很耗时。(UMENG_APPKEY 需要大家去申请)
1.3.Gradle 打包(Generate Signed APK 单个)
如果是单独打一个 APP 包加上上面的配置直接 IDE 中导出 APK 就行了,如果打很多个 APK 用
IDE 就很费力.下面了解一下 gradle如何手动打包一个 apk
一.检查 jks(如果有的话就可以直接跳过)
Eclispe 导出已经签名的 apk 使用的证书后缀叫 keystore,但是在 androidstudio 下面后缀叫 jks
二..点击 Build ,在下拉框中选择 "Generate Signed APK"
三..选择 "Create new"
四..按照里面的内容填写即可,注意最后文件的扩展名变为".jks",而不是以前的".keystore".
五.配置签名 jks
1>点击依赖
2>选中 module
3>点击+号代码创建
4>后面按格填写后点击 OK
以上操作实质是在
day02demoapk/build.gradle
中生成以下配置
六. 选择打包 release 或者 debug
>1 打开 gradle 脚本窗口
>2 选中 assembleDebug 或者 assembleRelease (选前面就编译 debug 下的 apk 选后者就是 release)
>3,点击 run
>4.查看生成的 apk
1.4.Gradle 打包(productFlavors 完成批量)
使用占位符与 productFlavors 变量完成批量打包
day02demoapk/src/main/AndroidManifest.xml
>>1.配置占位符
day02demoapk/build.gradle
》》2.对安卓 module 添加多个 productFlavors
》》3.对占位符(placeholders)进行赋值
// 配置占位符的值 ( 渠道名称 )
productFlavors {
AppChina{
manifestPlaceholders=[UMENG_CHANNEL:"AppChina"]
}
wandoujia {
manifestPlaceholders =[UMENG_CHANNEL: "wandoujia"]
}
baidu {
manifestPlaceholders =[UMENG_CHANNEL: "baidu"]
}
c360 {
manifestPlaceholders =[UMENG_CHANNEL: "c360"]
}
uc{
manifestPlaceholders =[UMENG_CHANNEL: "uc"]
}
}
》》4.执行批量打包操作(图形化)
后续就正常填写就行啦
点击 finish 后就生成了
这种占位符的方式呢?开发者只需配上占位符的值(具体的话就是渠道名称 不能是中文)执行生成apk操
作就可以啦。这个是可以满足批量打包的,效率高!
可以进一步使用反编译工具查看功能清单里面的渠道变量
1.5. 美团打包方案一分钟 900 个
美团技术团队
http://tech.meituan.com/mt-apk-packaging.html
==》
基本原理
因为 apk 的 meta-info 下的文件不参与签名,所以可在 meta-info 下面生成一个内容为空的文件, 渠道名的
前缀为 mtchannel_。应用程就可以使用 ZipFile读取文件名取得 mtchannel_的后面的字符串 例
mtchannel_c360 的 360 字符串然后发送给服务端,就可以得出渠道统计数据。其中批量打包在使用
python工具完成。
所以只要完成两个步骤就可以
一.下载相关工具
https://github.com/GavinCT/AndroidMultiChannelBuildTool
二.生成一个已经签名的 apk 程序内部使用 java 工具获取 meta-data 下面的渠道变量
day01.itheima.com.day02demoapk.MainActivity
public void showChannel(View view)
{
// 获取当前 apk 的渠道变量值发送给服务端
String channel = ChannelUtil.getChannel(this);
Log.i(TAG, "onCreate: "+channel);
Toast.makeText(this,"channel="+channel,Toast.LENGTH_LONG).show();
}
ChannelUtil 可以从下载的工具代码里面复制
三.运行 python 工具对该apk 进行复制与修改渠道变量
搭建 python 环境
下载地址
https://www.python.org/downloads/windows/
下载完成后点击该 exe 文件进行安装
安装完成后配置环境变量
再使用 cmd 命令行测试下 python
脚本的工作原理
一.首先创建一个空文件,等待写入 META-INF 目录作为 channel_xxx 文件
二.获取渠道列表。
三.找到初始 apk
四.遍历渠道号并写入 apk。
执行 python 脚本
AndroidMultiChannelBuildTool-master\PythonTool
把已经签名的 apk 放置在当前目录
双击执行 MultiChannelBuildTool.py 脚本
点开生成文件夹里面都是
安装后点击可以看到当前的渠道变量
2. 增量更新
Window 系统的升级过程
Android 系统的升级过程
Android studio 的升级过程
国内某些应用市场应用升级过程
自从 android 4.1 开始Google play 引入应用的增量更新功能。App 使用增量更新的方式可节省 2/3 的
流量。
增量更新的原理
将手机上已安装 apk 与服务器端最新 apk 进行二进制对比,得到差分包,用户更新程序时,只需要下载
差分包,并在本地使用差分包与已安装 apk,合成新版 apk。
例如,当前手机中已安装微博 V1,大小为 12.8MB,现在微博发布了最新版 V2,大小为 15.4MB,我们
对两个版本的 apk 文件差分比对之后,发现差异只有 3M,那么用户就只需要要下载一个 3M 的差分包,
使用旧版 apk 与这个差分包,合成得到一个新版本 apk,提醒用户安装即可,不需要整包下载 15.4M 的
微博 V2 版 apk。
共需要做 3 件事:
1 在服务器端,生成两个版本 apk 的差分包;
2 在手机客户端,使用已安装的 apk 与这个差分包进行合成,得到新版的微博 apk;
3 校验新合成的 apk 文件是否完整,MD5 或 SHA1 是否正确,如正确,则引导用户安装
支持差分与合成的开源工具
开源的二进制比较工具 bsdiff 来实现
http://www.daemonology.net/bsdiff/
Window 版本下载地址
http://sites.inka.de/tesla/others.html#bsdiff
因为 bsdiff 依赖 bzip2,所以我们还需要用到 bzip2
http://www.bzip.org/downloads.html
bsdiff 中,bsdiff.c 用于生成差分包,bspatch.c 用于合成文件。
服务端生成差异包
你的 apk 已经发布了 3 个版,V1.0、V2.0、V3.0,这时候你要在后台发布 V4.0,那么,当你在服务器上
传最新的 V4.0 包时,服务器端就应该立即生成以下差分包:
V1.0 ——> V4.0的差分包;
V2.0 ——> V4.0的差分包;
V3.0 ——> V4.0的差分包;
2.1. 差分包提取与合成
Desktop\tools
》》1. 生成差分包
Cd 到当前目录 使用命令
bsdiff.exeoldfile newfile patchfile
当前使用 Weibo5.5.apk+Weibo5.6.apk ==>生成差分包 Weibo5.5-5.6.patch
bsdiff.exe Weibo5.5.apk Weibo5.6.apk Weibo5.5-5.6.patch
运行时间稍长
》》2. 合成差分包
Cd 当前目录 使用命令
bspatch.exe oldfile newfile patchfile
当前使用 Weibo5.5.apk+差分包 Weibo5.5-5.6.patch==》Weibo5.6.new.apk
bspatch.exe Weibo5.5.apk Weibo5.6.new.apk Weibo5.5-5.6.patch
运行时间稍长
2.2. 客户端使用差异包合成完整 apk
进入下载页面获取项目的 zip
https://github.com/cundong/SmartAppUpdates
成功下载后解压得到
ApkPatchLibraryServer 服务器端生成差分包工程
ApkPatchLibrary 客户端使用的 apk 合成库,用于生成 libApkPatchLibrary.so
ApkPatchLibrarySample 一个 Sample,手机上安装 Weibo5.5.apk,通过与 SD 卡上预先存放的
weibo.patch 文件进行合并,得到 Weibo5.6.apk
导入 android模块
注意还需要添加依赖 design
day02demosmartappupdates/build.gradle
dependencies{
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
}
运行后界面
普通的模拟器使用 adb 命令安装以下 Weibo5.5.apk
而夜神的话要使用 nox_adb 作为命令
添加文件到 sdcard 这样写 nox_adb push apk 路径 mnt/sdcard
出现安装界面代表合成完整 apk 成功
2.2.1. 案例分析一
一.导入 SmartAppUpdates 中的 so 库和对应生成的 jar
注意 PatchUtils 的包名必须保持不变 这个是 jni 技术的要求
二.加载 so 库
public classMainActivity extends AppCompatActivity {
static {
System.loadLibrary("ApkPatchLibrary");
}
//...
}
三.添加权限
day02demo_updates/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="day01.itheima.com.day02demo_updates">
<!-- 写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 读权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
//...
四.代码编写
layout/activity_main.xml(xml 布局比较简单 不粘贴代码)
day01.itheima.com.day02demo_updates.MainActivity
public classMainActivity extends AppCompatActivity implements Runnable{
public static final String UPDATE1_2_PATCH= "update1-2.patch";
static {
System.loadLibrary("ApkPatchLibrary");
}
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mTv.setText(" 当前版本是 1");
}
private voidinitView() {
mTv = (TextView) findViewById(R.id.main_bnt_check);
}
public void checkPatch(View view)
{
// 查看补丁文件是否存在
File file = new File(Environment.getExternalStorageDirectory(), UPDATE1_2_PATCH);
// 存在
if(file.exists())
{
Toast.makeText(MainActivity.this, "在 存在 开始合并!",
Toast.LENGTH_SHORT).show();
new Thread(this).start();
}else// 不存在给出提示
{
Toast.makeText(MainActivity.this, " 不存在!", Toast.LENGTH_SHORT).show();
}
}
@Override
public void run() {
String oldapk= ApkUtils.getSourceApkPath(this,getPackageName());
final String
newapk=Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+System.cur
rentTimeMillis()+".apk";
String
pathPath=Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+UPDATE1_
2_PATCH;
int patchCode = PatchUtils.patch(oldapk, newapk, pathPath);// 参 1 旧文件 参 2
合成 apk 参 3 补丁文件
if(0==patchCode)
{
runOnUiThread(new Runnable() {
@Override
public void run() {
ApkUtils.installApk(MainActivity.this,newapk);
}
});
}
}
}
注意
day01.itheima.com.day02demo_updates.utils.ApkUtils#installApk 可以增加一个 flag
五.签名出两个 apk
注意版本 1 保持不变 版本 2 只要改 textview 的显示内容为 2 即可。
六.使用 bsdiff.exe 生成补丁,Push 补丁到 sdcard 根据路径
七.卸载 版本 1 重新安装 点击运行生成新 2.0apk
能够获取安装界面代表 通过差分包+版本 1.0 成功合成版本 2.0
2.3. 补丁修复框架
最新 github 上开源了很多热补丁动态修复框架,大致有:
https://github.com/dodola/HotFix
https://github.com/jasonross/Nuwa
https://github.com/bunnyblue/DroidFix
2.4.AndFix 热修复
2.4.1. AndFix 是什么?
阿里巴巴开源 Android App 线上问题修复工具!
AndFix 这个库为 Android App 提供了热修复的功能。它能够帮助 Android 开发人员在线修复 App Bug。
Andfix 是" And roid hot- fix "首字母缩写。
AndFix 支持 Android 2.3 至 6.0, arm 和 x86 架构, dalvik runtime 和 art runtime.
Github 下载地址
https://github.com/alibaba/AndFix
2.4.2. 原理
版本 1 发现有一个 bug,那就修复并且测试这个 Bug 签名出版本 2.
针对版本 1 与版本 2 进行 差分 生成 patch 即补丁文件
通过网络下载获取补丁文件 使用 AndFix 动态加载进用户正在使用的版本 1( 即 v1+patch=>v2)
Bug 被修复
2.4.3. 案例分析
》》1.创建 Demo
》》2.复制 Bug 程序
day01.itheima.com.day02demo_nuwa.R.layout#activity_main
day01.itheima.com.day02demo_nuwa.Cat
public classCat {
public static String sayHello()
{
//bugfix: return " 汪汪汪!!! ";
// return " 喵喵喵喵!!! ";
return " 汪汪汪!!!";
}
}
猫能汪汪叫吗?显示不能,所以是一个明显的简单的模拟 Bug
day01.itheima.com.day02demo_nuwa.MainActivity
public classMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void show(View view)
{
Cat cat = new Cat();
Toast.makeText(MainActivity.this,cat.sayHello() ,
Toast.LENGTH_SHORT).show();
}
}
》》3.配置 AndFix
DemoAndfix/build.gradle
dependencies{
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.alipay.euler:andfix:0.3.1@aar' // 依赖 AndFix 热补丁框架
}
》》4.编写 Andfix 加载 patch 代码
day01.itheima.com.day02demo_nuwa.MyApplication
public class MyApplication extends Application {
privatestatic final StringAPATCH_PATH ="hot.apatch";
privatestatic final StringTAG = MyApplication.class.getName();
/**PatchManager 补丁管理者 AndFix 的核心类 复 **/
privatePatchManager mPatchManager;
@Override
public void onCreate() {
super.onCreate();
// 创建实例
mPatchManager = new PatchManager(this);
mPatchManager.init("1.0");// 参 1 版本号 可以通过 PacakgeManager 获取
// 加载补丁
mPatchManager.loadPatch();
// 运行时添加补丁
try {
// .apatch 文件路径
File patch = new
File(Environment.getExternalStorageDirectory().getAbsolutePath(),APATCH_PATH);
String patchFileString=patch.getAbsolutePath();
if(patch.exists())
{
// 加载补丁
mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " 添加");
}
} catch(IOException e) {
Log.e(TAG, "", e);
}
}
}
DemoAndfix/src/main/AndroidManifest.xml
<!-- 补丁一般放在 sd 卡所以需要配置 sd 卡的访问权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:name=".MyApplication"
【要点】
PatchManager:AndFix热补丁框架的核心类:补丁管理者 负责动态加载 .patch 文件
Init 初始化方法
loadPatch 加载默认路径上的补丁文件
addPatch:添加指定路径上的补丁文件
》》5.使用 apk 签名出两个版本的 apk
version2-fixed.apk 是这样修复 bug 的
public classCat {
public static String sayHello()
{
//bugfix: return " 汪汪汪!!! ";
return " 喵喵喵喵!!!";
// return " 汪汪汪!!! ";
}
}
猫当然是 喵喵喵喵叫啦(当然这里只是一个示意,真实的企业项目肯定比这个复杂)
》》6.使用 apkPatch 工具进行差分生成补丁
apkPatch 下载AndFix 框架时包含的一个生成补丁的工具包
所以解压下载的文件 AndFix-master.zip 就可以看到 apkPatch 工具包了。
怎么 apkPatch 使用?使用cmd 打开命令窗口就可以查看到用法!
具体语法是?
apkpatch.bat -f version2-fixed.apk -t version1.apk -o patch -k itheima.jks -p
testtest -a itheima -etesttest
具体含义
-f 新版本 apk 即修复后的 apk
-t 旧版本 apk 即修复前的 apk
-o 差分包即 patch 补丁文件存放的文件夹当前为 patch 文件夹
-k 证书 eclipse 下为*.keystore 而在 androidstudio 下是*.jks 实验证明可以混用
-p 使用密码 当前是 testest
-a 别名 当前是 itheima
-e 别名对应的密码 当前是 testest
在把两个 apk版本放到当前目录下执行生成 patch 文件。然后从patch文件夹复制出来
【备注】hot.apatch一定需要跟 Application 里面的补丁文件名称保持一致!
》》7.复制到 sd 卡目录下即可
此处 因为使用夜神的模拟器所以使用 nox_adb 防止跟 sdk 中的 adb冲突
》》8.重新打开原程序
3. 动态加载
3.1. 动态加载基础 DexClassLoader
一.动态加载
在程序运行的时候,加载一些程序自身原本不存在的可执行文件并运行这些文件里的代码逻辑。
看起来就像是应用从服务器下载了一些代码,然后再执行这些代码!
启动页面:
现在大部分 APP 都有一个启动页面,如果到了一些重要的节日,APP 的服务器会配置一些与时节相关的
图片,APP 启动时候再把原有的启动图换成这些新的图片,这样就能提高用户的体验了
规避安卓市场审核
安卓市场开始扫描 APK 里面的 Manifest 甚至 dex 文件,查看开发者的 APK 包里是否有广告的代码,如果
有就有可能审核不通过。
先不要在 APK 写广告的代码,在用户运行 APP的时候,再从服务器下载广告的代码,运行,再现实广告
二.实现原理
Java 的可执行文件是 Jar,运行在虚拟机上 JVM 上,虚拟机通过 ClassLoader 加载 Jar 文件并执行里面的
代码。所以 Java 程序也可以通过动态调用 Jar 文件达到动态加载的目的。
3.1.1. 案例分析一: 加载外部.so
从 JniLib 目录加载(这个是 android studio 默认目录)
为了方便演示从网上下载一个最简单的 helloworld 的 demo
https://github.com/han1202012/NDKHelloworld
下载后在本地创建 demo(根据 jni 的开发要求不能修改包名所以包名是shuliang.han.nkdhellowold)
layout/activity_main.xml
shuliang.han.ndkhelloworld.MainActivity
public classMainActivity extends Activity{
// 静态代码块加载 C 语言库文件
static{
System.loadLibrary("hello");
}
/*
* 声明一个 native 方法
* 这个方法在 Java 中是没有实现的 , 没有方法体
* 该方法需要使用 C 语言编写
*/
public native String helloFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println(helloFromJNI());
}
public void onClick(View view) {
// 点击按钮显示从 jni 调用得到的字符串信息
Toast.makeText(getApplicationContext(), helloFromJNI(),
Toast.LENGTH_LONG).show();
}
}
点击后运行效果为
到此代表 jni 工程部署成功。
下面只需要另外创建一个 demo 用来测试从手机内部加载 so 文件
注意 main 下面 jniLibs 已经被删除了,同时创建 assets 目录复制 libhello.so
shuliang.han.ndkhelloworld.MainActivity
public classMainActivity extends Activity{
/*
* 声明一个 native 方法
* 这个方法在 Java 中是没有实现的 , 没有方法体
* 该方法需要使用 C 语言编写
*/
public native String helloFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 创建 data 目录下面的 jniLibs
File dir = this.getDir("jniLibs", Context.MODE_PRIVATE);
// 创建 libhello.so
File soFile=new File(dir.getAbsolutePath()+File.separator,"libhello.so");
// 复制 assets 下面的文件
if(FileUtils.copyFileFromAssets(this,"libhello.so",soFile))
{
System.load(soFile.getAbsolutePath());
System.out.println(helloFromJNI());
}
}
public void onClick(View view) {
// 点击按钮显示从 jni 调用得到的字符串信息
Toast.makeText(getApplicationContext(), helloFromJNI(), Toast.LENGTH_LONG).show();
}
}
点击按钮显示
小结:
System.load()有两个含义: 一个是从默认的 jniLibs 下面加载.so 文件二是可以从手机内部的 data/data/
包名/下面加载
SD 卡等外部存储路径是一种可拆卸的(mounted)不可执行(noexec)的储存媒介,不能直接用来
作为可执行文件的运行目录,使用前应该把可执行文件复制到 APP 内部存储再运行。
如果从 sd 加载会出现以下异常
Permission denied!
java.lang.UnsatisfiedLinkError: dlopen failed: couldn't map "/storage/emulated/0/libhello.so" segment 1:
Permission denied
3.1.2. 案例分析二: 加载外部 dex
当前是主 APK 和插件 APK 的经典安例
主 APK:主项目 APK、宿主 APK(Host APK),也就是我们希望采用动态加载技术的主项目;
插件 APK:Plugin,从主项目分离开来,我们能通过动态加载加载到主项目里面来的模块(一个主 APK 可
以同时加载多个插件 APK)
一.创建两个 Demo
Loadfordex 是主 apk plugin_app_cat 是插件 apk 由主 apk 调用插件中的dex
二.编写核心程序
【plugin_app_cat】
com.itheima.plugin.app.Cat
public classCat {
public static String sayHello()
{
//bugfix: return " 汪汪汪!!! ";
return " 喵喵喵喵!!!";
// return " 汪汪汪!!! ";
}
}
开发完签名出一个 apk plugin_app_cat.apk从中可以获取 plugin_app_cat.dex
【loadfordex】
layout/activity_main.xml
demo.itheima.com.loadfordex.MainActivity
加载 dex 文件到 sd 卡目录(此时的 dex 有被注入代码的危险)
public classMainActivity extends AppCompatActivity {
public static final String PLUGIN_APP_CAT_DEX = "plugin_app_cat.dex";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 查看 sd 卡下面的 dex
File dexfile= new
File(Environment.getExternalStorageDirectory()+File.separator,
PLUGIN_APP_CAT_DEX);
if(!dexfile.exists()){
// 不存在的话加载 可执行文件 dex
FileUtils.copyFileFromAssets(this, PLUGIN_APP_CAT_DEX, dexfile);
}
}
在按钮点击事件中调用插件程序的类(要好好掌握反射技术 Class Method Field 的使用)
public void invokeDexMethod(Viewview) {
// 需要装载的 APK 或者 Jar 文件的路径。包含多个路径用 File.pathSeparator 间隔开 , 在
Android 上默认是 ":"
String dexPath = Environment.getExternalStorageDirectory()+File.separator +
PLUGIN_APP_CAT_DEX;
// 优化后的 dex 文件存放目录,不能为 null
String optimizedDirectory = this.getDir("dex", 0).getAbsolutePath();
// 目标类中使用的 C/C++ 库的列表 , 每个目录用 File.pathSeparator 间隔开 ; 可以为 null
String libraryPath = null;
ClassLoader parent = getClassLoader();//DexClassLoader 父类加载器 一般是当前类的
加载器
// 创建 classLoader
DexClassLoader loader = new DexClassLoader(dexPath, optimizedDirectory,
libraryPath,parent);
// 调用外部 dex 中的代码 dexClassLoader
//com.itheima.plugin.app.Cat.sayHello()
try {
Class clz = loader.loadClass("com.itheima.plugin.app.Cat");
Method method = clz.getMethod("sayHello");
String result= (String) method.invoke(null,new Object[]{});
Toast.makeText(MainActivity.this,result, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
三.使用 debug 调试
四.运行效果
五.小结
Jar与 dex:有的 Android 应用能直接加载.jar 文件,那是因为这个.jar 文件已经经过优化,只不过后缀
名没改(其实已经是.dex 文件)
DexClassLoader:可以加载 jar/apk/dex,可以从 SD卡中加载未安装的 apk;
DexClassLoader(dexPath, optimizedDirectory,libraryPath, parent);参 1 优化后的 dex 参 2 加载器对 dex
的保存路径 3.c++库的路径 4.DexClassLoader 的父类加载器,一般是当前类的加载器
3.1.3. 案例分析三: 加载外部 apk
只需要将 dex 路径换成 apk 路径即可
Apk 虽然不是 dex 或者 jar 但是由于它内容也包含着 dex 所以我们的 DexClassLoader 可以先加载整个 apk
再找出内容的 dex.
小结:动态加载过程
把可执行文件(.so/dex/jar/apk)拷贝到应用 APP内部存储
加载可执行文件
调用具体的方法执行业务逻辑
DexClassLoader 可以加载 jar/apk/dex,可以从 SD 卡中加载未安装的apk;
PathClassLoader只能加载系统中已经安装过的 apk;
3.2. 动态加载框架
3.2.1. 案例分析一:android-pluginmgr 动态 Activity
一.Android-pluginmgr 是什么?
android-pluginmgr 是动态加载未安装 apk 的框架
特点:
插件为普通 apk,无须依赖任何 jar
Activity 生命周期由系统自己管理
使用简单,只需要了解一个类 PluginManager 的两个方法
启动 Activity 的效率高
不修改插件,被加载的插件仍然可以独立安装。
简单地说是一个高效的使用代码简洁的加载 apk 的框架。(所有的 apk 都是插件 apk 不需要安装)
二. 快速开始
下载地址
https://github.com/houkx/android-pluginmgr
》》1.下载并解压
》》2.创建 android Mode 并且建立依赖
Puluginmgr 为pluginmgr 框架 创建时设置为 library
Demopluginmgr 为宿主 apk 创建时设置为 application 当前工程依赖 Puluginmgr
【注意】由于当前工程不是标准的 android studio 工程建议大家不要导入。而是复制代码到新建工程
只要复制代码过来 修改下包名为 androidx.pluginmgr
》》3.往 sd 卡根据目录 push 任意的apk 应用
》》4.运行宿主应用
三. 经典实例
》》1.创建 Android Module
》》2.依赖框架
》》3.创建 Application 实例化 PluginManager
PluginManager 为pluginmgr 框架的核心类。负责动态加载未安装的 apk.
demo.itheima.com.demopluginmanager.HostApp
public classHostApp extends Application {
@Override
public void onCreate(){
super.onCreate();
// 初始化插件引擎
PluginManager.init(this);
}
}
》》4.配置功能清单
HostApp/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="demo.itheima.com.demopluginmanager">
<!-- 如果插件 apk 是从 sd 卡加载的话需要配置 sd 相关权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="demo.itheima.com.demopluginmanager.HostApp"
android:supportsRtl="true"
android:theme="@style/AppTheme">
//...
<!-- 使用动态创建 Activity 必需 -->
<activity android:name="androidx.pluginmgr.DynamicActivity" />
</application>
</manifest>
》》5.代码加载 apk
demo.itheima.com.demopluginmanager.MainActivity
当前继承 Activity 实现即可
public void open(View view)
{
String pluginApk = Environment.getExternalStorageDirectory() +File.separator +
"/pluginapp-debug.apk";
File myPlug = new File(pluginApk);
if(myPlug.exists())
{
try {
// 获取引擎单例
PluginManager mgr = PluginManager.getSingleton();
// 获取加载进来的 apk
Collection<PlugInfo> plugInfos =mgr.loadPlugin(myPlug);
// 组件信息 PlugInfo 包含信息丰富
PlugInfo plug =plugInfos.iterator().next();
// 打开 apk 主页面 当前是手机杀毒页面
mgr.startMainActivity(this,plug);// 参 1 上下文 参 2 插件信息
} catch (Exception e) {
e.printStackTrace();
}
}
}
androidx.pluginmgr.environment.PlugInfo 是什么?PluginManager 加载并且初始化的一些资源的封装。可
以点击源代码查看
可以看出有了插件 apk 包含的重要对象 service receiver providers 还有类加载器 ClassLoader.应用上下文
资源管理者 AssetManager,还包括资源 Resources 对象。
》》6.运行效果
正常调用应用的界面
3.2.2. 案例分析二:DroidPlugin
一.DroidPlugin 是什么?
DroidPlugin 是 360 手机助手在 Android 系统上实现了一种新的插件机制:
它可以在无需安装、修改的情况下运行 APK 文件,此机制对改进大型 APP 的架构,实现多团队协作开发
具有一定的好处。
二.快速开始
下载地址
https://github.com/Qihoo360/DroidPlugin
下载完成后解压当前的压缩包
直接导入到当前的 android studio
里面有包含 ndk 相关的配置 未安装 ndk 的话需要进行查看 NDK 环境配置
往 sd 卡 push 可以正常运行的apk
adb push xxx.apk mnt/sdcard/
运行 TestPlugin 可查看
可以进行以下操作
三.经典案例
》》1.创建 Demo
》》2.依赖 DroidPlugin 框架
HostApp/build.gradle
dependencies{
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:23.3.0'
compile project(':Libraries:DroidPlugin')
compile 'com.google.android.gms:play-services-appindexing:8.1.0'
}
》》3.在 Application 中初始化框架
demo.itheima.com.hostapp.HostApplication
public classHostApplication extends Application {
@Override
public void onCreate(){
super.onCreate();
// 获取插件帮助类
PluginHelperinstance = PluginHelper.getInstance();
// 进行初始化
instance.applicationOnCreate(this);
}
@Override
protected void attachBaseContext(Context base) {
// 把上下文实例注册到框架中
PluginHelper.getInstance().applicationAttachBaseContext(base);
super.attachBaseContext(base);
}
}
》》4.在功能清单中配置 Appplication 与权限
HostApp/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="demo.itheima.com.hostapp">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:name="demo.itheima.com.hostapp.HostApplication"
android:theme="@style/AppTheme">
//...
</application>
</manifest>
》》5 配置操作界面
项目中根据实际需要 这里为了更清析的演示只是添加了简洁界面
layout/activity_main.xml
》》6.基本操作
demo.itheima.com.hostapp.MainActivity
public classMainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getName();
private String mFilePath = Environment.getExternalStorageDirectory() +
File.separator + "pluginapp-debug.apk";
private String mPackageName = "demo.itheima.com.pluginapp";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void scan(View view)
{
int flags = 0;
StringBuffersb = new StringBuffer();
try {
// 查找 框架中的已经加载的 apk
List<ApplicationInfo> list =
PluginManager.getInstance().getInstalledApplications(flags);
for (ApplicationInfo item : list) {
Log.i(TAG, "onCreate: " + item.packageName);
sb.append(item.processName).append("\n");
}
} catch (RemoteException e2) {
e2.printStackTrace();
}
Toast.makeText(MainActivity.this, sb.toString(),
Toast.LENGTH_SHORT).show();
}
public void install(View view) {
int flags = 0;// 代表未加载状态
//int flags=PackageManagerCompat.INSTALL_REPLACE_EXISTING;// 加载新 apk 替换
已经存在
try {
// 加载插件 apk 到当前框架中
PluginManager.getInstance().installPackage(mFilePath, flags);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void unInstall(View view){
int flags = 0;
try {
// 从已经加载的框架中移除插件
PluginManager.getInstance().deletePackage(mPackageName, flags);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void deleteApk(View view){
// 删除插件 apk 的文件
new File(mFilePath).deleteOnExit();
}
public void openApk(View view) {
try {
// 既然插件已经加载到框架中了。就可以通过隐式意图打开它
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(mPackageName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
》》7.小结
PluginHelper:插件帮助类 负责初始化整体框架
PluginManager:与系统的 PackageManager 有点类似
getInstalledApplications:获取框架已经加载的 apk 信息
installPackage:加载指定路径的 apk 到框架中
deletePackage:删除框架中的 apk
4. 其它
4.1.Android6.0 运行时权限
private voidstartLoad() {
if(ActivityCompat.checkSelfPermission(getActivity(),
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
startLoadInner();
} else {
requestPermissions(new
String[]{Manifest.permission.READ_EXTERNAL_STORAGE},0x1);
}
}
源 码 中 被 用 来 检 查 和 请 求 权 限 的 方 法 分 别 是 Activity 的 checkSelfPermission 和
requestPermissions。这些方法api23引入。
ActivityCompat:android support v4 支持权限判断
requestPermissions:支持请求获取权限
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,int[]
grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 0x1) {
if(permissions != null && permissions.length > 0) {
for (int i =0; i < permissions.length; i++) {
String permisson = permissions[i];
int grantResult = grantResults[i];
if(Manifest.permission.READ_EXTERNAL_STORAGE.equals(permisson)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
startLoadInner();
} else {
Toast.makeText(getActivity(), " 没有授权,无法使用",
Toast.LENGTH_SHORT).show();
}
}
}
for (String permisson : permissions) {
}
}
}
}
4.2.NDK 环境变量配置
4.3.Gradle 环境变量配置
Gralde 的下载地址
http://services.gradle.org/distributions
将 bin 目录添加到 path
C:\Users\Administrator\.gradle\wrapper\dists\gradle-2.10-all\bin;添加到 path
重新启动电脑后在 cmd 窗口中执行 gradle -v
安装了 android stuido 后 还得自己安装gradle
>>1 下载
>>2.解压
>>3.配置 path
>>4.重启
4.3.1. Jar 与 与 aar 区别是什么?
https://github.com/kaedea/android-dynamical-loading
要输出 aar 文件,必须将 Module 配置为 library,在 gradle 文件中如下:
输出 aar : apply plugin: 'com.android.library'; 输出 apk:apply plugin: 'com.android.application'。
一份 aar 文件其实就是一份 zip 包,和 jar 不同的是,它将一些资源文件、第三方库文件、so 文件等
等都打包在内,而代码文件编译后压缩在在 classes.jar 中
4.3.2. Android studio 的六种依赖
Compile
compile 是对所有的 build type 以及 favlors 都会参与编译并且打包到最终的 apk 文件中。
Provided
Provided 是对所有的 build type 以及 favlors只在编译时使用,类似 eclipse 中的 external-libs,只参与编译,
不打包到最终 apk。
APK
只会打包到 apk 文件中,而不参与编译,所以不能再代码中直接调用 jar 中的类或方法,否则在编译时
会报错
Test compile
Test compile 仅仅是针对单元测试代码的编译编译以及最终打包测试 apk 时有效,而对正常的 debug 或
者 release apk 包不起作用。
Debug compile
Debug compile 仅仅针对 debug 模式的编译和最终的 debugapk 打包。
Release compile
Release compile 仅仅针对 Release 模式的编译和最终的 Release apk 打包。
1.1. 概念
一.渠道是什么?
可以下载 apk 的地方(具体讲就是应用商店或者应用市场)。只要把自己打包的应用上传到这些渠道。用
户就可以下载了,自己的应用也就推广出去了。同时这些渠道还可以将 app 下载的数据反馈给开发者。
二.常用见的渠道有哪些?
Google Play : https://play.google.com/apps/publish
应用汇: http://dev.appchina.com
机锋市场 : http://dev.gfan.com/
91 和安卓市场 : http://dev.apk.hiapk.com/
//说明一下,发布在安卓市场也会发布到 91 市场,他们其实同一家了
安智(goapk) : http://dev.anzhi.com/
木蚂蚁 : http://dev.mumayi.com/
N 多网 : http://www.nduoa.com/developer
联想乐商店 : http://developer.lenovomm.com/developer/
十字猫 : http://dev.crossmo.com/
腾讯应用宝 : http://tap.myapp.com/android/index.jsp
飞流 : http://dev.feiliu.com/
智汇云 : https://dev.hicloud.com/indexManageAction.action
小米应用商店 : http://developer.xiaomi.com/
百度 : http://developer.baidu.com/
魅族 : http://developer.meizu.com/
易优 : http://www.eomarket.com/user/login/lang/zh_CN
手机之家 : http://profile.imobile.com.cn
联通沃商店 : http://dev.wo.com.cn/userportal/mapc_login.action
爱卓网 : http://www.iandroid.cn/index.php?app=my_sharegoods&act=add&is_original=1
三.渠道统计的原理
1 为 apk 打标记
一般有两种实现方式 1、在代码中写(硬编码) 2 在 AndroidManifext.xml 中填写(元数据 meta-data)
2 运行 apk 的时候取出标识
3 上传标识给服务器统计
4 开发者使用账号登录查看统计数据
1.2. 基本工作
Umeng App 统计
1 为 app 配置每个渠道的元数据 手动地一个一个打包(相当费时)
例:使用友盟统计获取渠道数据
一.添加依赖
day02demoapk/build.gradle
compile 'com.umeng.analytics:analytics:latest.integration'
二.页面代码编写
day01.itheima.com.day02demoapk.MainActivity
public classMainActivity extends AppCompatActivity {
//...
public void onResume(){
super.onResume();
MobclickAgent.onResume(this);
}
public void onPause() {
super.onPause();
MobclickAgent.onPause(this);
}
}
三.功能清单元数据配置
day02demoapk/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
...>
<meta-data android:value="573e8baae0f55a3532000010"
android:name="UMENG_APPKEY"></meta-data>
<meta-data android:value="wandoujia" android:name="UMENG_CHANNEL"/>
</application>
四.账号登录查看
五小结:
以上是友盟的打包流程。刨去友盟统计的基本使用方法,打包的部分只是步骤 3meta-data 配置。但是一
个一个地去打包 如果只有 2,3 个还好,但如果要 100 个就很耗时。(UMENG_APPKEY 需要大家去申请)
1.3.Gradle 打包(Generate Signed APK 单个)
如果是单独打一个 APP 包加上上面的配置直接 IDE 中导出 APK 就行了,如果打很多个 APK 用
IDE 就很费力.下面了解一下 gradle如何手动打包一个 apk
一.检查 jks(如果有的话就可以直接跳过)
Eclispe 导出已经签名的 apk 使用的证书后缀叫 keystore,但是在 androidstudio 下面后缀叫 jks
二..点击 Build ,在下拉框中选择 "Generate Signed APK"
三..选择 "Create new"
四..按照里面的内容填写即可,注意最后文件的扩展名变为".jks",而不是以前的".keystore".
五.配置签名 jks
1>点击依赖
2>选中 module
3>点击+号代码创建
4>后面按格填写后点击 OK
以上操作实质是在
day02demoapk/build.gradle
中生成以下配置
六. 选择打包 release 或者 debug
>1 打开 gradle 脚本窗口
>2 选中 assembleDebug 或者 assembleRelease (选前面就编译 debug 下的 apk 选后者就是 release)
>3,点击 run
>4.查看生成的 apk
1.4.Gradle 打包(productFlavors 完成批量)
使用占位符与 productFlavors 变量完成批量打包
day02demoapk/src/main/AndroidManifest.xml
>>1.配置占位符
day02demoapk/build.gradle
》》2.对安卓 module 添加多个 productFlavors
》》3.对占位符(placeholders)进行赋值
// 配置占位符的值 ( 渠道名称 )
productFlavors {
AppChina{
manifestPlaceholders=[UMENG_CHANNEL:"AppChina"]
}
wandoujia {
manifestPlaceholders =[UMENG_CHANNEL: "wandoujia"]
}
baidu {
manifestPlaceholders =[UMENG_CHANNEL: "baidu"]
}
c360 {
manifestPlaceholders =[UMENG_CHANNEL: "c360"]
}
uc{
manifestPlaceholders =[UMENG_CHANNEL: "uc"]
}
}
》》4.执行批量打包操作(图形化)
后续就正常填写就行啦
点击 finish 后就生成了
这种占位符的方式呢?开发者只需配上占位符的值(具体的话就是渠道名称 不能是中文)执行生成apk操
作就可以啦。这个是可以满足批量打包的,效率高!
可以进一步使用反编译工具查看功能清单里面的渠道变量
1.5. 美团打包方案一分钟 900 个
美团技术团队
http://tech.meituan.com/mt-apk-packaging.html
==》
基本原理
因为 apk 的 meta-info 下的文件不参与签名,所以可在 meta-info 下面生成一个内容为空的文件, 渠道名的
前缀为 mtchannel_。应用程就可以使用 ZipFile读取文件名取得 mtchannel_的后面的字符串 例
mtchannel_c360 的 360 字符串然后发送给服务端,就可以得出渠道统计数据。其中批量打包在使用
python工具完成。
所以只要完成两个步骤就可以
一.下载相关工具
https://github.com/GavinCT/AndroidMultiChannelBuildTool
二.生成一个已经签名的 apk 程序内部使用 java 工具获取 meta-data 下面的渠道变量
day01.itheima.com.day02demoapk.MainActivity
public void showChannel(View view)
{
// 获取当前 apk 的渠道变量值发送给服务端
String channel = ChannelUtil.getChannel(this);
Log.i(TAG, "onCreate: "+channel);
Toast.makeText(this,"channel="+channel,Toast.LENGTH_LONG).show();
}
ChannelUtil 可以从下载的工具代码里面复制
三.运行 python 工具对该apk 进行复制与修改渠道变量
搭建 python 环境
下载地址
https://www.python.org/downloads/windows/
下载完成后点击该 exe 文件进行安装
安装完成后配置环境变量
再使用 cmd 命令行测试下 python
脚本的工作原理
一.首先创建一个空文件,等待写入 META-INF 目录作为 channel_xxx 文件
二.获取渠道列表。
三.找到初始 apk
四.遍历渠道号并写入 apk。
执行 python 脚本
AndroidMultiChannelBuildTool-master\PythonTool
把已经签名的 apk 放置在当前目录
双击执行 MultiChannelBuildTool.py 脚本
点开生成文件夹里面都是
安装后点击可以看到当前的渠道变量
2. 增量更新
Window 系统的升级过程
Android 系统的升级过程
Android studio 的升级过程
国内某些应用市场应用升级过程
自从 android 4.1 开始Google play 引入应用的增量更新功能。App 使用增量更新的方式可节省 2/3 的
流量。
增量更新的原理
将手机上已安装 apk 与服务器端最新 apk 进行二进制对比,得到差分包,用户更新程序时,只需要下载
差分包,并在本地使用差分包与已安装 apk,合成新版 apk。
例如,当前手机中已安装微博 V1,大小为 12.8MB,现在微博发布了最新版 V2,大小为 15.4MB,我们
对两个版本的 apk 文件差分比对之后,发现差异只有 3M,那么用户就只需要要下载一个 3M 的差分包,
使用旧版 apk 与这个差分包,合成得到一个新版本 apk,提醒用户安装即可,不需要整包下载 15.4M 的
微博 V2 版 apk。
共需要做 3 件事:
1 在服务器端,生成两个版本 apk 的差分包;
2 在手机客户端,使用已安装的 apk 与这个差分包进行合成,得到新版的微博 apk;
3 校验新合成的 apk 文件是否完整,MD5 或 SHA1 是否正确,如正确,则引导用户安装
支持差分与合成的开源工具
开源的二进制比较工具 bsdiff 来实现
http://www.daemonology.net/bsdiff/
Window 版本下载地址
http://sites.inka.de/tesla/others.html#bsdiff
因为 bsdiff 依赖 bzip2,所以我们还需要用到 bzip2
http://www.bzip.org/downloads.html
bsdiff 中,bsdiff.c 用于生成差分包,bspatch.c 用于合成文件。
服务端生成差异包
你的 apk 已经发布了 3 个版,V1.0、V2.0、V3.0,这时候你要在后台发布 V4.0,那么,当你在服务器上
传最新的 V4.0 包时,服务器端就应该立即生成以下差分包:
V1.0 ——> V4.0的差分包;
V2.0 ——> V4.0的差分包;
V3.0 ——> V4.0的差分包;
2.1. 差分包提取与合成
Desktop\tools
》》1. 生成差分包
Cd 到当前目录 使用命令
bsdiff.exeoldfile newfile patchfile
当前使用 Weibo5.5.apk+Weibo5.6.apk ==>生成差分包 Weibo5.5-5.6.patch
bsdiff.exe Weibo5.5.apk Weibo5.6.apk Weibo5.5-5.6.patch
运行时间稍长
》》2. 合成差分包
Cd 当前目录 使用命令
bspatch.exe oldfile newfile patchfile
当前使用 Weibo5.5.apk+差分包 Weibo5.5-5.6.patch==》Weibo5.6.new.apk
bspatch.exe Weibo5.5.apk Weibo5.6.new.apk Weibo5.5-5.6.patch
运行时间稍长
2.2. 客户端使用差异包合成完整 apk
进入下载页面获取项目的 zip
https://github.com/cundong/SmartAppUpdates
成功下载后解压得到
ApkPatchLibraryServer 服务器端生成差分包工程
ApkPatchLibrary 客户端使用的 apk 合成库,用于生成 libApkPatchLibrary.so
ApkPatchLibrarySample 一个 Sample,手机上安装 Weibo5.5.apk,通过与 SD 卡上预先存放的
weibo.patch 文件进行合并,得到 Weibo5.6.apk
导入 android模块
注意还需要添加依赖 design
day02demosmartappupdates/build.gradle
dependencies{
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'
}
运行后界面
普通的模拟器使用 adb 命令安装以下 Weibo5.5.apk
而夜神的话要使用 nox_adb 作为命令
添加文件到 sdcard 这样写 nox_adb push apk 路径 mnt/sdcard
出现安装界面代表合成完整 apk 成功
2.2.1. 案例分析一
一.导入 SmartAppUpdates 中的 so 库和对应生成的 jar
注意 PatchUtils 的包名必须保持不变 这个是 jni 技术的要求
二.加载 so 库
public classMainActivity extends AppCompatActivity {
static {
System.loadLibrary("ApkPatchLibrary");
}
//...
}
三.添加权限
day02demo_updates/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="day01.itheima.com.day02demo_updates">
<!-- 写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 读权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
//...
四.代码编写
layout/activity_main.xml(xml 布局比较简单 不粘贴代码)
day01.itheima.com.day02demo_updates.MainActivity
public classMainActivity extends AppCompatActivity implements Runnable{
public static final String UPDATE1_2_PATCH= "update1-2.patch";
static {
System.loadLibrary("ApkPatchLibrary");
}
private TextView mTv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mTv.setText(" 当前版本是 1");
}
private voidinitView() {
mTv = (TextView) findViewById(R.id.main_bnt_check);
}
public void checkPatch(View view)
{
// 查看补丁文件是否存在
File file = new File(Environment.getExternalStorageDirectory(), UPDATE1_2_PATCH);
// 存在
if(file.exists())
{
Toast.makeText(MainActivity.this, "在 存在 开始合并!",
Toast.LENGTH_SHORT).show();
new Thread(this).start();
}else// 不存在给出提示
{
Toast.makeText(MainActivity.this, " 不存在!", Toast.LENGTH_SHORT).show();
}
}
@Override
public void run() {
String oldapk= ApkUtils.getSourceApkPath(this,getPackageName());
final String
newapk=Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+System.cur
rentTimeMillis()+".apk";
String
pathPath=Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+UPDATE1_
2_PATCH;
int patchCode = PatchUtils.patch(oldapk, newapk, pathPath);// 参 1 旧文件 参 2
合成 apk 参 3 补丁文件
if(0==patchCode)
{
runOnUiThread(new Runnable() {
@Override
public void run() {
ApkUtils.installApk(MainActivity.this,newapk);
}
});
}
}
}
注意
day01.itheima.com.day02demo_updates.utils.ApkUtils#installApk 可以增加一个 flag
五.签名出两个 apk
注意版本 1 保持不变 版本 2 只要改 textview 的显示内容为 2 即可。
六.使用 bsdiff.exe 生成补丁,Push 补丁到 sdcard 根据路径
七.卸载 版本 1 重新安装 点击运行生成新 2.0apk
能够获取安装界面代表 通过差分包+版本 1.0 成功合成版本 2.0
2.3. 补丁修复框架
最新 github 上开源了很多热补丁动态修复框架,大致有:
https://github.com/dodola/HotFix
https://github.com/jasonross/Nuwa
https://github.com/bunnyblue/DroidFix
2.4.AndFix 热修复
2.4.1. AndFix 是什么?
阿里巴巴开源 Android App 线上问题修复工具!
AndFix 这个库为 Android App 提供了热修复的功能。它能够帮助 Android 开发人员在线修复 App Bug。
Andfix 是" And roid hot- fix "首字母缩写。
AndFix 支持 Android 2.3 至 6.0, arm 和 x86 架构, dalvik runtime 和 art runtime.
Github 下载地址
https://github.com/alibaba/AndFix
2.4.2. 原理
版本 1 发现有一个 bug,那就修复并且测试这个 Bug 签名出版本 2.
针对版本 1 与版本 2 进行 差分 生成 patch 即补丁文件
通过网络下载获取补丁文件 使用 AndFix 动态加载进用户正在使用的版本 1( 即 v1+patch=>v2)
Bug 被修复
2.4.3. 案例分析
》》1.创建 Demo
》》2.复制 Bug 程序
day01.itheima.com.day02demo_nuwa.R.layout#activity_main
day01.itheima.com.day02demo_nuwa.Cat
public classCat {
public static String sayHello()
{
//bugfix: return " 汪汪汪!!! ";
// return " 喵喵喵喵!!! ";
return " 汪汪汪!!!";
}
}
猫能汪汪叫吗?显示不能,所以是一个明显的简单的模拟 Bug
day01.itheima.com.day02demo_nuwa.MainActivity
public classMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void show(View view)
{
Cat cat = new Cat();
Toast.makeText(MainActivity.this,cat.sayHello() ,
Toast.LENGTH_SHORT).show();
}
}
》》3.配置 AndFix
DemoAndfix/build.gradle
dependencies{
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.alipay.euler:andfix:0.3.1@aar' // 依赖 AndFix 热补丁框架
}
》》4.编写 Andfix 加载 patch 代码
day01.itheima.com.day02demo_nuwa.MyApplication
public class MyApplication extends Application {
privatestatic final StringAPATCH_PATH ="hot.apatch";
privatestatic final StringTAG = MyApplication.class.getName();
/**PatchManager 补丁管理者 AndFix 的核心类 复 **/
privatePatchManager mPatchManager;
@Override
public void onCreate() {
super.onCreate();
// 创建实例
mPatchManager = new PatchManager(this);
mPatchManager.init("1.0");// 参 1 版本号 可以通过 PacakgeManager 获取
// 加载补丁
mPatchManager.loadPatch();
// 运行时添加补丁
try {
// .apatch 文件路径
File patch = new
File(Environment.getExternalStorageDirectory().getAbsolutePath(),APATCH_PATH);
String patchFileString=patch.getAbsolutePath();
if(patch.exists())
{
// 加载补丁
mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " 添加");
}
} catch(IOException e) {
Log.e(TAG, "", e);
}
}
}
DemoAndfix/src/main/AndroidManifest.xml
<!-- 补丁一般放在 sd 卡所以需要配置 sd 卡的访问权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:name=".MyApplication"
【要点】
PatchManager:AndFix热补丁框架的核心类:补丁管理者 负责动态加载 .patch 文件
Init 初始化方法
loadPatch 加载默认路径上的补丁文件
addPatch:添加指定路径上的补丁文件
》》5.使用 apk 签名出两个版本的 apk
version2-fixed.apk 是这样修复 bug 的
public classCat {
public static String sayHello()
{
//bugfix: return " 汪汪汪!!! ";
return " 喵喵喵喵!!!";
// return " 汪汪汪!!! ";
}
}
猫当然是 喵喵喵喵叫啦(当然这里只是一个示意,真实的企业项目肯定比这个复杂)
》》6.使用 apkPatch 工具进行差分生成补丁
apkPatch 下载AndFix 框架时包含的一个生成补丁的工具包
所以解压下载的文件 AndFix-master.zip 就可以看到 apkPatch 工具包了。
怎么 apkPatch 使用?使用cmd 打开命令窗口就可以查看到用法!
具体语法是?
apkpatch.bat -f version2-fixed.apk -t version1.apk -o patch -k itheima.jks -p
testtest -a itheima -etesttest
具体含义
-f 新版本 apk 即修复后的 apk
-t 旧版本 apk 即修复前的 apk
-o 差分包即 patch 补丁文件存放的文件夹当前为 patch 文件夹
-k 证书 eclipse 下为*.keystore 而在 androidstudio 下是*.jks 实验证明可以混用
-p 使用密码 当前是 testest
-a 别名 当前是 itheima
-e 别名对应的密码 当前是 testest
在把两个 apk版本放到当前目录下执行生成 patch 文件。然后从patch文件夹复制出来
【备注】hot.apatch一定需要跟 Application 里面的补丁文件名称保持一致!
》》7.复制到 sd 卡目录下即可
此处 因为使用夜神的模拟器所以使用 nox_adb 防止跟 sdk 中的 adb冲突
》》8.重新打开原程序
3. 动态加载
3.1. 动态加载基础 DexClassLoader
一.动态加载
在程序运行的时候,加载一些程序自身原本不存在的可执行文件并运行这些文件里的代码逻辑。
看起来就像是应用从服务器下载了一些代码,然后再执行这些代码!
启动页面:
现在大部分 APP 都有一个启动页面,如果到了一些重要的节日,APP 的服务器会配置一些与时节相关的
图片,APP 启动时候再把原有的启动图换成这些新的图片,这样就能提高用户的体验了
规避安卓市场审核
安卓市场开始扫描 APK 里面的 Manifest 甚至 dex 文件,查看开发者的 APK 包里是否有广告的代码,如果
有就有可能审核不通过。
先不要在 APK 写广告的代码,在用户运行 APP的时候,再从服务器下载广告的代码,运行,再现实广告
二.实现原理
Java 的可执行文件是 Jar,运行在虚拟机上 JVM 上,虚拟机通过 ClassLoader 加载 Jar 文件并执行里面的
代码。所以 Java 程序也可以通过动态调用 Jar 文件达到动态加载的目的。
3.1.1. 案例分析一: 加载外部.so
从 JniLib 目录加载(这个是 android studio 默认目录)
为了方便演示从网上下载一个最简单的 helloworld 的 demo
https://github.com/han1202012/NDKHelloworld
下载后在本地创建 demo(根据 jni 的开发要求不能修改包名所以包名是shuliang.han.nkdhellowold)
layout/activity_main.xml
shuliang.han.ndkhelloworld.MainActivity
public classMainActivity extends Activity{
// 静态代码块加载 C 语言库文件
static{
System.loadLibrary("hello");
}
/*
* 声明一个 native 方法
* 这个方法在 Java 中是没有实现的 , 没有方法体
* 该方法需要使用 C 语言编写
*/
public native String helloFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println(helloFromJNI());
}
public void onClick(View view) {
// 点击按钮显示从 jni 调用得到的字符串信息
Toast.makeText(getApplicationContext(), helloFromJNI(),
Toast.LENGTH_LONG).show();
}
}
点击后运行效果为
到此代表 jni 工程部署成功。
下面只需要另外创建一个 demo 用来测试从手机内部加载 so 文件
注意 main 下面 jniLibs 已经被删除了,同时创建 assets 目录复制 libhello.so
shuliang.han.ndkhelloworld.MainActivity
public classMainActivity extends Activity{
/*
* 声明一个 native 方法
* 这个方法在 Java 中是没有实现的 , 没有方法体
* 该方法需要使用 C 语言编写
*/
public native String helloFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 创建 data 目录下面的 jniLibs
File dir = this.getDir("jniLibs", Context.MODE_PRIVATE);
// 创建 libhello.so
File soFile=new File(dir.getAbsolutePath()+File.separator,"libhello.so");
// 复制 assets 下面的文件
if(FileUtils.copyFileFromAssets(this,"libhello.so",soFile))
{
System.load(soFile.getAbsolutePath());
System.out.println(helloFromJNI());
}
}
public void onClick(View view) {
// 点击按钮显示从 jni 调用得到的字符串信息
Toast.makeText(getApplicationContext(), helloFromJNI(), Toast.LENGTH_LONG).show();
}
}
点击按钮显示
小结:
System.load()有两个含义: 一个是从默认的 jniLibs 下面加载.so 文件二是可以从手机内部的 data/data/
包名/下面加载
SD 卡等外部存储路径是一种可拆卸的(mounted)不可执行(noexec)的储存媒介,不能直接用来
作为可执行文件的运行目录,使用前应该把可执行文件复制到 APP 内部存储再运行。
如果从 sd 加载会出现以下异常
Permission denied!
java.lang.UnsatisfiedLinkError: dlopen failed: couldn't map "/storage/emulated/0/libhello.so" segment 1:
Permission denied
3.1.2. 案例分析二: 加载外部 dex
当前是主 APK 和插件 APK 的经典安例
主 APK:主项目 APK、宿主 APK(Host APK),也就是我们希望采用动态加载技术的主项目;
插件 APK:Plugin,从主项目分离开来,我们能通过动态加载加载到主项目里面来的模块(一个主 APK 可
以同时加载多个插件 APK)
一.创建两个 Demo
Loadfordex 是主 apk plugin_app_cat 是插件 apk 由主 apk 调用插件中的dex
二.编写核心程序
【plugin_app_cat】
com.itheima.plugin.app.Cat
public classCat {
public static String sayHello()
{
//bugfix: return " 汪汪汪!!! ";
return " 喵喵喵喵!!!";
// return " 汪汪汪!!! ";
}
}
开发完签名出一个 apk plugin_app_cat.apk从中可以获取 plugin_app_cat.dex
【loadfordex】
layout/activity_main.xml
demo.itheima.com.loadfordex.MainActivity
加载 dex 文件到 sd 卡目录(此时的 dex 有被注入代码的危险)
public classMainActivity extends AppCompatActivity {
public static final String PLUGIN_APP_CAT_DEX = "plugin_app_cat.dex";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 查看 sd 卡下面的 dex
File dexfile= new
File(Environment.getExternalStorageDirectory()+File.separator,
PLUGIN_APP_CAT_DEX);
if(!dexfile.exists()){
// 不存在的话加载 可执行文件 dex
FileUtils.copyFileFromAssets(this, PLUGIN_APP_CAT_DEX, dexfile);
}
}
在按钮点击事件中调用插件程序的类(要好好掌握反射技术 Class Method Field 的使用)
public void invokeDexMethod(Viewview) {
// 需要装载的 APK 或者 Jar 文件的路径。包含多个路径用 File.pathSeparator 间隔开 , 在
Android 上默认是 ":"
String dexPath = Environment.getExternalStorageDirectory()+File.separator +
PLUGIN_APP_CAT_DEX;
// 优化后的 dex 文件存放目录,不能为 null
String optimizedDirectory = this.getDir("dex", 0).getAbsolutePath();
// 目标类中使用的 C/C++ 库的列表 , 每个目录用 File.pathSeparator 间隔开 ; 可以为 null
String libraryPath = null;
ClassLoader parent = getClassLoader();//DexClassLoader 父类加载器 一般是当前类的
加载器
// 创建 classLoader
DexClassLoader loader = new DexClassLoader(dexPath, optimizedDirectory,
libraryPath,parent);
// 调用外部 dex 中的代码 dexClassLoader
//com.itheima.plugin.app.Cat.sayHello()
try {
Class clz = loader.loadClass("com.itheima.plugin.app.Cat");
Method method = clz.getMethod("sayHello");
String result= (String) method.invoke(null,new Object[]{});
Toast.makeText(MainActivity.this,result, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
三.使用 debug 调试
四.运行效果
五.小结
Jar与 dex:有的 Android 应用能直接加载.jar 文件,那是因为这个.jar 文件已经经过优化,只不过后缀
名没改(其实已经是.dex 文件)
DexClassLoader:可以加载 jar/apk/dex,可以从 SD卡中加载未安装的 apk;
DexClassLoader(dexPath, optimizedDirectory,libraryPath, parent);参 1 优化后的 dex 参 2 加载器对 dex
的保存路径 3.c++库的路径 4.DexClassLoader 的父类加载器,一般是当前类的加载器
3.1.3. 案例分析三: 加载外部 apk
只需要将 dex 路径换成 apk 路径即可
Apk 虽然不是 dex 或者 jar 但是由于它内容也包含着 dex 所以我们的 DexClassLoader 可以先加载整个 apk
再找出内容的 dex.
小结:动态加载过程
把可执行文件(.so/dex/jar/apk)拷贝到应用 APP内部存储
加载可执行文件
调用具体的方法执行业务逻辑
DexClassLoader 可以加载 jar/apk/dex,可以从 SD 卡中加载未安装的apk;
PathClassLoader只能加载系统中已经安装过的 apk;
3.2. 动态加载框架
3.2.1. 案例分析一:android-pluginmgr 动态 Activity
一.Android-pluginmgr 是什么?
android-pluginmgr 是动态加载未安装 apk 的框架
特点:
插件为普通 apk,无须依赖任何 jar
Activity 生命周期由系统自己管理
使用简单,只需要了解一个类 PluginManager 的两个方法
启动 Activity 的效率高
不修改插件,被加载的插件仍然可以独立安装。
简单地说是一个高效的使用代码简洁的加载 apk 的框架。(所有的 apk 都是插件 apk 不需要安装)
二. 快速开始
下载地址
https://github.com/houkx/android-pluginmgr
》》1.下载并解压
》》2.创建 android Mode 并且建立依赖
Puluginmgr 为pluginmgr 框架 创建时设置为 library
Demopluginmgr 为宿主 apk 创建时设置为 application 当前工程依赖 Puluginmgr
【注意】由于当前工程不是标准的 android studio 工程建议大家不要导入。而是复制代码到新建工程
只要复制代码过来 修改下包名为 androidx.pluginmgr
》》3.往 sd 卡根据目录 push 任意的apk 应用
》》4.运行宿主应用
三. 经典实例
》》1.创建 Android Module
》》2.依赖框架
》》3.创建 Application 实例化 PluginManager
PluginManager 为pluginmgr 框架的核心类。负责动态加载未安装的 apk.
demo.itheima.com.demopluginmanager.HostApp
public classHostApp extends Application {
@Override
public void onCreate(){
super.onCreate();
// 初始化插件引擎
PluginManager.init(this);
}
}
》》4.配置功能清单
HostApp/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="demo.itheima.com.demopluginmanager">
<!-- 如果插件 apk 是从 sd 卡加载的话需要配置 sd 相关权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="demo.itheima.com.demopluginmanager.HostApp"
android:supportsRtl="true"
android:theme="@style/AppTheme">
//...
<!-- 使用动态创建 Activity 必需 -->
<activity android:name="androidx.pluginmgr.DynamicActivity" />
</application>
</manifest>
》》5.代码加载 apk
demo.itheima.com.demopluginmanager.MainActivity
当前继承 Activity 实现即可
public void open(View view)
{
String pluginApk = Environment.getExternalStorageDirectory() +File.separator +
"/pluginapp-debug.apk";
File myPlug = new File(pluginApk);
if(myPlug.exists())
{
try {
// 获取引擎单例
PluginManager mgr = PluginManager.getSingleton();
// 获取加载进来的 apk
Collection<PlugInfo> plugInfos =mgr.loadPlugin(myPlug);
// 组件信息 PlugInfo 包含信息丰富
PlugInfo plug =plugInfos.iterator().next();
// 打开 apk 主页面 当前是手机杀毒页面
mgr.startMainActivity(this,plug);// 参 1 上下文 参 2 插件信息
} catch (Exception e) {
e.printStackTrace();
}
}
}
androidx.pluginmgr.environment.PlugInfo 是什么?PluginManager 加载并且初始化的一些资源的封装。可
以点击源代码查看
可以看出有了插件 apk 包含的重要对象 service receiver providers 还有类加载器 ClassLoader.应用上下文
资源管理者 AssetManager,还包括资源 Resources 对象。
》》6.运行效果
正常调用应用的界面
3.2.2. 案例分析二:DroidPlugin
一.DroidPlugin 是什么?
DroidPlugin 是 360 手机助手在 Android 系统上实现了一种新的插件机制:
它可以在无需安装、修改的情况下运行 APK 文件,此机制对改进大型 APP 的架构,实现多团队协作开发
具有一定的好处。
二.快速开始
下载地址
https://github.com/Qihoo360/DroidPlugin
下载完成后解压当前的压缩包
直接导入到当前的 android studio
里面有包含 ndk 相关的配置 未安装 ndk 的话需要进行查看 NDK 环境配置
往 sd 卡 push 可以正常运行的apk
adb push xxx.apk mnt/sdcard/
运行 TestPlugin 可查看
可以进行以下操作
三.经典案例
》》1.创建 Demo
》》2.依赖 DroidPlugin 框架
HostApp/build.gradle
dependencies{
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:23.3.0'
compile project(':Libraries:DroidPlugin')
compile 'com.google.android.gms:play-services-appindexing:8.1.0'
}
》》3.在 Application 中初始化框架
demo.itheima.com.hostapp.HostApplication
public classHostApplication extends Application {
@Override
public void onCreate(){
super.onCreate();
// 获取插件帮助类
PluginHelperinstance = PluginHelper.getInstance();
// 进行初始化
instance.applicationOnCreate(this);
}
@Override
protected void attachBaseContext(Context base) {
// 把上下文实例注册到框架中
PluginHelper.getInstance().applicationAttachBaseContext(base);
super.attachBaseContext(base);
}
}
》》4.在功能清单中配置 Appplication 与权限
HostApp/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="demo.itheima.com.hostapp">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:name="demo.itheima.com.hostapp.HostApplication"
android:theme="@style/AppTheme">
//...
</application>
</manifest>
》》5 配置操作界面
项目中根据实际需要 这里为了更清析的演示只是添加了简洁界面
layout/activity_main.xml
》》6.基本操作
demo.itheima.com.hostapp.MainActivity
public classMainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getName();
private String mFilePath = Environment.getExternalStorageDirectory() +
File.separator + "pluginapp-debug.apk";
private String mPackageName = "demo.itheima.com.pluginapp";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void scan(View view)
{
int flags = 0;
StringBuffersb = new StringBuffer();
try {
// 查找 框架中的已经加载的 apk
List<ApplicationInfo> list =
PluginManager.getInstance().getInstalledApplications(flags);
for (ApplicationInfo item : list) {
Log.i(TAG, "onCreate: " + item.packageName);
sb.append(item.processName).append("\n");
}
} catch (RemoteException e2) {
e2.printStackTrace();
}
Toast.makeText(MainActivity.this, sb.toString(),
Toast.LENGTH_SHORT).show();
}
public void install(View view) {
int flags = 0;// 代表未加载状态
//int flags=PackageManagerCompat.INSTALL_REPLACE_EXISTING;// 加载新 apk 替换
已经存在
try {
// 加载插件 apk 到当前框架中
PluginManager.getInstance().installPackage(mFilePath, flags);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void unInstall(View view){
int flags = 0;
try {
// 从已经加载的框架中移除插件
PluginManager.getInstance().deletePackage(mPackageName, flags);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void deleteApk(View view){
// 删除插件 apk 的文件
new File(mFilePath).deleteOnExit();
}
public void openApk(View view) {
try {
// 既然插件已经加载到框架中了。就可以通过隐式意图打开它
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(mPackageName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
》》7.小结
PluginHelper:插件帮助类 负责初始化整体框架
PluginManager:与系统的 PackageManager 有点类似
getInstalledApplications:获取框架已经加载的 apk 信息
installPackage:加载指定路径的 apk 到框架中
deletePackage:删除框架中的 apk
4. 其它
4.1.Android6.0 运行时权限
private voidstartLoad() {
if(ActivityCompat.checkSelfPermission(getActivity(),
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
startLoadInner();
} else {
requestPermissions(new
String[]{Manifest.permission.READ_EXTERNAL_STORAGE},0x1);
}
}
源 码 中 被 用 来 检 查 和 请 求 权 限 的 方 法 分 别 是 Activity 的 checkSelfPermission 和
requestPermissions。这些方法api23引入。
ActivityCompat:android support v4 支持权限判断
requestPermissions:支持请求获取权限
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,int[]
grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 0x1) {
if(permissions != null && permissions.length > 0) {
for (int i =0; i < permissions.length; i++) {
String permisson = permissions[i];
int grantResult = grantResults[i];
if(Manifest.permission.READ_EXTERNAL_STORAGE.equals(permisson)) {
if(grantResult == PackageManager.PERMISSION_GRANTED) {
startLoadInner();
} else {
Toast.makeText(getActivity(), " 没有授权,无法使用",
Toast.LENGTH_SHORT).show();
}
}
}
for (String permisson : permissions) {
}
}
}
}
4.2.NDK 环境变量配置
4.3.Gradle 环境变量配置
Gralde 的下载地址
http://services.gradle.org/distributions
将 bin 目录添加到 path
C:\Users\Administrator\.gradle\wrapper\dists\gradle-2.10-all\bin;添加到 path
重新启动电脑后在 cmd 窗口中执行 gradle -v
安装了 android stuido 后 还得自己安装gradle
>>1 下载
>>2.解压
>>3.配置 path
>>4.重启
4.3.1. Jar 与 与 aar 区别是什么?
https://github.com/kaedea/android-dynamical-loading
要输出 aar 文件,必须将 Module 配置为 library,在 gradle 文件中如下:
输出 aar : apply plugin: 'com.android.library'; 输出 apk:apply plugin: 'com.android.application'。
一份 aar 文件其实就是一份 zip 包,和 jar 不同的是,它将一些资源文件、第三方库文件、so 文件等
等都打包在内,而代码文件编译后压缩在在 classes.jar 中
4.3.2. Android studio 的六种依赖
Compile
compile 是对所有的 build type 以及 favlors 都会参与编译并且打包到最终的 apk 文件中。
Provided
Provided 是对所有的 build type 以及 favlors只在编译时使用,类似 eclipse 中的 external-libs,只参与编译,
不打包到最终 apk。
APK
只会打包到 apk 文件中,而不参与编译,所以不能再代码中直接调用 jar 中的类或方法,否则在编译时
会报错
Test compile
Test compile 仅仅是针对单元测试代码的编译编译以及最终打包测试 apk 时有效,而对正常的 debug 或
者 release apk 包不起作用。
Debug compile
Debug compile 仅仅针对 debug 模式的编译和最终的 debugapk 打包。
Release compile
Release compile 仅仅针对 Release 模式的编译和最终的 Release apk 打包。