热修复——Tinker的集成与使用(1)

2)开启multiDex

defaultConfig {

multiDexEnabled true
}

3)应用tinker的gradle插件

这部分可先不管,在第三部分《Tinker的配置及任务》的第2节《配置Tinker与任务》中会添加。可跳过这部分继续往下看。

//apply tinker插件
apply plugin: 'com.tencent.tinker.patch

三、Tinker的配置及任务

1、开启支持大工程模式

Tinker文档中推荐将jumboMode设置为true。

android {
dexOptions {
// 支持大工程模式
jumboMode = true
}

}

2、配置Tinker与任务

将下面的配置全部复制粘贴到app的gradle文件(app/build.gradle)末尾,内容很多,但现在只需要看懂bakPath与ext括号内的东东就好了。

// Tinker配置与任务
def bakPath = file(“KaTeX parse error: Expected '}', got 'EOF' at end of input: …rOldApkPath = "{bakPath}/old-app.apk”
// 基础包的mapping.txt文件路径(用于辅助混淆补丁包的生成,一般在生成release版app时会使用到混淆,所以这个mapping.txt文件一般只是用于release安装包补丁的生成)
tinkerApplyMappingPath = “ b a k P a t h / o l d − a p p − m a p p i n g . t x t " / / 基础包的 R . t x t 文件路径(如果你的安装包中资源文件有改动,则需要使用该 R . t x t 文件来辅助生成补丁包) t i n k e r A p p l y R e s o u r c e P a t h = " {bakPath}/old-app-mapping.txt" // 基础包的R.txt文件路径(如果你的安装包中资源文件有改动,则需要使用该R.txt文件来辅助生成补丁包) tinkerApplyResourcePath = " bakPath/oldappmapping.txt"//基础包的R.txt文件路径(如果你的安装包中资源文件有改动,则需要使用该R.txt文件来辅助生成补丁包)tinkerApplyResourcePath="{bakPath}/old-app-R.txt”
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = “${bakPath}/flavor”
}

def getOldApkPath() {
return hasProperty(“OLD_APK”) ? OLD_APK : ext.tinkerOldApkPath
}

def getApplyMappingPath() {
return hasProperty(“APPLY_MAPPING”) ? APPLY_MAPPING : ext.tinkerApplyMappingPath
}

def getApplyResourceMappingPath() {
return hasProperty(“APPLY_RESOURCE”) ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
}

def getTinkerIdValue() {
return hasProperty(“TINKER_ID”) ? TINKER_ID : android.defaultConfig.versionName
}

def buildWithTinker() {
return hasProperty(“TINKER_ENABLE”) ? TINKER_ENABLE : ext.tinkerEnabled
}

def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}

