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安装到手机上即可。


相关文章
|
存储
openzfs draid是什么
openzfs draid是什么
3381 0
openzfs draid是什么
|
Linux 数据安全/隐私保护 网络架构
如何搭建远程控制家中设备的Home Assistant智能家居系统【内网穿透】(上)
如何搭建远程控制家中设备的Home Assistant智能家居系统【内网穿透】
2646 0
|
自然语言处理 监控 算法
|
8月前
|
智能硬件
|
3月前
|
缓存 Kubernetes 安全
Harness 顶级架构:DeerFlow 2.0 沙盒 Sandbox 架构设计、源码深度解析(史上最深 、价值 逆天)
Harness 顶级架构:DeerFlow 2.0 沙盒 Sandbox 架构设计、源码深度解析(史上最深 、价值 逆天)
Harness 顶级架构:DeerFlow 2.0 沙盒 Sandbox 架构设计、源码深度解析(史上最深 、价值 逆天)
|
Web App开发 编解码 JavaScript
Flutter Web:图片相关及跨域问题
在flutter web上也可以使用Image这个widget来加载显示图片。但是涉及到网络图片的时候就可能会出现问题,现象是不显示图片,控制台报错:
2329 0
|
Oracle Java 关系型数据库
Mac电脑上安装和配置Flutter开发环境
Mac电脑上安装和配置Flutter开发环境
1161 158
|
Linux Docker 容器
docker启动完美容器的过程
本文详细介绍了使用Docker创建和管理容器的过程,包括拉取镜像、搜索镜像、创建容器、启动、停止、删除容器,以及查看容器日志和进程信息的常用命令。
964 2
|
网络协议 Linux 网络安全
网络工程师的福音!三分钟快速上手这款强大的网络设备模拟器:PNETLab!
【8月更文挑战第22天】PNETLab是一款强大的开源网络设备模拟器,支持多种网络设备如交换机、路由器、防火墙等的模拟,以及TCP/IP等多种协议。用户可通过直观的界面创建网络拓扑、配置设备参数,进行网络测试以验证连通性和排查故障,并能导出结果便于分享与分析,为学习和工作提供高效工具。
1505 1
|
算法 计算机视觉
【YOLOv8训练结果评估】YOLOv8如何使用训练好的模型对验证集进行评估及评估参数详解
【YOLOv8训练结果评估】YOLOv8如何使用训练好的模型对验证集进行评估及评估参数详解