Tinker patch res更新

patch res 生成

1、ResDiffDecoder.java patch 新资源处理, Md5 生成,AndroidManifest.xml文件忽略

@Override
public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException {
    String name = getRelativePathStringToNewFile(newFile);

    //actually, it won't go below
    if (newFile == null || !newFile.exists()) {
        String relativeStringByOldDir = getRelativePathStringToOldFile(oldFile);
        if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, relativeStringByOldDir)) {
            Logger.e("found delete resource: " + relativeStringByOldDir + " ,but it match ignore change pattern, just ignore!");
            return false;
        }
        deletedSet.add(relativeStringByOldDir);
        writeResLog(newFile, oldFile, TypedValue.DEL);
        return true;
    }

    File outputFile = getOutputPath(newFile).toFile();

    if (oldFile == null || !oldFile.exists()) {
        if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) {
            Logger.e("found add resource: " + name + " ,but it match ignore change pattern, just ignore!");
            return false;
        }
        FileOperation.copyFileUsingStream(newFile, outputFile);
        addedSet.add(name);
        writeResLog(newFile, oldFile, TypedValue.ADD);
        return true;
    }
    //both file length is 0
    if (oldFile.length() == 0 && newFile.length() == 0) {
        return false;
    }
    //new add file
    String newMd5 = MD5.getMD5(newFile);
    String oldMd5 = MD5.getMD5(oldFile);

    //oldFile or newFile may be 0b length
    if (oldMd5 != null && oldMd5.equals(newMd5)) {
        return false;
    }
    if (Utils.checkFileInPattern(config.mResIgnoreChangePattern, name)) {
        Logger.d("found modify resource: " + name + ", but it match ignore change pattern, just ignore!");
        return false;
    }
    if (name.equals(TypedValue.RES_MANIFEST)) {
        Logger.d("found modify resource: " + name + ", but it is AndroidManifest.xml, just ignore!");
        return false;
    }
    if (name.equals(TypedValue.RES_ARSC)) {
        if (AndroidParser.resourceTableLogicalChange(config)) {
            Logger.d("found modify resource: " + name + ", but it is logically the same as original new resources.arsc, just ignore!");
            return false;
        }
    }
    dealWithModifyFile(name, newMd5, oldFile, newFile, outputFile);
    return true;
}

2、ResDiffDecoder.java  dealWithModifyFile BSDiff.bsdiff 生成差分文件

private boolean dealWithModifyFile(String name, String newMd5, File oldFile, File newFile, File outputFile) throws IOException {
    if (checkLargeModFile(newFile)) {
        if (!outputFile.getParentFile().exists()) {
            outputFile.getParentFile().mkdirs();
        }
        BSDiff.bsdiff(oldFile, newFile, outputFile);
        //treat it as normal modify
        if (Utils.checkBsDiffFileSize(outputFile, newFile)) {
            LargeModeInfo largeModeInfo = new LargeModeInfo();
            largeModeInfo.path = newFile;
            largeModeInfo.crc = FileOperation.getFileCrc32(newFile);
            largeModeInfo.md5 = newMd5;
            largeModifiedSet.add(name);
            largeModifiedMap.put(name, largeModeInfo);
            writeResLog(newFile, oldFile, TypedValue.LARGE_MOD);
            return true;
        }
    }
    modifiedSet.add(name);
    FileOperation.copyFileUsingStream(newFile, outputFile);
    writeResLog(newFile, oldFile, TypedValue.MOD);
    return false;
}

3、ResDiffDecoder.onAllPatchesEnd() 生成res_meta.txt