if (buildWithTinker()) {
//apply tinker插件
apply plugin: ‘com.tencent.tinker.patch’

// 全局信息相关的配置项
tinkerPatch {
tinkerEnable = buildWithTinker()// 是否打开tinker的功能。
oldApk = getOldApkPath() // 基准apk包的路径,必须输入,否则会报错。
ignoreWarning = false // 是否忽略有风险的补丁包。这里选择不忽略,当补丁包风险时会中断编译。
useSign = true // 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名。
// 编译相关的配置项
buildConfig {
applyMapping = getApplyMappingPath()
// 可选参数;在编译新的apk时候,我们希望通过保持旧apk的proguard混淆方式,从而减少补丁包的大小。这个只是推荐设置,不设置applyMapping也不会影响任何的assemble编译。
applyResourceMapping = getApplyResourceMappingPath()
// 可选参数;在编译新的apk时候,我们希望通过旧apk的R.txt文件保持ResId的分配,这样不仅可以减少补丁包的大小,同时也避免由于ResId改变导致remote view异常。
tinkerId = getTinkerIdValue()
// 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName等等。
keepDexApply = false
// 如果我们有多个dex,编译补丁时可能会由于类的移动导致变更增多。若打开keepDexApply模式,补丁包将根据基准包的类分布来编译。
isProtectedApp = false // 是否使用加固模式,仅仅将变更的类合成补丁。注意,这种模式仅仅可以用于加固应用中。
supportHotplugComponent = false // 是否支持新增非export的Activity(1.9.0版本开始才有的新功能)
}
// dex相关的配置项
dex {
dexMode = “jar”
// 只能是’raw’或者’jar’。 对于’raw’模式,我们将会保持输入dex的格式。对于’jar’模式,我们将会把输入dex重新压缩封装到jar。如果你的minSdkVersion小于14,你必须选择‘jar’模式,而且它更省存储空间,但是验证md5时比’raw’模式耗时。默认我们并不会去校验md5,一般情况下选择jar模式即可。
pattern = [“classes*.dex”,
“assets/secondary-dex-?.jar”]
// 需要处理dex路径,支持*、?通配符,必须使用’/‘分割。路径是相对安装包的,例如assets/…
loader = [
// 定义哪些类在加载补丁包的时候会用到。这些类是通过Tinker无法修改的类,也是一定要放在main dex的类。
// 如果你自定义了TinkerLoader,需要将它以及它引用的所有类也加入loader中;
// 其他一些你不希望被更改的类,例如Sample中的BaseBuildInfo类。这里需要注意的是,这些类的直接引用类也需要加入到loader中。或者你需要将这个类变成非preverify。
]
}
// lib相关的配置项
lib {
pattern = [“lib//.so”,“src/main/jniLibs//.so”]
// 需要处理lib路径,支持*、?通配符,必须使用’/‘分割。与dex.pattern一致, 路径是相对安装包的,例如assets/…
}
// res相关的配置项
res {
pattern = [“res/", "assets/”, “resources.arsc”, “AndroidManifest.xml”]
// 需要处理res路径,支持*、?通配符,必须使用’/‘分割。与dex.pattern一致, 路径是相对安装包的,例如assets/…,务必注意的是,只有满足pattern的资源才会放到合成后的资源包。
ignoreChange = [
// 支持*、?通配符,必须使用’/'分割。若满足ignoreChange的pattern,在编译时会忽略该文件的新增、删除与修改。 最极端的情况,ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。
“assets/sample_meta.txt”
]
largeModSize = 100
// 对于修改的资源,如果大于largeModSize,我们将使用bsdiff算法。这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
}
// 用于生成补丁包中的’package_meta.txt’文件
packageConfig {
// configField(“key”, “value”), 默认我们自动从基准安装包与新安装包的Manifest中读取tinkerId,并自动写入configField。
// 在这里,你可以定义其他的信息,在运行时可以通过TinkerLoadResult.getPackageConfigByName得到相应的数值。
// 但是建议直接通过修改代码来实现,例如BuildConfig。
configField(“platform”, “all”)
configField(“patchVersion”, “1.0”)
// configField(“patchMessage”, “tinker is sample to use”)
}
// 7zip路径配置项,执行前提是useSign为true
sevenZip {
zipArtifact = “com.tencent.mm:SevenZip:1.1.10”
}
}
List flavors = new ArrayList<>();
project.android.productFlavors.each { flavor ->
flavors.add(flavor.name)
}
boolean hasFlavors = flavors.size() > 0
def date = new Date().format(“MMdd-HH-mm-ss”)

/**

  • bak apk and mapping
    /
    android.applicationVariants.all { variant ->
    /
    *
  • task type, you want to bak
    */
    def taskName = variant.name

tasks.all {
if (“assemble${taskName.capitalize()}”.equalsIgnoreCase(it.name)) {

it.doLast {
copy {
def fileNamePrefix = “ p r o j e c t . n a m e − {project.name}- project.name{variant.baseName}”
def newFileNamePrefix = hasFlavors ? “ f i l e N a m e P r e f i x " : " {fileNamePrefix}" : " fileNamePrefix":"{fileNamePrefix}-${date}”

def destPath = hasFlavors ? file(“ b a k P a t h / {bakPath}/ bakPath/{project.name}- d a t e / {date}/ date/{variant.flavorName}”) : bakPath
from variant.outputs.first().outputFile
into destPath
rename { String fileName ->
fileName.replace(“ f i l e N a m e P r e f i x . a p k " , " {fileNamePrefix}.apk", " fileNamePrefix.apk","{newFileNamePrefix}.apk”)
}

from “ b u i l d D i r / o u t p u t s / m a p p i n g / {buildDir}/outputs/mapping/ buildDir/outputs/mapping/{variant.dirName}/mapping.txt”
into destPath
rename { String fileName ->
fileName.replace(“mapping.txt”, “${newFileNamePrefix}-mapping.txt”)
}

from “ b u i l d D i r / i n t e r m e d i a t e s / s y m b o l s / {buildDir}/intermediates/symbols/ buildDir/intermediates/symbols/{variant.dirName}/R.txt”
into destPath
rename { String fileName ->
fileName.replace(“R.txt”, “KaTeX parse error: Expected 'EOF', got '}' at position 29: …refix}-R.txt") }̲ } } } } } proj…{flavor.capitalize()}Release”)
dependsOn tinkerTask
def preAssembleTask = tasks.getByName(“processKaTeX parse error: Expected '}', got 'EOF' at end of input: …atch.oldApk = "{originOldPath}/ f l a v o r N a m e / {flavorName}/ flavorName/{project.name}- f l a v o r N a m e − r e l e a s e . a p k " p r o j e c t . t i n k e r P a t c h . b u i l d C o n f i g . a p p l y M a p p i n g = " {flavorName}-release.apk" project.tinkerPatch.buildConfig.applyMapping = " flavorNamerelease.apk"project.tinkerPatch.buildConfig.applyMapping="{originOldPath}/ f l a v o r N a m e / {flavorName}/ flavorName/{project.name}- f l a v o r N a m e − r e l e a s e − m a p p i n g . t x t " p r o j e c t . t i n k e r P a t c h . b u i l d C o n f i g . a p p l y R e s o u r c e M a p p i n g = " {flavorName}-release-mapping.txt" project.tinkerPatch.buildConfig.applyResourceMapping = " flavorNamereleasemapping.txt"project.tinkerPatch.buildConfig.applyResourceMapping="{originOldPath}/ f l a v o r N a m e / {flavorName}/ flavorName/{project.name}-${flavorName}-release-R.txt”

}

}
}

task(tinkerPatchAllFlavorDebug) {
group = ‘tinker’
def originOldPath = getTinkerBuildFlavorDirectory()
for (String flavor : flavors) {
def tinkerTask = tasks.getByName(“tinkerPatch f l a v o r . c a p i t a l i z e ( ) D e b u g " ) d e p e n d s O n t i n k e r T a s k d e f p r e A s s e m b l e T a s k = t a s k s . g e t B y N a m e ( " p r o c e s s {flavor.capitalize()}Debug") dependsOn tinkerTask def preAssembleTask = tasks.getByName("process flavor.capitalize()Debug")dependsOntinkerTaskdefpreAssembleTask=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 = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- project.name{flavorName}-debug.apk”
project.tinkerPatch.buildConfig.applyMapping = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- project.name{flavorName}-debug-mapping.txt”
project.tinkerPatch.buildConfig.applyResourceMapping = “ o r i g i n O l d P a t h / {originOldPath}/ originOldPath/{flavorName}/ p r o j e c t . n a m e − {project.name}- project.name{flavorName}-debug-R.txt”
}

}
}
}
}
}

其中,有几点配置在这里说明一下,方便理解后续的操作(当tinkerEnabled = true的情况下):

  • app的生成目录是:主Module(一般是名为app)/build/bakApk文件夹。
  • 补丁包的生成路径:主Module(一般是名为app)/build/outputs/apk/tinkerPatch/debug/patch_signed_7zip.apk。
  • 基础包的名字:old-app.apk,放于bakApk文件夹下。
  • 基础包的mapping.txt和R.txt文件一般在编译release签名的apk时才会用到。
  • 在用到mapping.txt文件时,需要重命名为old-app-mapping.txt,放于bakApk文件夹下。
  • 在用到R.txt文件时,需要重命名为old-app-R.txt,放于bakApk文件夹下。

对于mapping.txt和R.txt文件,在配置中有说明,请回配置中仔细看。
上面只是我项目中的配置,这些其实都是可以自定义的,建议在搞清楚配置内容之后再去自定义修改。

什么是基础包??

基础包就是已经上架的apk文件(假设是1.0版本)。这其实很好理解,在新版本的App上架之前(假设是2.0版本),我们会用到Tinker来修复1.0版App中存在的bug,这时就需要用到Tinker来产生补丁包文件,而补丁包文件的本质,就是修复好Bug的App与1.0版本App之间的文件差异。在2.0版本上架之前,我们可能会多次产生新的补丁包,用于修复在用户手机上的1.0版App,所以补丁包必须以1.0版App作为参考标准,也就是说用户手机上的app就是基础包,即当前应用市场上的apk文件(前面说的1.0版本)。

四、Tinker封装与拓展

1、拷贝文件

将Demo中提供的tinker包下的所有文件及文件夹都拷贝到自己项目中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些文件其实就是Tinker官方Demo中的文件完全复制过来的,只是多加了一些注释。

简单说明下,这几个文件的作用:

  • SampleUncaughtExceptionHandler:Tinker的全局异常捕获器。
  • MyLogImp:Tinker的日志输出实现类。
  • SampleLoadReporter:加载补丁时的一些回调。
  • SamplePatchListener:过滤Tinker收到的补丁包的修复、升级请求。
  • SamplePatchReporter:修复或者升级补丁时的一些回调。
  • SampleTinkerReport:修复结果(成功、冲突、失败等)。
  • SampleResultService::patch补丁合成进程将合成结果返回给主进程的类。
  • TinkerManager:Tinker管理器(安装、初始化Tinker)。
  • TinkerUtils:拓展补丁条件判定、锁屏或后台时应用重启功能的工具类。

这些只是对Tinker功能的拓展和封装罢了,都是可选的,但这些文件对项目的功能完善会有所帮助,建议加入到自己的项目中。
如果你仅仅只是为了修复bug,而不做过多的工作(如:上传打补丁信息到服务器等),则无须理会这些文件的作用,当然你也可以自己封装。

对于这些自定义类及错误码的详细说明,请参考:「Tinker官方Wiki:可选的自定义类」

2、清单文件中添加服务

前面添加的文件中,有一个SampleResultService文件,是四大组件之一,所以必须在清单文件中声明。

五、编写Application的代理类

Tinker表示,Application无法动态修复,所以有两种选择:

  1. 使用「继承TinkerApplication + DefaultApplicationLike」。
  2. 使用「DefaultLifeCycle注解 + DefaultApplicationLike」。

当然,如果你觉得你自定义的Application不会用到热修复,可无视这部分;
但下方代码中的initTinker()方法记得要拷贝到你项目中,用于初始化Tinker。

第1种方式感觉比较鸡肋,这里使用第2种(Tinker官方推荐的方式):「DefaultLifeCycle注解 + TinkerApplicationLike」,DefaultLifeCycle注解生成Application,下面就来编写Application的代理类:

1、编写TinkerApplicationLike

将下方的代码拷贝到项目中,注释简单明了,不多解释:

@SuppressWarnings(“unused”)
@DefaultLifeCycle(application = “com.lqr.tinker.MyApplication”,// application类名。只能用字符串,这个MyApplication文件是不存在的,但可以在AndroidManifest.xml的application标签上使用(name)
flags = ShareConstants.TINKER_ENABLE_ALL,// tinkerFlags
loaderClass = “com.tencent.tinker.loader.TinkerLoader”,//loaderClassName, 我们这里使用默认即可!(可不写)
loadVerifyFlag = false)//tinkerLoadVerifyFlag
public class TinkerApplicationLike extends DefaultApplicationLike {

private Application mApplication;
private Context mContext;
private Tinker mTinker;

// 固定写法
public TinkerApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}

// 固定写法
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
getApplication().registerActivityLifecycleCallbacks(callback);
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
mApplication = getApplication();
mContext = getApplication();
initTinker(base);
// 可以将之前自定义的Application中onCreate()方法所执行的操作搬到这里…
}

private void initTinker(Context base) {
// tinker需要你开启MultiDex
MultiDex.install(base);

TinkerManager.setTinkerApplicationLike(this);
// 设置全局异常捕获
TinkerManager.initFastCrashProtect();
//开启升级重试功能(在安装Tinker之前设置)
TinkerManager.setUpgradeRetryEnable(true);
//设置Tinker日志输出类
TinkerInstaller.setLogIml(new MyLogImp());
//安装Tinker(在加载完multiDex之后,否则你需要将com.tencent.tinker.**手动放到main dex中)
TinkerManager.installTinker(this);
mTinker = Tinker.with(getApplication());
}

}

