Android 热修复框架 Tinker ( 三 )

来源:互联网 发布:淘宝店铺四个钻石 编辑:程序博客网 时间:2024/05/18 19:47

这篇博客是基于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下生成:下面三个文件1

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" //上面打包生成的.apktinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txttinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt

8.一件制作补丁包:

调用tinkerPatchRelease, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/2

9.打补丁:

然后我们将patch_signed_7zip.apk 补丁包 推送到手机的sdcard中

4

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下生成:下面三个文件1

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" //上面打包生成的.apktinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txttinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt

9.一件制作补丁包:

调用tinkerPatchRelease, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/2

10.打补丁:

然后我们将patch_signed_7zip.apk 补丁包 推送到手机的sdcard中

4

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下生成:下面三个文件1

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" //上面打包生成的.apktinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txttinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt

8.一件制作补丁包:

调用tinkerPatchRelease, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/2

9.打补丁:

然后我们将patch_signed_7zip.apk 补丁包 推送到手机的sdcard中

4

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库

12

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下生成:下面三个文件1

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" //上面打包生成的.apktinkerApplyMappingPath = "${bakPath}/app-release-1220-10-49-26-mapping.txt"//上面生成的mapping.txttinkerApplyResourcePath = "${bakPath}/app-release-1220-10-49-26-R.txt" //上面打包生成的R.txt

8.一件制作补丁包:

调用tinkerPatchRelease, 补丁包与相关日志会保存在/build/outputs/tinkerPatch/2

9.打补丁:

然后我们将patch_signed_7zip.apk 补丁包 推送到手机的sdcard中

4

10.重新启动APP( 有时需要重新启动APP才能生效 )

再次点击chick me的时候就会冒出Toast:

现在我们就可以实现在没有重新安装apk的前提下动态发布代码,动态更新so库。

11.总结:

1.Tinker可以实现so库动态的更新

该项目下载地址

1 0