@Override
public void onAllPatchesEnd() throws IOException, TinkerPatchException {
    //only there is only deleted set, we just ignore
    if (addedSet.isEmpty() && modifiedSet.isEmpty() && largeModifiedSet.isEmpty()) {
        return;
    }

    if (!config.mResRawPattern.contains(TypedValue.RES_ARSC)) {
        throw new TinkerPatchException("resource must contain resources.arsc pattern");
    }
    if (!config.mResRawPattern.contains(TypedValue.RES_MANIFEST)) {
        throw new TinkerPatchException("resource must contain AndroidManifest.xml pattern");
    }

    //check gradle build
    if (config.mUsingGradle) {
        final boolean ignoreWarning = config.mIgnoreWarning;
        final boolean resourceArscChanged = modifiedSet.contains(TypedValue.RES_ARSC)
            || largeModifiedSet.contains(TypedValue.RES_ARSC);
        if (resourceArscChanged && !config.mUseApplyResource) {
            if (ignoreWarning) {
                //ignoreWarning, just log
                Logger.e("Warning:ignoreWarning is true, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times");
            } else {
                Logger.e("Warning:ignoreWarning is false, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times");

                throw new TinkerPatchException(
                    String.format("ignoreWarning is false, but resources.arsc is changed, you should use applyResourceMapping mode to build the new apk, otherwise, it may be crash at some times")
                );
            }
        } /*else if (config.mUseApplyResource) {
            int totalChangeSize = addedSet.size() + modifiedSet.size() + largeModifiedSet.size();
            if (totalChangeSize == 1 && resourceArscChanged) {
                Logger.e("Warning: we are using applyResourceMapping mode to build the new apk, but there is only resources.arsc changed, you should ensure there is actually resource changed!");
            }
        }*/
    }
    //add delete set
    deletedSet.addAll(getDeletedResource(config.mTempUnzipOldDir, config.mTempUnzipNewDir));

    //we can't modify AndroidManifest file
    addedSet.remove(TypedValue.RES_MANIFEST);
    deletedSet.remove(TypedValue.RES_MANIFEST);
    modifiedSet.remove(TypedValue.RES_MANIFEST);
    largeModifiedSet.remove(TypedValue.RES_MANIFEST);
    //remove add, delete or modified if they are in ignore change pattern also
    removeIgnoreChangeFile(modifiedSet);
    removeIgnoreChangeFile(deletedSet);
    removeIgnoreChangeFile(addedSet);
    removeIgnoreChangeFile(largeModifiedSet);

    // after ignore-changes resource files are being removed, we now check if there's any anim
    // resources in added and modified files.
    checkIfSpecificResWasAnimRes(addedSet);
    checkIfSpecificResWasAnimRes(modifiedSet);
    checkIfSpecificResWasAnimRes(largeModifiedSet);

    // last add test res in assets for user cannot ignore it;
    addAssetsFileForTestResource();

    File tempResZip = new File(config.mOutFolder + File.separator + TEMP_RES_ZIP);
    final File tempResFiles = config.mTempResultDir;

    //gen zip resources_out.zip
    FileOperation.zipInputDir(tempResFiles, tempResZip, null);
    File extractToZip = new File(config.mOutFolder + File.separator + TypedValue.RES_OUT);

    String resZipMd5 = Utils.genResOutputFile(extractToZip, tempResZip, config,
        addedSet, modifiedSet, deletedSet, largeModifiedSet, largeModifiedMap);

    Logger.e("Final normal zip resource: %s, size=%d, md5=%s", extractToZip.getName(), extractToZip.length(), resZipMd5);
    logWriter.writeLineToInfoFile(
        String.format("Final normal zip resource: %s, size=%d, md5=%s", extractToZip.getName(), extractToZip.length(), resZipMd5)
    );
    //delete temp file
    FileOperation.deleteFile(tempResZip);

    //first, write resource meta first
    //use resources.arsc's base crc to identify base.apk
    String arscBaseCrc = FileOperation.getZipEntryCrc(config.mOldApkFile, TypedValue.RES_ARSC);
    String arscMd5 = FileOperation.getZipEntryMd5(extractToZip, TypedValue.RES_ARSC);
    if (arscBaseCrc == null || arscMd5 == null) {
        throw new TinkerPatchException("can't find resources.arsc's base crc or md5");
    }

    String resourceMeta = Utils.getResourceMeta(arscBaseCrc, arscMd5);
    writeMetaFile(resourceMeta);

    //pattern
    String patternMeta = TypedValue.PATTERN_TITLE;
    HashSet<String> patterns = new HashSet<>(config.mResRawPattern);
    //we will process them separate
    patterns.remove(TypedValue.RES_MANIFEST);

    writeMetaFile(patternMeta + patterns.size());
    //write pattern
    for (String item : patterns) {
        writeMetaFile(item);
    }

    //add store files
    getCompressMethodFromApk();

    //write meta file, write large modify first
    writeMetaFile(largeModifiedSet, TypedValue.LARGE_MOD);
    writeMetaFile(modifiedSet, TypedValue.MOD);
    writeMetaFile(addedSet, TypedValue.ADD);
    writeMetaFile(deletedSet, TypedValue.DEL);
    writeMetaFile(storedSet, TypedValue.STORED);

}