2、搬运自定义Application中的操作

把项目中在自定义Application的操作移到TinkerApplicationLike的onCreate()或onBaseContextAttached()方法中。

public class TinkerApplicationLike extends DefaultApplicationLike {

@Override
public void onCreate() {
super.onCreate();
// 将之前自定义的Application中onCreate()方法所执行的操作搬到这里…
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
mApplication = getApplication();
mContext = getApplication();
initTinker(base);
// 或搬到这里…
}
}

3、清单文件中注册

将@DefaultLifeCycle中application对应的值,即"com.lqr.tinker.MyApplication",赋值给清单文件的application标签的name属性,如下:



注意:
此时name属性会报红,因为项目源码中根本不存在MyApplication.java文件,但不必担心,因为它是动态生成的,Build一下项目就好了,不管它也无所谓。

对于Application代理类的详细说明,请参考:「Tinker官方Wiki:Application代理类」

到这里就已经集成好Tinker了,但只是本地集成而已,服务端下发补丁包到app的文章之后会陆续发布更新。

六、常用API

现在来了解下代码中会用到的几个Tinker的重要API。

1、请求打补丁

TinkerInstaller.onReceiveUpgradePatch(context, 补丁包的本地路径);

2、卸载补丁

Tinker.with(getApplicationContext()).cleanPatch();// 卸载所有的补丁
Tinker.with(getApplicationContext()).cleanPatchByVersion(版本号)// 卸载指定版本的补丁

