最近在弄热更新,看了几个开源框架,感觉微信的tinker不错,学习一下,但是发现官方文档给的实在蛋疼,全是坑,做个笔记记录一下,以后用的时候避过这些坑。
下载下来以后有很多,直接把tinker-sample-android拿出来就好了,看这个就行了。
1.导入demo的问题,导入后你有可能遇见这个
tinkerId is not set!!!
找到你的moudle的build.gradle,在那里边有这个` tinkerId = “1.0”的配置我这直接给写死了
注意:这个你自己导入的时候最好是写你的版本号,因为这个是来判断到底你的修复的差别文件在那个上边使用,如果你像我一样写死你会发现你在更新包的时候,你的apk更新不了
2.运行demo在这块会生成你的apk包
3.然后你随意修改下代码
在你的gradle文件中修改tinkerOldApkPath
这个要和你上边生成的包名字保持一致
3.点击生成差别文件,debug和release看你是正式的还是测试的
4.等待运行完毕就会生成这个差别文件,没有生成差别文件的话你点击生成的log看看有什么错误,我运行的就有一个这个错误,
Warning:ignoreWarning is false, but resources.arsc is changed
这个的话你修改下build里边的这个就好了,改成true
ignoreWarning = false
具体错误自己看下,很好理解。
5.生成差别包以后,demo里边会有一个loadpatch的按钮,把你的差别包拷贝进这个路径下,这是德莫的路径自己你可以自己定
Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk"
装你原来的应用然后点击加载按钮,然后点击kill按钮杀死应用重新进入就是你改过的了。
******************************以上是导入demo********************************
集成进入项目
1.新建一个项目,找到你的project的gradle,引入这个,后边那个tinker_version是我在gradle.properties这个里边配置的版本,你也可以直接写,demo也有配置我就不在贴图了。
2.
(1.)找到model的gradle,进行配置,只贴一些必要的不必要的就不再贴了,添加依赖
(2.)apk路径你可以自己定,
//在build中创建一个bakApk文件夹
def bakPath = file("${buildDir}/bakApk/")
ext {
//for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
tinkerEnabled = true
//旧版本apk路径配置
tinkerOldApkPath = "${bakPath}/app-debug-0118-16-57-13.apk"
//用于混淆,没有混淆可以不管
tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
//旧版本apk R文件
tinkerApplyResourcePath = "${bakPath}/app-debug-0118-16-43-45-R.txt"
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
//全局信息相关的配置项
tinkerPatch {
// 基准apk包的路径,必须输入,否则会报错。
oldApk = tinkerOldApkPath
// 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。
useSign = true
ignoreWarning = true
// 编译相关的配置项
buildConfig {
// 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId.
// 这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName等等
tinkerId = "1.0"
}
packageConfig {
configField("patchMessage", "tinker is sample to use")
configField("platform", "all")
configField("patchVersion", "1.0")
}
// dex相关的配置项
dex {
/*只能是'raw'或者'jar'。
对于'raw'模式,我们将会保持输入dex的格式。
对于'jar'模式,我们将会把输入dex重新压缩封装到jar。
如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,
但是验证md5时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。*/
dexMode = "jar"
// 需要处理dex路径,支持*、?通配符,必须使用'/'分割。路径是相对安装包的,例如assets/..
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"]
/*这一项非常重要,它定义了哪些类在加载补丁包的时候会用到。
这些类是通过Tinker无法修改的类,也是一定要放在main dex的类。
这里需要定义的类有:
1. 你自己定义的Application类;
2. Tinker库中用于加载补丁包的部分类,即com.tencent.tinker.loader.*;
3. 如果你自定义了TinkerLoader,需要将它以及它引用的所有类也加入loader中;
4. 其他一些你不希望被更改的类,例如Sample中的BaseBuildInfo类。
这里需要注意的是,这些类的直接引用类也需要加入到loader中。或者你需要将这个类变成非preverify。
5. 使用1.7.6版本之后版本,参数1、2会自动填写。*/
loader = [
"tinker.sample.android.app.BaseBuildInfo"
]
}
// lib相关的配置项
lib {
pattern = ["lib/armeabi/*.so", "lib/arm64-v8a/*.so", "lib/armeabi-v7a/*.so", "lib/mips/*.so", "lib/mips64/*.so", "lib/x86/*.so", "lib/x86_64/*.so"]
}
// res相关的配置项
res {
pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
largeModSize = 100
ignoreChange = ["assets/sample_meta.txt"]
}
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.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")
}
}
}
}
}
}
project.afterEvaluate {
//sample use for build all flavor for one time
if (hasFlavors) {
task(tinkerPatchAllFlavorRelease) {
group = 'tinker'
def originOldPath = tinkerBuildFlavorDirectory
for (String flavor : flavors) {
def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
dependsOn tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
preAssembleTask.doFirst {
String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
}
}
}
task(tinkerPatchAllFlavorDebug) {
group = 'tinker'
def originOldPath = tinkerBuildFlavorDirectory
for (String flavor : flavors) {
def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
dependsOn tinkerTask
def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
preAssembleTask.doFirst {
String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
}
}
}
}
}
3.在代码中的使用,tinker定了一个自己的DefaultApplicationLike
我们的application得继承这个
@DefaultLifeCycle(
application = "com.tinker.tinker.MyApplication",
flags = ShareConstants.TINKER_ENABLE_ALL
)
public class ApplicationLike extends DefaultApplicationLike {
public ApplicationLike(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);
TinkerInstaller.install(this);
}
}
在然后调用下这个代码
TinkerInstaller.onReceiveUpgradePatch(this.getApplication(),"你的差别文件的路径");
其他的生成差别文件这些和demo一样
最后在附上一个我写的一个下载差别文件的测试代码
url = "你的下载路径";
String s = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "AAAAAA" + File.separator;
File file = new File(s);
if (!file.exists()) {
file.mkdirs();
}
patch = new File(s + "patch.apk");
if (!patch.exists()) {
try {
patch.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url1 = new URL(url);
HttpURLConnection connection = (HttpURLConnection) url1.openConnection();
if (connection.getResponseCode() == 200) {
InputStream inputStream = connection.getInputStream();
byte[] bytes = new byte[1024];
int len;
connection.connect();
FileOutputStream outputStream = new FileOutputStream(patch);
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
inputStream.close();
outputStream.close();
Log.e("下载", "下载---》patch成功");
} else {
Log.e("下载", "下载---》patch失败");
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
最后附上一个通过修改class方式的 RocooFix热修复