patch res 合成

1、TinkerPatchService.java doApplyPatch

private static void doApplyPatch(Context context, Intent intent) {
    // Since we may retry with IntentService, we should prevent
    // racing here again.
    if (!sIsPatchApplying.compareAndSet(false, true)) {
        TinkerLog.w(TAG, "TinkerPatchService doApplyPatch is running by another runner.");
        return;
    }

    Tinker tinker = Tinker.with(context);
    tinker.getPatchReporter().onPatchServiceStart(intent);

    if (intent == null) {
        TinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring.");
        return;
    }
    String path = getPatchPathExtra(intent);
    if (path == null) {
        TinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring.");
        return;
    }
    File patchFile = new File(path);

    long begin = SystemClock.elapsedRealtime();
    boolean result;
    long cost;
    Throwable e = null;

    PatchResult patchResult = new PatchResult();
    try {
        if (upgradePatchProcessor == null) {
            throw new TinkerRuntimeException("upgradePatchProcessor is null.");
        }
        result = upgradePatchProcessor.tryPatch(context, path, patchResult);
    } catch (Throwable throwable) {
        e = throwable;
        result = false;
        tinker.getPatchReporter().onPatchException(patchFile, e);
    }

    cost = SystemClock.elapsedRealtime() - begin;
    tinker.getPatchReporter()
            .onPatchResult(patchFile, result, cost);

    patchResult.isSuccess = result;
    patchResult.rawPatchFilePath = path;
    patchResult.costTime = cost;
    patchResult.e = e;

    AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));

    sIsPatchApplying.set(false);
}

2、UpgradePatch.java tryPatch分别对dex、so、res patch加载

if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
    TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
    return false;
}

// check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted
if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {
    TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed");
    return false;
}

if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) {
    TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");
    manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);
    return false;
}

3、ResDiffPatchInternal.java   extractResourceDiffInternals checkAndExtractResourceLargeFile  大文件 BSPatch.patchFast(oldStream, newStream, largeModeInfo.file);

InputStream oldStream = null;
InputStream newStream = null;
try {
    oldStream = apkFile.getInputStream(baseEntry);
    newStream = patchZipFile.getInputStream(patchEntry);
    BSPatch.patchFast(oldStream, newStream, largeModeInfo.file);
} finally {
    StreamUtil.closeQuietly(oldStream);
    StreamUtil.closeQuietly(newStream);
}

patch res 加载
1、TinkerResourceLoader.java loadTinkerResources