3、杀死应用的其他进程

ShareTinkerInternals.killAllOtherProcess(getApplicationContext());

4、Hack方式修复so

TinkerLoadLibrary.installNavitveLibraryABI(this, abi);

abi:cpu架构类型

5、非Hack方式修复so

TinkerLoadLibrary.loadLibraryFromTinker(getApplicationContext(), “lib/” + abi, so库的模块名); // 加载任意abi库
TinkerLoadLibrary.loadArmLibrary(getApplicationContext(), so库的模块名); // 只适用于加载armeabi库
TinkerLoadLibrary.loadArmV7Library(getApplicationContext(), so库的模块名); // 只适用于加载armeabi-v7a库

loadArmLibrary()与loadArmV7Library()本质是调用了loadLibraryFromTinker(),有兴趣的可以查看下源码。

对于Tinker所有API的详细说明,请参考:「Tinker官方Wiki:Tinker-API概览」

七、测试

因为布局简单且不是重点,这里就给出一张Demo的运行图片,剩下的就靠想像了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1、编译基础包

没有基础包,那要补丁有什么用?所以,第一步就是打包一个apk。

在Terminal中使用命令行./gradlew assembleDebug。不会命令行无所谓,Android Studio为我们提供了图形化操作,根据下图操作即可:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果你是要release签名的打包,则双击assembleRelease,不过还要配置签名文件,这个后面再说。

