Thinker 使用详解(上)

简介: Thinker 使用详解(上)

Tinker基本介绍


Tinker 是微信官方的 Andriod 热补丁解决方案,它支持动态下发代码,so库以及资源,让应用在不需要安装的情况下实现更新,当然,你也可以使用 Thinker 来更新你的插件。


它主要包含以下几部分:


1,gradle 编译插件 tinker-patch-gradle-plugin:主要用于在 as 中直接完成 patch 文件的生成


2,核心 sdk 库 tinker-android-lib :核心库,为应用层提供的 api


3,非 gradle 编译用户的命令行版本,tinker-path-cli.jar :为 eclipse 做的一个工具


为什么使用 Tinker


当前市面上 热修复的解决方案很多,但是他们都有一些无法解决的问题,但是 Tinker 的功能是比较全面的。


image.png


Tinker 执行原理及流程


基于 android 原生的 ClassLoader ,开发了自己的 ClassLoader,通过自定义的 ClassLoader去加载 patch 文件中的字节码


基于 android 原生的 aapt,开发了自己的 aapt ,加载 资源文件


基于 Dex 文件的格式,研发了 DexDiff 算法,通过 DexDiff 算法比较两个 apk 文件中的差异


简单的使用 Tinker


1,在项目的gradle.properties 中添加


# tinker版本号 ,控制版本,以下版本已经兼容 9.0
TINKER_VERSION=1.9.14
TINKERPATCH_VERSION=1.2.14


2,在项目的 gradle中添加:


classpath("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}")


3,在 app 中的 gradle 中添加:


compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
    annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
    // tinker 的核心 sdk 库
    implementation("com.tinkerpatch.sdk:tinkerpatch-android-sdk:${TINKERPATCH_VERSION}") { changing = true }


4,接着进行初始化,新建一个类用于管理 tinker 的初始化


/**
 * 对 TinkerManager api 做一层封装
 */
public class TinkerManager {
    /**
     * 是否初始化
     */
    private static boolean isInstalled = false;
    private static ApplicationLike mApplike;
    /**
     * 完成 Tinker初始化
     *
     * @param applicationLike
     */
    public static void installTinker(ApplicationLike applicationLike) {
        mApplike = applicationLike;
        if (isInstalled) {
            return;
        }
        //完成 tinker 初始化
        TinkerInstaller.install(mApplike);
        isInstalled = true;
    }
    public static void loadPatch(String path) {
        if (Tinker.isTinkerInstalled()) {
            TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);
        }
    }
    /**
     * 通过 ApplicationLike 获取 Context
     */
    private static Context getApplicationContext() {
        if (mApplike != null) {
            return mApplike.getApplication().getApplicationContext();
        }
        return null;
    }
}


5,自定义 application 继承自 ApplicationLike


//通过 DefaultLifeCycle 注解来生成我们程序中需要用到的 Application
@DefaultLifeCycle(application = ".MyTinkerApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
public class CustomTinkerLike extends ApplicationLike {
  public CustomTinkerLike(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);
        //初始化
        TinkerManager.installTinker(this);
    }
}


为什么需要继承 ApplicationLike,而不直接在 Application 中初始化呢?


因为 ApplicationLike 需要对 Application 的生命周期进行监听, 所以他通过 ApplicationLike 进行代理。通过这个代理可以完成对 Application 的生命周期监听,然后在不同的生命周期做一些别的工作,因为Tinker 初始化非常复杂,所用用 ApplicationLike 进行了代理,这样使用就非常简单了!


注意上面的注解:通过这个注解可以生成 需要在程序中进行添加的 application,


public class MyTinkerApplication extends TinkerApplication {
    public MyTinkerApplication() {
        super(15, "com.testdemo.www.tinker.CustomTinkerLike", "com.tencent.tinker.loader.TinkerLoader", false);
    }
}


上面这个就是通过注解生成的。我们需要将他添加的 AndroidManifest 中。


6,配置 tinker


