继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

android:一步步实现插件化与热更新(三)

晓果
关注TA
已关注
手记 13
粉丝 50
获赞 188

接着android:一步步实现插件化与热更新(二)的内容继续下面的操作

热更新

热更新其实来源于插件化,如果你使用了上面的Small进行插件化开发,就可以直接进行插件化更新了!如果你没有使用插件化开发,你就可以只有热更新实现代码的热修复。

这里主要使用的是腾讯的Tinker。

  • 在peoject的build中配置如下:
  dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
TINKER_VERSION需要在gradle.properties中进行配置
TINKER_VERSION=1.7.7
  • 接下来是配置app的build
这是我的,可以直接使用:

apply plugin: 'com.android.application'
//配置java版本
def javaVersion = JavaVersion.VERSION_1_7

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.2"
    compileOptions {
        sourceCompatibility javaVersion
        targetCompatibility javaVersion
    }
    //recommend
    dexOptions {
        jumboMode = true
    }
    signingConfigs {
        release {
            keyAlias 'tsou'
            keyPassword 'tsou123'
            storeFile file('D:/study/TinkerTest/app/keystore/tinkertest.jks')
            storePassword 'tsou123'
        }
        debug {
            keyAlias 'tsou'
            keyPassword 'tsou123'
            storeFile file('D:/study/TinkerTest/app/keystore/tinkertest.jks')
            storePassword 'tsou123'
        }
    }
    defaultConfig {
        applicationId "tsou.cn.tinkertest"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
        buildConfigField "String", "MESSAGE", "\"I am the base apk\""
        //客户端版本更新补丁
        buildConfigField "String", "TINKER_ID", "\"2.0\""
        buildConfigField "String", "PLATFORM", "\"all\""
    }

    buildTypes {
        release {
            minifyEnabled true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            debuggable true
            minifyEnabled false
            signingConfig signingConfigs.debug
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    //可选,用于生成application类
    compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
    //tinker的核心库
    provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
    compile 'com.android.support:multidex:1.0.1'
}
def bakPath = file("${buildDir}/bakApk/")

//老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改
ext {
    tinkerEnabled = true

    tinkerOldApkPath = "${bakPath}/app-release-1110-16-24-35.apk"
    tinkerApplyMappingPath = "${bakPath}/app-release-1110-16-24-35-mapping.txt"
    tinkerApplyResourcePath = "${bakPath}/app-release-1110-16-24-35-R.txt"
    //only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}

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 buildWithTinker() {
    return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}

def getTinkerBuildFlavorDirectory() {
    return ext.tinkerBuildFlavorDirectory
}
/**
 * tinkerId一定要有
 */
if (buildWithTinker()) {
    apply plugin: 'com.tencent.tinker.patch'
    tinkerPatch {
        oldApk = getOldApkPath()
        ignoreWarning = true
        useSign = true
        tinkerEnable = buildWithTinker()
        buildConfig {
            applyMapping = getApplyMappingPath()
            applyResourceMapping = getApplyResourceMappingPath()
            tinkerId = "1.0"/*getTinkerIdValue()*/
            keepDexApply = false
        }
        dex {
            dexMode = "jar"
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            loader = [
                    //use sample, let BaseBuildInfo unchangeable with tinker
                    "tinker.sample.android.app.BaseBuildInfo"
            ]
        }
        lib {
            pattern = ["lib/*/*.so"]
        }
        res {
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
            ignoreChange = ["assets/sample_meta.txt"]
            largeModSize = 100
        }
        packageConfig {
            configField("patchMessage", "tinker is sample to use")
            configField("platform", "all")
            configField("patchVersion", "1.0")
        }

        sevenZip {
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
//        path = "/usr/local/bin/7za"
        }
    }

    List<String> flavors = new ArrayList<>();
    project.android.productFlavors.each { flavor ->
        flavors.add(flavor.name)
    }
    boolean hasFlavors = flavors.size() > 0
    android.applicationVariants.all { variant ->
        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 {
        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"
                    }

                }
            }
        }
    }
}
注意 修改buildConfigField 
//客户端版本更新补丁
buildConfigField "String", "TINKER_ID", "\"2.0\""

如果更新客户端补丁需要修改,例如我把1.0改为2.0
  • 配置application
/**
 * Created by Administrator on 2017/10/27 0027.
 */

@DefaultLifeCycle(application = ".SimpleTinkerInApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag =true)
public class SimpleTinkerInApplicationLike extends ApplicationLike {
    public SimpleTinkerInApplicationLike (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(base);
        TinkerInstaller.install(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }
}
SimpleTinkerInApplicationLike 并不是一个application
真正的application是@DefaultLifeCycle(application = “.SimpleTinkerInApplication”,这个
所以在androidmanifest中配置applica的name
 <application
        android:name=".SimpleTinkerInApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        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>
    </application>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  • 运行你的项目使用release版本
    图片描述

效果如下:

图片描述

  • 更改你的build.gradle
//老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改
ext {
    tinkerEnabled = true

    tinkerOldApkPath = "${bakPath}/app-release-1201-13-55-26.apk"
    tinkerApplyMappingPath = "${bakPath}/app-release-1201-13-55-26-mapping.txt"
    tinkerApplyResourcePath = "${bakPath}/app-release-1201-13-55-26-R.txt"
    //only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
  • 更改代码:把“我的”改为“2222”

  • 然后点击 编译
    图片描述

  • 生成目录如下
    图片描述

  • 把patch_signed_7zip.apk放到手机根目录下,再执行更新
    图片描述

  • 热更新代码如下:
public class MainActivity extends AppCompatActivity {

    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv);
        Button loadPath = (Button) findViewById(R.id.loadPath);
        tv.setText("2222");
        loadPath.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                requestPermi();
            }
        });
    }

    private void requestPermi() {
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.READ_EXTERNAL_STORAGE)) {

            } else {
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                        100);
            }
        } else {
            //有权限直接加载apk
            loadApk();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 100: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //权限申请成功加载apk
                    loadApk();
                } else {
                }
                return;
            }
        }
    }

    public void loadApk() {
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
        File file = new File(path);
        if (file.exists()) {
            Toast.makeText(this, "补丁已经存在", Toast.LENGTH_SHORT).show();
            TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);
        } else {
            Toast.makeText(this, "补丁已经不存在", Toast.LENGTH_SHORT).show();
        }
    }
}

好了到此插件化与热更新都可以实现了.......

android插件化开发Demo:https://gitee.com/huangxiaoguo/androidChaJianHuaKaiFa

android-Tinker热修复Demo:https://gitee.com/huangxiaoguo/android-TinkerReXiuFu

打开App,阅读手记
4人推荐
发表评论
随时随地看视频慕课网APP