编译完成后,可以在build目录下会自动创建一个bakApk文件夹,里面就有打包好的apk文件,因为之后的所有生成的补丁包都以这个apk会标准,所以这就是那个基础包文件(相当于应用市场上的app)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果这个apk文件是release签名且是要放到应用市场上的,那么你必须将apk与R.txt(如果有使用混淆的话,还会有一个mapping.txt)这几个文件保存好,切记。

现在就把这个tinker-local-debug-1206-11-48-42.apk安装到手机上(相当于是用户手机上的app)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 点击"say someting"按钮吐司"Hello"。
  2. 点击"get string from .so"按钮吐司"hello LQR"。
  3. 点击"show info"按钮显示"patch is not loaded",说明当前没有加载补丁。

1、修复java代码

下面是"say someting"按钮点击时,调用的方法,使用Toast显示Hello字符串:

public void say(View view) {
Toast.makeText(getApplicationContext(), “Hello”, Toast.LENGTH_SHORT).show();
}

1)修复代码

现在我想让它吐司Hello World,所以代码修改为:

public void say(View view) {
Toast.makeText(getApplicationContext(), “Hello World”, Toast.LENGTH_SHORT).show();
}

2)制作补丁包

先将基础包(前面那个tinker-local-debug-1206-11-48-42.apk文件)重命名为old-app.apk,然后双击tinkerPatchDebug,操作如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

