参考文章:
测试demo地址:
https://github.com/WanAndroid/tinkerTest
先说下我测试中的问题
issue
1。改变布局xml中内容有效,但是改变java中的内容没有作用。我现在测试的是,可以改变activity_main.xml中的布局,但是代码改变的时候,没有更新。大家解决的话,可以留言给我。
2.使用return退出,关闭activity,并不是退出进程,需要退出进程,再次进入才能更新
配置
API
proguard_rules.pro
AndroidManifest.xml
patch生成
配置
project 下的build.gradle
中添加如下代码
dependencies {
// tinker
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.8.1')
}
app/build.gralde中添加如下依赖
dependencies {
// ...
//可选,用于生成application类
provided('com.tencent.tinker:tinker-android-anno:1.7.7')
//tinker的核心库
compile('com.tencent.tinker:tinker-android-lib:1.7.7')
}
签名的配置
顺便加一下签名的配置:
android{
//...
signingConfigs {
release {
try {
storeFile file("release.keystore")
storePassword "testres"
keyAlias "testres"
keyPassword "testres"
} catch (ex) {
throw new InvalidUserDataException(ex.toString())
}
}
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
文末会有demo的下载地址,可以直接参考build.gradle文件,不用担心这些签名文件去哪找。
API
API主要就是初始化和loadPacth。
正常情况下我们会在Application中onCreate方法中初始化,不过tinker推荐下面的写法:
@DefaultLifeCycle(
application = ".SimpleTinkerInApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class SimpleTinkerInApplicationLike extends ApplicationLike {
public SimpleTinkerInApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
}
@Override
public void onCreate() {
super.onCreate();
TinkerInstaller.install(this);
}
}
pplicationLike通过名字你可能会猜,并非是Application的子类,而是一个类似Application的类。
tinker建议编写一个ApplicationLike的子类,你可以当成Application去使用,注意顶部的注解:@DefaultLifeCycle
,其application属性,会在编译期生成一个SimpleTinkerInApplication类。
所以,虽然我们这么写了,但是实际上Application会在编译期生成,所以AndroidManifest.xml中是这样的:
<application
android:allowBackup="true"
android:name=".SimpleTinkerInApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
</application>
编写如果报红,可以build下,
这个注解背后有个Annotation Processor在做处理,如果你没了解过,可以看下:
Android 如何编写基于编译时注解的项目
通过该文会对一个编译时注解的运行流程和基本API有一定的掌握,文中也会对tinker该部分的源码做解析。
MainActivity中的调用如下方法
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_kill= (Button) findViewById(R.id.btn_kill);
btn_kill.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
android.os.Process.killProcess(android.os.Process.myPid());
}
});
}
public void loadPatch(View view) {
// /storage/emulated/0/patch_signed_7zip.apk
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.alpha.tinkerdemo.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="loadPatch"
android:text="Load Patch" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:id="@+id/btn_kill"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="kill Process" />
</LinearLayout>
proguard_rules.pro
关于混淆,如下:
-keepattributes *Annotation*
-dontwarn com.tencent.tinker.anno.AnnotationProcessor
-keep @com.tencent.tinker.anno.DefaultLifeCycle public class *
-keep public class * extends android.app.Application {
*;
}
-keep public class com.tencent.tinker.loader.app.ApplicationLifeCycle {
*;
}
-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {
*;
}
-keep public class com.tencent.tinker.loader.TinkerLoader {
*;
}
-keep public class * extends com.tencent.tinker.loader.TinkerLoader {
*;
}
-keep public class com.tencent.tinker.loader.TinkerTestDexLoad {
*;
}
#your dex.loader pattern here
-keep class com.tencent.tinker.loader.**
#注意要改为自己的Application
-keep class com.alpha.tinkerdemo.SimpleTinkerInApplication
注意:最后一行要修改为我们在SimpleTinkerInApplicationLike
注释中的类,包括自己的包名
AndroidManifest.xml
完整的AndroidManifest.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.alpha.tinkerdemo">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:name=".SimpleTinkerInApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="TINKER_ID"
android:value="tinker_id_6235658" />
</application>
</manifest>
因为我们需要读取sdcard 的内容,所以需要添加
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
命令行接入Tinker,我们需要添加
<meta-data
android:name="TINKER_ID"
android:value="tinker_id_6235658" />
这个id,测试的时候可以随便写,正式开发的时候使用git commit版本较好
patch生成
首先要下载 demo
demo,然后将脚本相关
的文件夹放置到一个目录下:
1.生成old.apk
首先生成第一个版本,也就是有bug 的版本,将这个生成的apk,放置到脚本相关目录下,建议修改一下名字,不然生成new.apk的时候,会覆盖这个版本,
2.mapping.txt
将app/build/outputs/mapping/debug/mapping.txt拷贝到与proguard_roles.pro同一级目录下,如果没有,打开app/build.gradle,修改buildTypes如下:
要给debug添加混淆,才会生成mapping文件
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
- 3.修改内容
给activity_main.xml中添加个button
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.alpha.tinkerdemo.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="loadPatch"
android:text="Load Patch" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:id="@+id/btn_kill"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="kill Process" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="loadPatch"
android:text="添加button" />
</LinearLayout>
然后生成新的apk,同样与old.apk一样,修改为new.apk,并放在同一级目录
4.proguard.rules.pro
在该文件中,添加
-applymapping mapping.txt
,也就是引用刚才生成的mapping.txt文件。
5.tinker_config.xml
修改tinker_config.xml中该参数,与混淆文件中最后一个是一样的
<loader value="com.alpha.tinkerdemo.SimpleTinkerInApplication"/>
- 6.生成patch
打开终端,cd 到刚才的脚本相关
文件
xu:~ xiaokai$ cd /Users/xiaokai/Downloads/热更新/脚本相关
然后输入命令
java -jar tinker-patch-cli-1.7.7.jar -old old.apk -new new.apk -config tinker_config.xml -out output
output中的patch_signed.apk就我们生成的patch,也就是我们的补丁包。
7.install old.apk
使用adb安装old.apk
adb install old.apk
8.push patch
在patch前,先将
patch_signed.apk
改名为patch_signed_7zip.apk
,这里你可以改为任意名字,例如:patch_signed_7zip.dex等使用adb 将我们生成的patch,push到手机上。
xu:脚本相关 xiaokai$ adb push /Users/xiaokai/Downloads/热更新/脚本相关/output/patch_signed_7zip.apk /storage/emulated/0/patch_signed_7zip.apk
- 9.测试
启动old.apk,点击loadPatch,会自动退出,然后再次打开,就可以看到有更新来
- 10.issue
1.只可以改变布局,不能改变代码
我现在测试的是,可以改变activity_main.xml中的布局,但是代码改变的时候,没有更新。大家解决的话,可以留言给我。
2.使用return退出,关闭activity,并不是退出进程,需要退出进程,再次进入才能更新,