//buildDir 代表的是 app 目录下 build 文件夹,
// 如果创建成果,他会在 build 文件夹中创建 bakApk文件夹,存放 old.apk
def bakPath = file("${buildDir}/bakApk") //指定基准文件存放位置
ext {
    tinkerEnable = true
    tinkerID = "1.0"
    tinkerOldApkPath = "${bakPath}/"
    tinkerApplyMappingPath = "${bakPath}/"
    tinkerApplyResourcePath = "${bakPath}/"
}
//是否启用 tinker
def buildWithTinker() {
    return ext.tinkerEnable
}
// old 路径
def getOldApkPath() {
    return ext.tinkerOldApkPath
}
// 混淆文件路径
def getApplyMappingPath() {
    return ext.tinkerApplyMappingPath
}
// 资源文件路径
def getApplyResourceMappingPath() {
    return ext.tinkerApplyResourcePath
}
// id
def getTinkerIdValue() {
    return ext.tinkerID
}
if (buildWithTinker()) {
    //启用 tinker
    apply plugin: 'com.tencent.tinker.patch'
    //所有 tinker 相关的参数配置
    tinkerPatch() {
        oldApk = getOldApkPath() // old.apk 路径
        ignoreWarning = false //不忽略警告,如果警告取消生成patch
        useSign = true  // 强制 patch 文件使用签名
        tinkerEnable = buildWithTinker() //指示是否启用 tinker
        buildConfig() {
            applyMapping = getApplyMappingPath() // 指定old.apk 打包时所使用的的混淆文件
            applyResourceMapping = getApplyResourceMappingPath() // 指定 old.apk 资源文件
            tinkerId = getTinkerIdValue() //指定 TinkerId
            keepDexApply = false //一般置为 false,true:生成patch 的时候会根据 dex 文件的分包去动态的编译 patch 文件
        }
        dex() {
            dexMode = "jar"  //jar 是配到14以下,会将dex压缩为jar文件,然后进行处理,体积小row只能在14以上使用,直接对 dex 文件处理
            pattern = ["classes*.dex", "assets/secondary-dex-?.jar"]//指定 dex 文件位于哪些牡蛎
            loader = ["com.testdemo.www.tinker.MyTinkerApplication"] //指定加载patch文件时所用到的类
        }
        //工程中的 jar 和 so
        lib {
            pattern = ["libs/*/*.so"]
        }
        res { //指定 tinker 可以修改的资源文件路径
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
            ignoreChange = ["assets/sampple_meta.txt"] //制定不受影响的资源路径,即时修改了这个资源文件,也不会被打入 patch
            largeModSize = 100 //资源修改大小默认值
        }
//-----------------必须项配置完成-------------------------------------
        //patch的介绍
        packageConfig {
            //补丁加载完成后通过 key 可以拿到这些 value
            configField("patchMessage", "fix 1.0 version's bugs")
            configField("patchVersion", "1.0")
        }
        //使用压缩
        sevenZip {
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
        }
    }
List<String> flavors = new ArrayList<>()
    project.android.productFlavors.each { flavor ->
        flavors.add(flavor.name)
    }
boolean hasFlavors = flavors.size() > 0
    /**
     * 复制基准包和其它必须文件到指定目录
     */
    android.applicationVariants.all { variant ->
        /**
         * task type, you want to bak
         */
        def taskName = variant.name
        def date = new Date().format("MMdd-HH-mm-ss")
        tasks.all {
            if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
                it.doLast {
                    copy {
                        def fileNamePrefix = "${project.name}-${variant.baseName}"
                        def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
                        def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
                        from variant.outputs[0].outputFile
                        into destPath
                        rename { String fileName ->
                            fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                        }
                        from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
                        }
                        from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                        }
                    }
                }
            }
        }
    }
}


注意 ext 中:


tinkerEnable = true //是否启用 tinker
tinkerID = “1.0” // id ,线上的版本 id 和 补丁包的 tinkerID 必须相等


详细的说明


7,进行测试,打包


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String FILE_END = ".apk";
    private String mPatchDir;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
  //创建路径
        mPatchDir = getExternalCacheDir().getAbsolutePath() + "/tpatch/";
        File file = new File(mPatchDir);
        file.mkdir();
        findViewById(R.id.btn).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
      //加载补丁包
        TinkerManager.loadPatch(getPatchName());
    }
  //拼装一个路径
    private String getPatchName() {
        return mPatchDir.concat("tinker").concat(FILE_END);
    }
}


看一下布局


<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="20dp"
        android:text="加载 PATCH 包"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />
</androidx.appcompat.widget.LinearLayoutCompat>


接着我们就可以进行打包了,注意不能是 debug 。是需要签名的。打包完成后会在 build 文件下生成 bakApk 文件夹,里面就是打包的 apk。


0a2653c851af460fa595bd959398a8f1.png


然后把这个apk安装到手机上即可。


相关文章
|
Android开发 数据安全/隐私保护
Thinker 使用详解(下)
Thinker 使用详解(下)
Thinker 使用详解(下)
AtCoder Beginner Contest 174 ——D.Alter Altar(思维)
AtCoder Beginner Contest 174 ——D.Alter Altar(思维)
88 0
DayDayUp:2019.01.24马云冬季达沃斯论坛(演讲)—Machine will be smarter than human beings, but will never be wiser
DayDayUp:2019.01.24马云冬季达沃斯论坛(演讲)—Machine will be smarter than human beings, but will never be wiser
|
缓存 架构师 程序员
神物:如何召唤Codethulhu
神物也有些别名,比如“Codethulhu”,“初级开发者的克星”和“质量保证分析员”。
1013. Battle Over Cities (25)
//连通分量数量 - 1 //如何求连通分量: #include #include #include #include using namespace std; int e[1000][1000], n; bool ...
773 0
|
机器学习/深度学习 人工智能 自然语言处理