public static boolean loadTinkerResources(TinkerApplication application, String directory, Intent intentResult) {
    if (resPatchInfo == null || resPatchInfo.resArscMd5 == null) {
        return true;
    }
    String resourceString = directory + "/" + RESOURCE_PATH +  "/" + RESOURCE_FILE;
    File resourceFile = new File(resourceString);
    long start = System.currentTimeMillis();

    if (application.isTinkerLoadVerifyFlag()) {
        if (!SharePatchFileUtil.checkResourceArscMd5(resourceFile, resPatchInfo.resArscMd5)) {
            Log.e(TAG, "Failed to load resource file, path: " + resourceFile.getPath() + ", expect md5: " + resPatchInfo.resArscMd5);
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_MD5_MISMATCH);
            return false;
        }
        Log.i(TAG, "verify resource file:" + resourceFile.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));
    }
    try {
        TinkerResourcePatcher.monkeyPatchExistingResources(application, resourceString);
        Log.i(TAG, "monkeyPatchExistingResources resource file:" + resourceString + ", use time: " + (System.currentTimeMillis() - start));
    } catch (Throwable e) {
        Log.e(TAG, "install resources failed");
        //remove patch dex if resource is installed failed
        try {
            SystemClassLoaderAdder.uninstallPatchDex(application.getClassLoader());
        } catch (Throwable throwable) {
            Log.e(TAG, "uninstallPatchDex failed", e);
        }
        intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
        ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION);
        return false;
    }

    return true;
}

2、TinkerResourcePatcher.java monkeyPatchExistingResources 依靠反射,通过AssertManager addAssetPath函数,加入外部的资源路径

public static void monkeyPatchExistingResources(Context context, String externalResourceFile) throws Throwable {
    if (externalResourceFile == null) {
        return;
    }

    final ApplicationInfo appInfo = context.getApplicationInfo();

    final Field[] packagesFields;
    if (Build.VERSION.SDK_INT < 27) {
        packagesFields = new Field[]{packagesFiled, resourcePackagesFiled};
    } else {
        packagesFields = new Field[]{packagesFiled};
    }
    for (Field field : packagesFields) {
        final Object value = field.get(currentActivityThread);

        for (Map.Entry<String, WeakReference<?>> entry
                : ((Map<String, WeakReference<?>>) value).entrySet()) {
            final Object loadedApk = entry.getValue().get();
            if (loadedApk == null) {
                continue;
            }
            final String resDirPath = (String) resDir.get(loadedApk);
            if (appInfo.sourceDir.equals(resDirPath)) {
                resDir.set(loadedApk, externalResourceFile);
            }
        }
    }

    // Create a new AssetManager instance and point it to the resources installed under
    if (((Integer) addAssetPathMethod.invoke(newAssetManager, externalResourceFile)) == 0) {
        throw new IllegalStateException("Could not create new AssetManager");
    }

    // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
    // in L, so we do it unconditionally.
    if (stringBlocksField != null && ensureStringBlocksMethod != null) {
        stringBlocksField.set(newAssetManager, null);
        ensureStringBlocksMethod.invoke(newAssetManager);
    }

    for (WeakReference<Resources> wr : references) {
        final Resources resources = wr.get();
        if (resources == null) {
            continue;
        }
        // Set the AssetManager of the Resources instance to our brand new one
        try {
            //pre-N
            assetsFiled.set(resources, newAssetManager);
        } catch (Throwable ignore) {
            // N
            final Object resourceImpl = resourcesImplFiled.get(resources);
            // for Huawei HwResourcesImpl
            final Field implAssets = findField(resourceImpl, "mAssets");
            implAssets.set(resourceImpl, newAssetManager);
        }

        clearPreloadTypedArrayIssue(resources);

        resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
    }

    // Handle issues caused by WebView on Android N.
    // Issue: On Android N, if an activity contains a webview, when screen rotates
    // our resource patch may lost effects.
    // for 5.x/6.x, we found Couldn't expand RemoteView for StatusBarNotification Exception
    if (Build.VERSION.SDK_INT >= 24) {
        try {
            if (publicSourceDirField != null) {
                publicSourceDirField.set(context.getApplicationInfo(), externalResourceFile);
            }
        } catch (Throwable ignore) {
            // Ignored.
        }
    }

    if (!checkResUpdate(context)) {
        throw new TinkerRuntimeException(ShareConstants.CHECK_RES_INSTALL_FAIL);
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值