编译完成后,build/outputs/apk/tinkerPatch会产生3个补丁包,我们要的就是patch_signed_7zip.apk。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于build/outputs/apk/tinkerPatch目录下文件及文件夹的详细说明,请参考:「Tinker官方Wiki:输出文件详解」

3)下发补丁包

将patch_signed_7zip.apk放到手机的SD卡目录下:

不一定是SD卡目录,位置由我们开发者决定,Demo中调用TinkerInstaller.onReceiveUpgradePatch(context, 补丁包的本地路径)方法时,第二个参数指定了是SD卡,故如此操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4)打补丁

可以看到,在install patch之前,点击"say someting"按钮时,还是吐司"Hello"。 点击"install patch"按钮后,会提示"patch success,please restart process",说明Tinker已经打上补丁了。 这时点击"show info",可以看到"patch is not loaded",说明当前补丁还没有生效。 最后,点击"kill myself"按钮,杀死当前app(进程)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在重新打开app,再点击"say someting"按钮,吐司"Hello World"。 再点击"show info",可以看到"patch is loaded",说明app重启后补丁生效了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小结:
Tinker热修复无法让补丁实时生效,在重启进程后,补丁才会生效。
Tinker会在app重启后自动应用补丁。

2、修复so库

在一开始制作基础包时,工程中就已经加入了一些so文件,存放在src/main/jniLibs目录下,因为Android Studio默认的库目录是libs(与src同级),所以这里需要在app的build.gradle文件中进行配置,指定so库所在文件夹。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面是"get string from .so"按钮点击时调用的方法:

public void string_from_so(View view) {
String string = JniUtil.hello();
Toast.makeText(getApplicationContext(), string, Toast.LENGTH_SHORT).show();
}

这个JniUtil的代码如下:

public class JniUtil {
public static String LIB_NAME = “LQRJni”;
public JniUtil() {}
static {
System.loadLibrary(LIB_NAME);
}
public static native String hello();
}

加载so库有2点需要注意:

  1. System.loadLibrary(libname)加载固定目录下的库文件,而System.load(filename)加载指定目录下的库文件。
  2. System.loadLibrary(libname)的参数libname指的是库的模块名,不是so文件的名字,如libLQRJni.so文件的模块名实际上是LQRJni。

so文件的制作代码包含在Demo中,有兴趣的朋友可以尝试自己制作。

1)替换so文件

回归正题,现在so库中得到的文字是"Hello LQR",现在变一下,我需要得到的文字是"Hello CSDN_LQR",将新的so文件替换掉旧的so文件即可。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2)检查Tinker的lib匹配规则

在app的build.gradle文件中,我们前面在第三部分《Tinker的配置及任务》的第2节《配置Tinker与任务》中,有如下一段配置:

lib {
pattern = [“lib//.so”, “src/main/jniLibs//.so”]
}

更多学习和讨论,欢迎加入我们!

有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

这里有2000+小伙伴,让你的学习不寂寞~·
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

1)替换so文件

回归正题,现在so库中得到的文字是"Hello LQR",现在变一下,我需要得到的文字是"Hello CSDN_LQR",将新的so文件替换掉旧的so文件即可。

[外链图片转存中…(img-UGvGAEpI-1715110282650)]

2)检查Tinker的lib匹配规则

在app的build.gradle文件中,我们前面在第三部分《Tinker的配置及任务》的第2节《配置Tinker与任务》中,有如下一段配置:

lib {
pattern = [“lib//.so”, “src/main/jniLibs//.so”]
}

[外链图片转存中…(img-yZVzAyzI-1715110282650)]

更多学习和讨论,欢迎加入我们!

有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

这里有2000+小伙伴,让你的学习不寂寞~·
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值