这篇博客是基于Android 热修复框架 Tinker ( 一)写的,在看这篇博客之前请先看Android 热修复框架 Tinker ( 一 )。
1.方法替换
注意:下面是居于上一篇文章的项目(已经集成Tinker)
1.编写avtivity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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: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="xmg.com.tinkertest.MainActivity">
<Button
android:id="@+id/btn_mothed"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="5dp"
android:textAllCaps="false"
/>
</RelativeLayout>
2.编写MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//补丁所在的路劲
String path=Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
//添加补丁
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path );
Log.d("TAG","path="+path);
//1.演示方法的替换:
final Button mButton=(Button)findViewById(R.id.btn_mothed);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String name = getName();
mButton.setText(name);
}
});
}
private String getName() {
return "登录";
}
}
3.修改app/build.gradle:
tinkerBuildFlavorDirectory
注释掉,因为暂时不用到多渠道打包。
ext {
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true
//for normal build
//old apk file to build patch apk
tinkerOldApkPath = "${bakPath}/app-debug-1218-16-32-05.apk"
//proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}/mapping.txt"
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}/app-debug-1218-16-32-05-R.txt"
//only use for build all flavor, if not, just ignore this field
// tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
4.开始打包:
点击assembleRelease后,在app/build/apk下生成:下面三个文件
5.将apk安装在模拟器中
安装:app-release-1220-10-49-26.apk
当点击button的时候出现:登录
6.修改代码,:
替换掉MainActivity中的getName() 方法
public class MainActivity extends AppCompatActivity {
private TextView tv_show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String path=Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path );
Log.d("TAG","path="+path);
//1.演示方法的替换:
final Button mButton=(Button)findViewById(R.id.btn_mothed);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// String name = getName(); // 该方法被下面的方法替换
String name=getNameFromValuse();
mButton.setText(name);
}
});
}
private String getName() {
return "登录";
}
/**
* 从资源文件上获取文字
* @return
*/
private String getNameFromValuse(){
String name=getResources().getString(R.string.mothed_name);
return name;
}
}
在vaulse包下的strings.xml中添加:
<string name="mothed_name">替换了getName的方法</string>
7.修改build.gradle的配置文件
启用并修改下面两个属性,并启用它们在该gradle被引用的地方,因为在第一打包的时候已把它们注释了
tinkerOldApkPath = "${bakPath}/app-release-1220-10-49-26.apk" //上面打包生成的.apk
tinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txt
tinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt
8.一件制作补丁包:
调用tinkerPatchRelease
, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/
。
9.打补丁:
然后我们将patch_signed_7zip.apk
补丁包 推送到手机的sdcard中
10.重新启动APP( 有时需要重新启动APP才能生效 )
再次点击chick me的时候就会冒出Toast:
现在我们就可以实现在没有重新安装apk的前提下动态发布代码,动态添加代码修复bugs。
11.总结:
1.Tinker可以实现方法的替换
2.Tinker可以实现对res资源下的valuse文件夹下的资源操作
2.类替换
1.编写avtivity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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: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="xmg.com.tinkertest.MainActivity">
<Button
android:id="@+id/btn_class"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="5dp"
android:text="AUtils.Class"
android:textAllCaps="false"
/>
</RelativeLayout>
2.编写MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//补丁所在的路劲
String path=Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
//添加补丁
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path );
Log.d("TAG","path="+path);
//2.演示类的替换
final Button btn_class = (Button) findViewById(R.id.btn_class);
btn_class.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//新建了一个A类的对象
A a = new A();
btn_class.setText(a.getName());
}
});
}
}
3.新添加一个A.java类
public class A {
public String getName(){
return "Name is form A Class ";
}
}
4.修改app/build.gradle:
tinkerBuildFlavorDirectory
注释掉,因为暂时不用到多渠道打包。
ext {
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true
//for normal build
//old apk file to build patch apk
tinkerOldApkPath = "${bakPath}/app-debug-1218-16-32-05.apk"
//proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}/mapping.txt"
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}/app-debug-1218-16-32-05-R.txt"
//only use for build all flavor, if not, just ignore this field
// tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
5.开始打包:
点击assembleRelease后,在app/build/apk下生成:下面三个文件
6.将apk安装在模拟器中
安装:app-release-1220-10-49-26.apk
当点击button的时候出现:Name is form A Class
7.修改代码,:
添加一个B.java类
public class B {
public String getName(Context context){
try {
InputStream in = context.getAssets().open("config.txt");
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
String s = reader.readLine();
return s;
} catch (IOException e) {
e.printStackTrace();
}
return " IOException is from B class";
}
}
在Assets文件夹中添加config.txt文件, 并输入
Name if from B Class
在MainActivity中用B类替换A类
public class MainActivity extends AppCompatActivity {
private TextView tv_show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String path=Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path );
Log.d("TAG","path="+path);
//2.演示类的替换
final Button btn_class = (Button) findViewById(R.id.btn_class);
btn_class.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// A a = new A();
// btn_class.setText(a.getName());
//B类替换了A类
B b=new B();
String name = b.getName(MainActivity.this);
btn_class.setText(name);
}
});
}
}
8.修改build.gradle的配置文件
启用并修改下面两个属性,并启用它们在该gradle被引用的地方,因为在第一打包的时候已把它们注释了
tinkerOldApkPath = "${bakPath}/app-release-1220-10-49-26.apk" //上面打包生成的.apk
tinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txt
tinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt
9.一件制作补丁包:
调用tinkerPatchRelease
, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/
。
10.打补丁:
然后我们将patch_signed_7zip.apk
补丁包 推送到手机的sdcard中
11.重新启动APP( 有时需要重新启动APP才能生效 )
再次点击button的时候就会冒出:name if from B Class
现在我们就可以实现在没有重新安装apk的前提下动态发布代码,动态添加代码修复bugs。
12.总结:
1.Tinker可以实现添加类,并实现类的替换
2.Tinker可以实现对Assets资源下的文件进行操作
3.资源替换
1.编写avtivity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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: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="xmg.com.tinkertest.MainActivity">
<ImageView
android:layout_width="90dp"
android:layout_height="90dp"
android:src="@mipmap/ic_launcher"
android:layout_gravity="center"
android:layout_marginTop="10dp"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="5dp"
android:hint="输入账号"
android:paddingLeft="10dp"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="5dp"
android:hint="输入密码"
android:paddingLeft="10dp"
/>
<Button
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="5dp"
android:text="登录"
/>
<Button
android:id="@+id/btn_Theme"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="5dp"
android:text="切换主题"
/>
</RelativeLayout>
2.编写MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//补丁所在的路劲
String path=Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
//添加补丁
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path );
Log.d("TAG","path="+path);
//3.演示资源的替换
final Button btn_class =(Button)findViewById(R.id.btn_Theme);
btn_class.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "资源替换", Toast.LENGTH_SHORT).show();
}
});
}
}
3.修改app/build.gradle:
tinkerBuildFlavorDirectory
注释掉,因为暂时不用到多渠道打包。
ext {
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true
//for normal build
//old apk file to build patch apk
tinkerOldApkPath = "${bakPath}/app-debug-1218-16-32-05.apk"
//proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}/mapping.txt"
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}/app-debug-1218-16-32-05-R.txt"
//only use for build all flavor, if not, just ignore this field
// tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
4.开始打包:
点击assembleRelease后,在app/build/apk下生成:下面三个文件
5.将apk安装在模拟器中
安装:app-release-1220-10-49-26.apk
当点击切换主题的时候出现:资源替换
6.修改代码,:
修改MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView tv_show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//切换主题
sp= getSharedPreferences("config", 0);
boolean theme = sp.getBoolean("theme",true);
if(theme){
this.setTheme(R.style.DayTheme);
}else{
this.setTheme(R.style.NightTheme);
}
setContentView(R.layout.activity_main);
String path=Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path );
Log.d("TAG","path="+path);
//3.演示资源的替换:切换主题
final Button btn_class =(Button)findViewById(R.id.btn_Theme);
btn_class.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Toast.makeText(MainActivity.this, "资源替换", Toast.LENGTH_SHORT).show();
boolean theme = sp.getBoolean("theme",true);
sp.edit().putBoolean("theme",!theme).commit();
MainActivity.this.recreate();//从新Create Activity
}
});
}
}
在vaulse包下的styles.xml中添加:
<!--白天主题-->
<style name="DayTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#03A9F4</item>
<item name="android:textColorPrimary">#ffffff</item>
<item
name="android:windowBackground">@color/background_material_light</item>
<item name="colorAccent">#00BCD4</item>
<item name="colorControlNormal">#00BCD4</item>
<item name="android:textColor">#9C27B0</item>
<item name="android:textSize">16sp</item>
</style>
<!--夜晚主题-->
<style name="NightTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#00796B</item>
<item name="android:textColorPrimary">#212121</item>
<item
name="android:windowBackground">@color/background_material_dark</item>
<item name="colorAccent">#00796B</item>
<item name="colorControlNormal">#212121</item>
<item name="android:textColor">#212121</item>
<item name="android:textSize">20sp</item>
</style>
7.修改build.gradle的配置文件
启用并修改下面两个属性,并启用它们在该gradle被引用的地方,因为在第一打包的时候已把它们注释了
tinkerOldApkPath = "${bakPath}/app-release-1220-10-49-26.apk" //上面打包生成的.apk
tinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txt
tinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt
8.一件制作补丁包:
调用tinkerPatchRelease
, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/
。
9.打补丁:
然后我们将patch_signed_7zip.apk
补丁包 推送到手机的sdcard中
10.重新启动APP( 有时需要重新启动APP才能生效 )
再次点击切换主题的时候就会:切换主题
现在我们就可以实现在没有重新安装apk的前提下动态发布代码,动态添加代码修复bugs。
11.总结:
1.Tinker可以实现资源的替换
2.Tinker可以实现对res资源下的valuse文件夹下的资源操作
4.So替换
1.编写avtivity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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: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="xmg.com.tinkertest.MainActivity">
<Button
android:id="@+id/btn_so"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="5dp"
android:text="So的替换"
android:textAllCaps="false"
/>
</RelativeLayout>
2.编写MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String path= Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path );
Log.d("TAG","path="+path);
//4.演示So的替换
final Button btn_so=(Button)findViewById(R.id.btn_so);
btn_so.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//for lib/armeabi, just use TinkerInstaller.loadLibrary
TinkerInstaller.loadArmLibrary(this,"testShare");//加载testShare.so
String version1 =TestShare.getVersionStatic1();
Log.d("TAG","version1="+version1);
btn_so.setText(version1);
}
});
}
}
3.添加testShare.so库
4.添加TestShare.java
新建一个包:xmg.com.ndktest.ndk
然后把TestShare.java 类添加到该包中
public class TestShare {
static {
System.loadLibrary("testShare");
}
public native String getVersion1();
public static native String getVersionStatic1();
}
5.修改app/build.gradle:
tinkerBuildFlavorDirectory
也注释掉,因为暂时不用到多渠道打包。
ext {
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true
//for normal build
//old apk file to build patch apk
tinkerOldApkPath = "${bakPath}/app-debug-1218-16-32-05.apk"
//proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}/mapping.txt"
//resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}/app-debug-1218-16-32-05-R.txt"
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
6.开始打包:
点击assembleRelease后,在app/build/apk下生成:下面三个文件
7.将apk安装在模拟器中
安装:app-release-1220-10-49-26.apk
当点击button的时候出现:Hello World from jni 1.0.0!
8.修改代码,:
修改MainActivity中的代码
public class MainActivity extends AppCompatActivity {
private TextView tv_show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//4.演示So的替换
//for lib/armeabi, just use TinkerInstaller.loadLibrary
TinkerInstaller.loadArmLibrary(this,"testShare");//加载testShare
final Button btn_so=(Button)findViewById(R.id.btn_so);
btn_so.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//替换换成so的第二版本库
String version2 =TestShare.getVersionStatic2();
Log.d("TAG","version2="+version2);
btn_so.setText(version2);
}
});
}
}
修改TestShare.java
public class TestShare {
static {
System.loadLibrary("testShare");
}
public native String getVersion1();
public static native String getVersionStatic1();
//添加下面两个本地方法
public native String getVersion2();
public static native String getVersionStatic2();
}
把lib下所有的so库全部替换成第二版本的so库:
9.修改build.gradle的配置文件
启用并修改下面两个属性,并启用它们在该gradle被引用的地方,因为在第一打包的时候已把它们注释了
tinkerOldApkPath = "${bakPath}/app-release-1220-10-49-26.apk" //上面打包生成的.apk
tinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txt
tinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt
8.一件制作补丁包:
调用tinkerPatchRelease
, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/
。
9.打补丁:
然后我们将patch_signed_7zip.apk
补丁包 推送到手机的sdcard中
10.重新启动APP( 有时需要重新启动APP才能生效 )
再次点击chick me的时候就会冒出Toast:
现在我们就可以实现在没有重新安装apk的前提下动态发布代码,动态更新so库。
11.总结:
1.Tinker可以实现so库动态的更新