tinker熱更新 簡單
阿新 • • 發佈:2019-02-04
project下
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.1.2' //此處請匯入 classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1') // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir } app 的gradle //////////////////////////////////////////////////////////////////////////////////////
apply plugin: 'com.android.application' apply plugin: 'com.tencent.tinker.patch' android { //此處生成簽名檔案 生成完之後 //1 Project Structrue - signing配置 然後到 - Build Types - release - signing config選擇名字 點選OK //他會自動出現這些 signingConfigs { release { keyAlias 'key0' keyPassword '123456' storeFile file('D:/keystory/tinkerjks/tinker.jks') storePassword '123456' } } compileSdkVersion 27 defaultConfig { applicationId "com.example.administrator.training2" minSdkVersion 14 targetSdkVersion 27 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" //此處請複製 javaCompileOptions { annotationProcessorOptions { includeCompileClasspath true } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } //這個需不需要粘? debug { signingConfig signingConfigs.release } } buildToolsVersion '27.0.3' } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' //以下三個 是拷貝過來的 compileOnly 'com.tencent.tinker:tinker-android-anno:1.9.1' implementation 'com.tencent.tinker:tinker-android-lib:1.9.1' implementation 'com.android.support:multidex:1.0.1' } def bakPath = file("${buildDir}/bakApk/") ext { //此處的地址是第一次生成包的地址 tinkerOlApkPath = "${bakPath}/app-release-0607-20-59-42.apk" } def getOldApkPath(){ return ext.tinkerOlApkPath } tinkerPatch{ tinkerEnable true oldApk = getOldApkPath() ignoreWarning true // 忽略 混淆檔案沒有的警告 是 useSign true buildConfig{ tinkerId = "tinker_id_b168b32" keepDexApply = false isProtectedApp = false } dex{ dexMode = "jar" pattern = ["classes*.dex","assets/secondary-dex-?.jar"] //自己的包名+.MyTinkerApplication loader = ["com.example.administrator.training2.MyTinkerApplication"] } lib{ pattern = ["lib/*/*.so"] } res{ pattern = ["res/*","assets/*","resources.arsc","AndroidManifest.xml"] largeModSize = 100 } packageConfig{ configField ("patchVersion","1.0") } } List<String> 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 = "${project.name}-${variant.baseName}" def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}" def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath from variant.outputs.first().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 = getTinkerBuildFlavorDirectory() 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 = getTinkerBuildFlavorDirectory() 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" } } } } }
AndroidManifest///////////////////////////////////////////////////////////////////////////////////////
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.administrator.training2"> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application android:allowBackup="true" android:name=".MyTinkerApplication" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="TINKER_ID" android:value="tinker_id_b168b32"/> </application> </manifest>MainActivity/////////////////////////////////////////////////////////////////////////////////////////
btn_load_patch下的是熱更新開始
更改內部程式碼 沒有佈局程式碼package com.example.administrator.training2; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import java.io.File; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; private TextView txtShow; private Button btnLoad; private Button btnNew; private Button btngradle; private String patchDir = "patch"; private String patchName = "patch_signed.apk"; private File patchFile; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); txtShow = findViewById(R.id.txt_show); btnLoad = findViewById(R.id.btn_load_patch); btnNew = findViewById(R.id.btn_new_function); btngradle =findViewById(R.id.uiui); //在手機裡面建立一個資料夾彷如補丁包 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { File rootDirectory = Environment.getExternalStorageDirectory(); File patchPath = new File(rootDirectory, patchDir); if (!patchPath.exists()) { patchPath.mkdirs(); } patchFile = new File(patchPath, patchName); } btnLoad.setOnClickListener(this); btnNew.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_load_patch: String path = patchFile.getAbsolutePath(); Log.i(TAG, "path: " + path); TinkerManager.loadPatch(path); break; case R.id.btn_new_function: Toast.makeText(this, "這是新功能", Toast.LENGTH_SHORT).show(); break; case R.id.uiui: Toast.makeText(this, "gradlew我新家的功能", Toast.LENGTH_SHORT).show(); break; } } }
BaseApplication///////////////////////////////////////////////////////////////////////////////
package com.example.administrator.training2; import android.app.Application; import android.content.Context; import android.content.Intent; import android.support.multidex.MultiDex; import com.tencent.tinker.anno.DefaultLifeCycle; import com.tencent.tinker.loader.app.DefaultApplicationLike; import com.tencent.tinker.loader.shareutil.ShareConstants; @DefaultLifeCycle(application = ".MyTinkerApplication", flags = ShareConstants.TINKER_ENABLE_ALL) public class BaseApplication extends DefaultApplicationLike { public BaseApplication(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); MultiDex.install(getApplication().getApplicationContext()); TinkerManager.install(this); } }
TinkerManager/////////////////////////////////////////////////////////////////////////////////
package com.example.administrator.training2; import android.content.Context; import com.tencent.tinker.lib.tinker.Tinker; import com.tencent.tinker.lib.tinker.TinkerInstaller; import com.tencent.tinker.loader.app.ApplicationLike; public class TinkerManager { private static boolean isInstalled = false; private static ApplicationLike mApplicationLike; /** * 安裝tinker * * @param applicationLike */ public static void install(ApplicationLike applicationLike) { mApplicationLike = applicationLike; if (isInstalled) { return; } TinkerInstaller.install(applicationLike); isInstalled = true; } /** * 載入補丁 * * @param path */ public static void loadPatch(String path) { if (Tinker.isTinkerInstalled()) { TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path); } } /** * 獲取上下文 * * @return */ private static Context getApplicationContext() { if (mApplicationLike != null) { return mApplicationLike.getApplication().getApplicationContext(); } return null; } }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
gradlew assembleRelease 打老包 新包需要在project下的 - Tasks-tinker-tinkerPatchRelease 點選