接入Tinker热修复和踩坑

46 篇文章 1 订阅
8 篇文章 0 订阅

前言

公司最近项目上线后总是遇见各种问题或bug,而我最近就一直在背黑锅,幸亏最近不用上Google Play了,赶紧加上热更新来脱离苦海吧,谁知道接入Tinker的过程中也踩了将近一周的坑,哎...一言难尽

为什么选择Tinker和对比了也懒得说了(其实是因为隔壁阿里收费)

而且需要注意,中间很多版本不要使用最新的,要使用我写的,不然有问题了找都找不到(我在这中间查,试,测,中间用了不知道多长时间)

正文

此次接入Tinker是直接用的Tinker,没有使用Bugly的方式

官方地址

不多感慨了,直接上步骤吧

1.项目的gradle->buildscript.dependencies中加入

classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')//版本号不能改

2.module的gradle->dependencies中加入

implementation('com.tencent.tinker:tinker-android-lib:1.9.9')//版本号不能改

3.改造Application,Tinker有两种改造Application的方法:

第一种手动改造,兼容性最高

第二种使用注解自动改造

本来嫌费事用的第二种,然后接入后各种问题(但也不知这的问题),然后我又改成第一种了

首先写一个类

public class SampleApplicationLike extends DefaultApplicationLike {

    public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
                                 long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //在这里把之前Application中的初始化搬到这里
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        MultiDex.install(base);//这里加上分dex,我用的androidx的

        TinkerManager.setTinkerApplicationLike(this);
        TinkerManager.setUpgradeRetryEnable(true);
        TinkerManager.installTinker(this);
        Tinker.with(getApplication());
    }

然后改造你的Application,让其"真"·什么都不做,改成我这个样子

public class BaseApplication extends TinkerApplication {

    public BaseApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, SampleApplicationLike.class.getName());
    }
}

4.加上以下工具类(在上面的代码中用到了)

/**
 * Created by zhangshaowen on 16/6/30.
 * we use BuildInfo instead of {@link BuildInfo} to make less change
 */
public class BuildInfo {
    /**
     * they are not final, so they won't change with the BuildConfig values!
     */
    public static boolean DEBUG        = BuildConfig.DEBUG;
    public static String VERSION_NAME = BuildConfig.VERSION_NAME;
    public static int     VERSION_CODE = BuildConfig.VERSION_CODE;

    public static String MESSAGE       = BuildConfig.MESSAGE;
    public static String TINKER_ID     = BuildConfig.TINKER_ID;
    public static String PLATFORM      = BuildConfig.PLATFORM;

}
import android.content.Context;
import android.os.Looper;
import android.os.MessageQueue;

import com.tencent.tinker.lib.reporter.DefaultLoadReporter;
import com.tencent.tinker.lib.util.UpgradePatchRetry;
import com.tencent.tinker.loader.shareutil.ShareConstants;

import java.io.File;


/**
 * optional, you can just use DefaultLoadReporter
 * Created by zhangshaowen on 16/4/13.
 */
public class SampleLoadReporter extends DefaultLoadReporter {
    private final static String TAG = "Tinker.SampleLoadReporter";

    public SampleLoadReporter(Context context) {
        super(context);
    }

    @Override
    public void onLoadPatchListenerReceiveFail(final File patchFile, int errorCode) {
        super.onLoadPatchListenerReceiveFail(patchFile, errorCode);
        SampleTinkerReport.onTryApplyFail(errorCode);
    }

    @Override
    public void onLoadResult(File patchDirectory, int loadCode, long cost) {
        super.onLoadResult(patchDirectory, loadCode, cost);
        switch (loadCode) {
            case ShareConstants.ERROR_LOAD_OK:
                SampleTinkerReport.onLoaded(cost);
                break;
        }
        Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {
                    SampleTinkerReport.onReportRetryPatch();
                }
                return false;
            }
        });
    }

    @Override
    public void onLoadException(Throwable e, int errorCode) {
        super.onLoadException(e, errorCode);
        SampleTinkerReport.onLoadException(e, errorCode);
    }

    @Override
    public void onLoadFileMd5Mismatch(File file, int fileType) {
        super.onLoadFileMd5Mismatch(file, fileType);
        SampleTinkerReport.onLoadFileMisMatch(fileType);
    }

    /**
     * try to recover patch oat file
     *
     * @param file
     * @param fileType
     * @param isDirectory
     */
    @Override
    public void onLoadFileNotFound(File file, int fileType, boolean isDirectory) {
        super.onLoadFileNotFound(file, fileType, isDirectory);
        SampleTinkerReport.onLoadFileNotFound(fileType);
    }

    @Override
    public void onLoadPackageCheckFail(File patchFile, int errorCode) {
        super.onLoadPackageCheckFail(patchFile, errorCode);
        SampleTinkerReport.onLoadPackageCheckFail(errorCode);
    }

    @Override
    public void onLoadPatchInfoCorrupted(String oldVersion, String newVersion, File patchInfoFile) {
        super.onLoadPatchInfoCorrupted(oldVersion, newVersion, patchInfoFile);
        SampleTinkerReport.onLoadInfoCorrupted();
    }

    @Override
    public void onLoadInterpret(int type, Throwable e) {
        super.onLoadInterpret(type, e);
        SampleTinkerReport.onLoadInterpretReport(type, e);
    }

    @Override
    public void onLoadPatchVersionChanged(String oldVersion, String newVersion, File patchDirectoryFile, String currentPatchName) {
        super.onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectoryFile, currentPatchName);
    }

}
import android.app.ActivityManager;
import android.content.Context;
import android.content.SharedPreferences;

import com.tencent.tinker.lib.listener.DefaultPatchListener;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;

import java.io.File;
import java.util.Properties;

/**
 * Created by zhangshaowen on 16/4/30.
 * optional, you can just use DefaultPatchListener
 * we can check whatever you want whether we actually send a patch request
 * such as we can check rom space or apk channel
 */
public class SamplePatchListener extends DefaultPatchListener {
    private static final String TAG = "Tinker.SamplePatchListener";

    protected static final long NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN = 60 * 1024 * 1024;

    private final int maxMemory;

    public SamplePatchListener(Context context) {
        super(context);
        maxMemory = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        TinkerLog.i(TAG, "application maxMemory:" + maxMemory);
    }

    /**
     * because we use the defaultCheckPatchReceived method
     * the error code define by myself should after {@code ShareConstants.ERROR_RECOVER_INSERVICE
     *
     * @param path
     * @param newPatch
     * @return
     */
    @Override
    public int patchCheck(String path, String patchMd5) {
        File patchFile = new File(path);
        TinkerLog.i(TAG, "receive a patch file: %s, file size:%d", path, SharePatchFileUtil.getFileOrDirectorySize(patchFile));
        int returnCode = super.patchCheck(path, patchMd5);

        if (returnCode == ShareConstants.ERROR_PATCH_OK) {
            returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory);
        }

        if (returnCode == ShareConstants.ERROR_PATCH_OK) {
            SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);
            //optional, only disable this patch file with md5
            int fastCrashCount = sp.getInt(patchMd5, 0);
            if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) {
                returnCode = Utils.ERROR_PATCH_CRASH_LIMIT;
            }
        }
        // Warning, it is just a sample case, you don't need to copy all of these
        // Interception some of the request
        if (returnCode == ShareConstants.ERROR_PATCH_OK) {
            Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile);
            if (properties == null) {
                returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
            } else {
                String platform = properties.getProperty(Utils.PLATFORM);
                TinkerLog.i(TAG, "get platform:" + platform);
                // check patch platform require
                if (platform == null || !platform.equals(BuildInfo.PLATFORM)) {
                    returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
                }
            }
        }

        SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);
        return returnCode;
    }
}
import android.content.Context;
import android.content.Intent;

import com.tencent.tinker.lib.reporter.DefaultPatchReporter;
import com.tencent.tinker.loader.shareutil.SharePatchInfo;

import java.io.File;
import java.util.List;

/**
 * optional, you can just use DefaultPatchReporter
 * Created by zhangshaowen on 16/4/8.
 */
public class SamplePatchReporter extends DefaultPatchReporter {
    private final static String TAG = "Tinker.SamplePatchReporter";
    public SamplePatchReporter(Context context) {
        super(context);
    }

    @Override
    public void onPatchServiceStart(Intent intent) {
        super.onPatchServiceStart(intent);
        SampleTinkerReport.onApplyPatchServiceStart();
    }

    @Override
    public void onPatchDexOptFail(File patchFile, List<File> dexFiles, Throwable t) {
        super.onPatchDexOptFail(patchFile, dexFiles, t);
        SampleTinkerReport.onApplyDexOptFail(t);
    }

    @Override
    public void onPatchException(File patchFile, Throwable e) {
        super.onPatchException(patchFile, e);
        SampleTinkerReport.onApplyCrash(e);
    }

    @Override
    public void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion) {
        super.onPatchInfoCorrupted(patchFile, oldVersion, newVersion);
        SampleTinkerReport.onApplyInfoCorrupted();
    }

    @Override
    public void onPatchPackageCheckFail(File patchFile, int errorCode) {
        super.onPatchPackageCheckFail(patchFile, errorCode);
        SampleTinkerReport.onApplyPackageCheckFail(errorCode);
    }

    @Override
    public void onPatchResult(File patchFile, boolean success, long cost) {
        super.onPatchResult(patchFile, success, cost);
        SampleTinkerReport.onApplied(cost, success);
    }

    @Override
    public void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType) {
        super.onPatchTypeExtractFail(patchFile, extractTo, filename, fileType);
        SampleTinkerReport.onApplyExtractFail(fileType);
    }

    @Override
    public void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion) {
        super.onPatchVersionCheckFail(patchFile, oldPatchInfo, patchFileVersion);
        SampleTinkerReport.onApplyVersionCheckFail();
    }
}
import java.io.File;

/**
 * optional, you can just use DefaultTinkerResultService
 * we can restart process when we are at background or screen off
 * Created by zhangshaowen on 16/4/13.
 */
public class SampleResultService extends DefaultTinkerResultService {
    private static final String TAG = "Tinker.SampleResultService";
    private static boolean isUploaded = false;

    @Override
    public void onPatchResult(final PatchResult result) {
        if (result == null) {
            TinkerLog.e(TAG, "SampleResultService received null result!!!!");
            return;
        }
        TinkerLog.i(TAG, "SampleResultService receive result: %s", result.toString());

        //first, we want to kill the recover process
        TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());

        //在这里自定义处理加载成功和失败
        String log = result.isSuccess ? "patch success, please restart process  补丁加载成功,请重启应用" : "patch fail, please check reason  补丁加载失败!!!!!!!!!!!";
        try {
            if (!isUploaded) {
                StringExtendFunKt.showDebugToast(log);
                StringExtendFunKt.w(log, "lllttt_tinker");
                UmengDiyEventUtilKt.actionEvent(SampleApplicationLike.getApp(),
                        "updateHotFix",
                        new UmengEventBean("fixSuccess", result.isSuccess),
                        new UmengEventBean("systemInfo", JSONObject.toJSONString(YxUtil.getInstance().getInputDevice()))
                );
            }
            isUploaded = true;
        } catch (Exception e) {
            AnyExtendFunKt.upload(e);
        }

        // is success and newPatch, it is nice to delete the raw file, and restart at once
        // for old patch, you can't delete the patch file
        if (result.isSuccess) {
            deleteRawPatchFile(new File(result.rawPatchFilePath));

            //not like TinkerResultService, I want to restart just when I am at background!
            //if you have not install tinker this moment, you can use TinkerApplicationHelper api
            if (checkIfNeedKill(result)) {
                if (Utils.isBackground()) {
                    TinkerLog.i(TAG, "it is in background, just restart process");
                    restartProcess();
                } else {
                    //we can wait process at background, such as onAppBackground
                    //or we can restart when the screen off
                    TinkerLog.i(TAG, "tinker wait screen to restart process");
                    new Utils.ScreenState(getApplicationContext(), new Utils.ScreenState.IOnScreenOff() {
                        @Override
                        public void onScreenOff() {
                            restartProcess();
                        }
                    });
                }
            } else {
                TinkerLog.i(TAG, "I have already install the newly patch version!");
            }
        }
    }

    /**
     * you can restart your process through service or broadcast
     */
    private void restartProcess() {
        TinkerLog.i(TAG, "app is background now, i can kill quietly");
        //you can send service or broadcast intent to restart your process
        android.os.Process.killProcess(android.os.Process.myPid());
    }

}
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;

/**
 * a simple tinker data reporter
 * Created by zhangshaowen on 16/9/17.
 */
public class SampleTinkerReport {
    private static final String TAG = "Tinker.SampleTinkerReport";

    // KEY - PV
    public static final int KEY_REQUEST                   = 0;
    public static final int KEY_DOWNLOAD                  = 1;
    public static final int KEY_TRY_APPLY                 = 2;
    public static final int KEY_TRY_APPLY_SUCCESS         = 3;
    public static final int KEY_APPLIED_START             = 4;
    public static final int KEY_APPLIED                   = 5;
    public static final int KEY_LOADED                    = 6;
    public static final int KEY_CRASH_FAST_PROTECT        = 7;
    public static final int KEY_CRASH_CAUSE_XPOSED_DALVIK = 8;
    public static final int KEY_CRASH_CAUSE_XPOSED_ART    = 9;
    public static final int KEY_APPLY_WITH_RETRY          = 10;

    //Key -- try apply detail
    public static final int KEY_TRY_APPLY_UPGRADE                 = 70;
    public static final int KEY_TRY_APPLY_DISABLE                 = 71;
    public static final int KEY_TRY_APPLY_RUNNING                 = 72;
    public static final int KEY_TRY_APPLY_INSERVICE               = 73;
    public static final int KEY_TRY_APPLY_NOT_EXIST               = 74;
    public static final int KEY_TRY_APPLY_GOOGLEPLAY              = 75;
    public static final int KEY_TRY_APPLY_ROM_SPACE               = 76;
    public static final int KEY_TRY_APPLY_ALREADY_APPLY           = 77;
    public static final int KEY_TRY_APPLY_MEMORY_LIMIT            = 78;
    public static final int KEY_TRY_APPLY_CRASH_LIMIT             = 79;
    public static final int KEY_TRY_APPLY_CONDITION_NOT_SATISFIED = 80;
    public static final int KEY_TRY_APPLY_JIT                     = 81;

    //Key -- apply detail
    public static final int KEY_APPLIED_UPGRADE      = 100;
    public static final int KEY_APPLIED_UPGRADE_FAIL = 101;

    public static final int KEY_APPLIED_EXCEPTION                               = 120;
    public static final int KEY_APPLIED_DEXOPT_OTHER                            = 121;
    public static final int KEY_APPLIED_DEXOPT_EXIST                            = 122;
    public static final int KEY_APPLIED_DEXOPT_FORMAT                           = 123;
    public static final int KEY_APPLIED_INFO_CORRUPTED                          = 124;
    //package check
    public static final int KEY_APPLIED_PACKAGE_CHECK_SIGNATURE                 = 150;
    public static final int KEY_APPLIED_PACKAGE_CHECK_DEX_META                  = 151;
    public static final int KEY_APPLIED_PACKAGE_CHECK_LIB_META                  = 152;
    public static final int KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND   = 153;
    public static final int KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 154;
    public static final int KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND            = 155;
    public static final int KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL       = 156;
    public static final int KEY_APPLIED_PACKAGE_CHECK_RES_META                  = 157;
    public static final int KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT    = 158;

    //version check
    public static final int KEY_APPLIED_VERSION_CHECK      = 180;
    //extract error
    public static final int KEY_APPLIED_PATCH_FILE_EXTRACT = 181;
    public static final int KEY_APPLIED_DEX_EXTRACT        = 182;
    public static final int KEY_APPLIED_LIB_EXTRACT        = 183;
    public static final int KEY_APPLIED_RESOURCE_EXTRACT   = 184;
    //cost time
    public static final int KEY_APPLIED_SUCC_COST_5S_LESS  = 200;
    public static final int KEY_APPLIED_SUCC_COST_10S_LESS = 201;
    public static final int KEY_APPLIED_SUCC_COST_30S_LESS = 202;
    public static final int KEY_APPLIED_SUCC_COST_60S_LESS = 203;
    public static final int KEY_APPLIED_SUCC_COST_OTHER    = 204;

    public static final int KEY_APPLIED_FAIL_COST_5S_LESS  = 205;
    public static final int KEY_APPLIED_FAIL_COST_10S_LESS = 206;
    public static final int KEY_APPLIED_FAIL_COST_30S_LESS = 207;
    public static final int KEY_APPLIED_FAIL_COST_60S_LESS = 208;
    public static final int KEY_APPLIED_FAIL_COST_OTHER    = 209;


    // KEY -- load detail
    public static final int KEY_LOADED_UNKNOWN_EXCEPTION        = 250;
    public static final int KEY_LOADED_UNCAUGHT_EXCEPTION       = 251;
    public static final int KEY_LOADED_EXCEPTION_DEX            = 252;
    public static final int KEY_LOADED_EXCEPTION_DEX_CHECK      = 253;
    public static final int KEY_LOADED_EXCEPTION_RESOURCE       = 254;
    public static final int KEY_LOADED_EXCEPTION_RESOURCE_CHECK = 255;


    public static final int KEY_LOADED_MISMATCH_DEX       = 300;
    public static final int KEY_LOADED_MISMATCH_LIB       = 301;
    public static final int KEY_LOADED_MISMATCH_RESOURCE  = 302;
    public static final int KEY_LOADED_MISSING_DEX        = 303;
    public static final int KEY_LOADED_MISSING_LIB        = 304;
    public static final int KEY_LOADED_MISSING_PATCH_FILE = 305;
    public static final int KEY_LOADED_MISSING_PATCH_INFO = 306;
    public static final int KEY_LOADED_MISSING_DEX_OPT    = 307;
    public static final int KEY_LOADED_MISSING_RES        = 308;
    public static final int KEY_LOADED_INFO_CORRUPTED     = 309;

    //load package check
    public static final int KEY_LOADED_PACKAGE_CHECK_SIGNATURE                 = 350;
    public static final int KEY_LOADED_PACKAGE_CHECK_DEX_META                  = 351;
    public static final int KEY_LOADED_PACKAGE_CHECK_LIB_META                  = 352;
    public static final int KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND   = 353;
    public static final int KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND = 354;
    public static final int KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL       = 355;
    public static final int KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND    = 356;
    public static final int KEY_LOADED_PACKAGE_CHECK_RES_META                  = 357;
    public static final int KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT    = 358;


    public static final int KEY_LOADED_SUCC_COST_500_LESS  = 400;
    public static final int KEY_LOADED_SUCC_COST_1000_LESS = 401;
    public static final int KEY_LOADED_SUCC_COST_3000_LESS = 402;
    public static final int KEY_LOADED_SUCC_COST_5000_LESS = 403;
    public static final int KEY_LOADED_SUCC_COST_OTHER     = 404;

    public static final int KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR = 450;
    public static final int KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR   = 451;
    public static final int KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK         = 452;


    interface Reporter {
        void onReport(int key);

        void onReport(String message);
    }

    private static Reporter reporter = null;

    public void setReporter(Reporter reporter) {
        this.reporter = reporter;
    }

    public static void onTryApply(boolean success) {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_TRY_APPLY);

        reporter.onReport(KEY_TRY_APPLY_UPGRADE);

        if (success) {
            reporter.onReport(KEY_TRY_APPLY_SUCCESS);
        }
    }

    public static void onTryApplyFail(int errorCode) {
        if (reporter == null) {
            return;
        }
        switch (errorCode) {
            case ShareConstants.ERROR_PATCH_NOTEXIST:
                reporter.onReport(KEY_TRY_APPLY_NOT_EXIST);
                break;
            case ShareConstants.ERROR_PATCH_DISABLE:
                reporter.onReport(KEY_TRY_APPLY_DISABLE);
                break;
            case ShareConstants.ERROR_PATCH_INSERVICE:
                reporter.onReport(KEY_TRY_APPLY_INSERVICE);
                break;
            case ShareConstants.ERROR_PATCH_RUNNING:
                reporter.onReport(KEY_TRY_APPLY_RUNNING);
                break;
            case ShareConstants.ERROR_PATCH_JIT:
                reporter.onReport(KEY_TRY_APPLY_JIT);
                break;
            case Utils.ERROR_PATCH_ROM_SPACE:
                reporter.onReport(KEY_TRY_APPLY_ROM_SPACE);
                break;
            case Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL:
                reporter.onReport(KEY_TRY_APPLY_GOOGLEPLAY);
                break;
            case ShareConstants.ERROR_PATCH_ALREADY_APPLY:
                reporter.onReport(KEY_TRY_APPLY_ALREADY_APPLY);
                break;
            case Utils.ERROR_PATCH_CRASH_LIMIT:
                reporter.onReport(KEY_TRY_APPLY_CRASH_LIMIT);
                break;
            case Utils.ERROR_PATCH_MEMORY_LIMIT:
                reporter.onReport(KEY_TRY_APPLY_MEMORY_LIMIT);
                break;
            case Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED:
                reporter.onReport(KEY_TRY_APPLY_CONDITION_NOT_SATISFIED);
                break;

        }
    }

    public static void onLoadPackageCheckFail(int errorCode) {
        if (reporter == null) {
            return;
        }
        switch (errorCode) {
            case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_SIGNATURE);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_DEX_META);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_LIB_META);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);

                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_RES_META);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
                reporter.onReport(KEY_LOADED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
                break;
        }
    }

    public static void onLoaded(long cost) {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_LOADED);

        if (cost < 0L) {
            TinkerLog.e(TAG, "hp_report report load cost failed, invalid cost");
            return;
        }

        if (cost <= 500) {
            reporter.onReport(KEY_LOADED_SUCC_COST_500_LESS);
        } else if (cost <= 1000) {
            reporter.onReport(KEY_LOADED_SUCC_COST_1000_LESS);
        } else if (cost <= 3000) {
            reporter.onReport(KEY_LOADED_SUCC_COST_3000_LESS);
        } else if (cost <= 5000) {
            reporter.onReport(KEY_LOADED_SUCC_COST_5000_LESS);
        } else {
            reporter.onReport(KEY_LOADED_SUCC_COST_OTHER);
        }
    }

    public static void onLoadInfoCorrupted() {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_LOADED_INFO_CORRUPTED);
    }

    public static void onLoadFileNotFound(int fileType) {
        if (reporter == null) {
            return;
        }
        switch (fileType) {
            case ShareConstants.TYPE_DEX_OPT:
                reporter.onReport(KEY_LOADED_MISSING_DEX_OPT);
                break;
            case ShareConstants.TYPE_DEX:
                reporter.onReport(KEY_LOADED_MISSING_DEX);
                break;
            case ShareConstants.TYPE_LIBRARY:
                reporter.onReport(KEY_LOADED_MISSING_LIB);
                break;
            case ShareConstants.TYPE_PATCH_FILE:
                reporter.onReport(KEY_LOADED_MISSING_PATCH_FILE);
                break;
            case ShareConstants.TYPE_PATCH_INFO:
                reporter.onReport(KEY_LOADED_MISSING_PATCH_INFO);
                break;
            case ShareConstants.TYPE_RESOURCE:
                reporter.onReport(KEY_LOADED_MISSING_RES);
                break;
        }
    }

    public static void onLoadInterpretReport(int type, Throwable e) {
        if (reporter == null) {
            return;
        }
        switch (type) {
            case ShareConstants.TYPE_INTERPRET_GET_INSTRUCTION_SET_ERROR:
                reporter.onReport(KEY_LOADED_INTERPRET_GET_INSTRUCTION_SET_ERROR);
                reporter.onReport("Tinker Exception:interpret occur exception " + Utils.getExceptionCauseString(e));
                break;
            case ShareConstants.TYPE_INTERPRET_COMMAND_ERROR:
                reporter.onReport(KEY_LOADED_INTERPRET_INTERPRET_COMMAND_ERROR);
                reporter.onReport("Tinker Exception:interpret occur exception " + Utils.getExceptionCauseString(e));
                break;
            case ShareConstants.TYPE_INTERPRET_OK:
                reporter.onReport(KEY_LOADED_INTERPRET_TYPE_INTERPRET_OK);
                break;
        }
    }

    public static void onLoadFileMisMatch(int fileType) {
        if (reporter == null) {
            return;
        }
        switch (fileType) {
            case ShareConstants.TYPE_DEX:
                reporter.onReport(KEY_LOADED_MISMATCH_DEX);
                break;
            case ShareConstants.TYPE_LIBRARY:
                reporter.onReport(KEY_LOADED_MISMATCH_LIB);
                break;
            case ShareConstants.TYPE_RESOURCE:
                reporter.onReport(KEY_LOADED_MISMATCH_RESOURCE);
                break;
        }
    }

    public static void onLoadException(Throwable throwable, int errorCode) {
        if (reporter == null) {
            return;
        }
        boolean isCheckFail = false;
        switch (errorCode) {
            case ShareConstants.ERROR_LOAD_EXCEPTION_DEX:
                if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) {
                    reporter.onReport(KEY_LOADED_EXCEPTION_DEX_CHECK);
                    isCheckFail = true;
                    TinkerLog.e(TAG, "tinker dex check fail:" + throwable.getMessage());
                } else {
                    reporter.onReport(KEY_LOADED_EXCEPTION_DEX);
                    TinkerLog.e(TAG, "tinker dex reflect fail:" + throwable.getMessage());
                }
                break;
            case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE:
                if (throwable.getMessage().contains(ShareConstants.CHECK_RES_INSTALL_FAIL)) {
                    reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE_CHECK);
                    isCheckFail = true;
                    TinkerLog.e(TAG, "tinker res check fail:" + throwable.getMessage());
                } else {
                    reporter.onReport(KEY_LOADED_EXCEPTION_RESOURCE);
                    TinkerLog.e(TAG, "tinker res reflect fail:" + throwable.getMessage());
                }
                break;
            case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT:
                reporter.onReport(KEY_LOADED_UNCAUGHT_EXCEPTION);
                break;
            case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:
                reporter.onReport(KEY_LOADED_UNKNOWN_EXCEPTION);
                break;
        }
        //reporter exception, for dex check fail, we don't need to report stacktrace
        if (!isCheckFail) {
            reporter.onReport("Tinker Exception:load tinker occur exception " + Utils.getExceptionCauseString(throwable));
        }
    }

    public static void onApplyPatchServiceStart() {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_APPLIED_START);
    }

    public static void onApplyDexOptFail(Throwable throwable) {
        if (reporter == null) {
            return;
        }
        if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_EXIST_FAIL)) {
            reporter.onReport(KEY_APPLIED_DEXOPT_EXIST);
        } else if (throwable.getMessage().contains(ShareConstants.CHECK_DEX_OAT_FORMAT_FAIL)) {
            reporter.onReport(KEY_APPLIED_DEXOPT_FORMAT);
        } else {
            reporter.onReport(KEY_APPLIED_DEXOPT_OTHER);
            reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
        }
    }

    public static void onApplyInfoCorrupted() {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_APPLIED_INFO_CORRUPTED);
    }

    public static void onApplyVersionCheckFail() {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_APPLIED_VERSION_CHECK);
    }

    public static void onApplyExtractFail(int fileType) {
        if (reporter == null) {
            return;
        }
        switch (fileType) {
            case ShareConstants.TYPE_DEX:
                reporter.onReport(KEY_APPLIED_DEX_EXTRACT);
                break;
            case ShareConstants.TYPE_LIBRARY:
                reporter.onReport(KEY_APPLIED_LIB_EXTRACT);
                break;
            case ShareConstants.TYPE_PATCH_FILE:
                reporter.onReport(KEY_APPLIED_PATCH_FILE_EXTRACT);
                break;
            case ShareConstants.TYPE_RESOURCE:
                reporter.onReport(KEY_APPLIED_RESOURCE_EXTRACT);
                break;
        }
    }

    public static void onApplied(long cost, boolean success) {
        if (reporter == null) {
            return;
        }
        if (success) {
            reporter.onReport(KEY_APPLIED);
        }

        if (success) {
            reporter.onReport(KEY_APPLIED_UPGRADE);
        } else {
            reporter.onReport(KEY_APPLIED_UPGRADE_FAIL);
        }

        TinkerLog.i(TAG, "hp_report report apply cost = %d", cost);

        if (cost < 0L) {
            TinkerLog.e(TAG, "hp_report report apply cost failed, invalid cost");
            return;
        }

        if (cost <= 5000) {
            if (success) {
                reporter.onReport(KEY_APPLIED_SUCC_COST_5S_LESS);
            } else {
                reporter.onReport(KEY_APPLIED_FAIL_COST_5S_LESS);
            }
        } else if (cost <= 10 * 1000) {
            if (success) {
                reporter.onReport(KEY_APPLIED_SUCC_COST_10S_LESS);
            } else {
                reporter.onReport(KEY_APPLIED_FAIL_COST_10S_LESS);
            }
        } else if (cost <= 30 * 1000) {
            if (success) {
                reporter.onReport(KEY_APPLIED_SUCC_COST_30S_LESS);
            } else {
                reporter.onReport(KEY_APPLIED_FAIL_COST_30S_LESS);
            }
        } else if (cost <= 60 * 1000) {
            if (success) {
                reporter.onReport(KEY_APPLIED_SUCC_COST_60S_LESS);
            } else {
                reporter.onReport(KEY_APPLIED_FAIL_COST_60S_LESS);
            }
        } else {
            if (success) {
                reporter.onReport(KEY_APPLIED_SUCC_COST_OTHER);
            } else {
                reporter.onReport(KEY_APPLIED_FAIL_COST_OTHER);
            }
        }
    }

    public static void onApplyPackageCheckFail(int errorCode) {
        if (reporter == null) {
            return;
        }
        TinkerLog.i(TAG, "hp_report package check failed, error = %d", errorCode);

        switch (errorCode) {
            case ShareConstants.ERROR_PACKAGE_CHECK_SIGNATURE_FAIL:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_SIGNATURE);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_DEX_META);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_LIB_META_CORRUPTED:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_LIB_META);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_PATCH_TINKER_ID_NOT_FOUND);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_APK_TINKER_ID_NOT_FOUND);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKER_ID_NOT_EQUAL);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_PACKAGE_META_NOT_FOUND:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_META_NOT_FOUND);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_RESOURCE_META_CORRUPTED:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_RES_META);
                break;
            case ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT:
                reporter.onReport(KEY_APPLIED_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT);
                break;
        }
    }

    public static void onApplyCrash(Throwable throwable) {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_APPLIED_EXCEPTION);
        reporter.onReport("Tinker Exception:apply tinker occur exception " + Utils.getExceptionCauseString(throwable));
    }

    public static void onFastCrashProtect() {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_CRASH_FAST_PROTECT);
    }

    public static void onXposedCrash() {
        if (reporter == null) {
            return;
        }
        if (ShareTinkerInternals.isVmArt()) {
            reporter.onReport(KEY_CRASH_CAUSE_XPOSED_ART);
        } else {
            reporter.onReport(KEY_CRASH_CAUSE_XPOSED_DALVIK);
        }
    }

    public static void onReportRetryPatch() {
        if (reporter == null) {
            return;
        }
        reporter.onReport(KEY_APPLY_WITH_RETRY);
    }

}
import android.content.Context;
import android.content.SharedPreferences;
import android.os.SystemClock;

import com.tencent.tinker.entry.ApplicationLike;
import com.tencent.tinker.lib.tinker.TinkerApplicationHelper;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.ShareTinkerInternals;

/**
 * optional, use dynamic configuration is better way
 * for native crash,
 * <p/>
 * Created by zhangshaowen on 16/7/3.
 * tinker's crash is caught by {@code LoadReporter.onLoadException}
 * use {@code TinkerApplicationHelper} api, no need to install tinker!
 */
public class SampleUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "Tinker.SampleUncaughtExHandler";

    private final Thread.UncaughtExceptionHandler ueh;
    private static final long   QUICK_CRASH_ELAPSE  = 10 * 1000;
    public static final  int    MAX_CRASH_COUNT     = 3;
    private static final String DALVIK_XPOSED_CRASH = "Class ref in pre-verified class resolved to unexpected implementation";

    public SampleUncaughtExceptionHandler() {
        ueh = Thread.getDefaultUncaughtExceptionHandler();
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        TinkerLog.e(TAG, "uncaughtException:" + ex.getMessage());
        tinkerFastCrashProtect();
        tinkerPreVerifiedCrashHandler(ex);
        ueh.uncaughtException(thread, ex);
    }

    /**
     * Such as Xposed, if it try to load some class before we load from patch files.
     * With dalvik, it will crash with "Class ref in pre-verified class resolved to unexpected implementation".
     * With art, it may crash at some times. But we can't know the actual crash type.
     * If it use Xposed, we can just clean patch or mention user to uninstall it.
     */
    private void tinkerPreVerifiedCrashHandler(Throwable ex) {
        ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();
        if (applicationLike == null || applicationLike.getApplication() == null) {
            TinkerLog.w(TAG, "applicationlike is null");
            return;
        }

        if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {
            TinkerLog.w(TAG, "tinker is not loaded");
            return;
        }

        Throwable throwable = ex;
        boolean isXposed = false;
        while (throwable != null) {
            if (!isXposed) {
                isXposed = Utils.isXposedExists(throwable);
            }

            // xposed?
            if (isXposed) {
                boolean isCausedByXposed = false;
                //for art, we can't know the actually crash type
                //just ignore art
                if (throwable instanceof IllegalAccessError && throwable.getMessage().contains(DALVIK_XPOSED_CRASH)) {
                    //for dalvik, we know the actual crash type
                    isCausedByXposed = true;
                }

                if (isCausedByXposed) {
                    SampleTinkerReport.onXposedCrash();
                    TinkerLog.e(TAG, "have xposed: just clean tinker");
                    //kill all other process to ensure that all process's code is the same.
                    ShareTinkerInternals.killAllOtherProcess(applicationLike.getApplication());

                    TinkerApplicationHelper.cleanPatch(applicationLike);
                    ShareTinkerInternals.setTinkerDisableWithSharedPreferences(applicationLike.getApplication());
                    return;
                }
            }
            throwable = throwable.getCause();
        }
    }

    /**
     * if tinker is load, and it crash more than MAX_CRASH_COUNT, then we just clean patch.
     */
    private boolean tinkerFastCrashProtect() {
        ApplicationLike applicationLike = TinkerManager.getTinkerApplicationLike();

        if (applicationLike == null || applicationLike.getApplication() == null) {
            return false;
        }
        if (!TinkerApplicationHelper.isTinkerLoadSuccess(applicationLike)) {
            return false;
        }

        final long elapsedTime = SystemClock.elapsedRealtime() - applicationLike.getApplicationStartElapsedTime();
        //this process may not install tinker, so we use TinkerApplicationHelper api
        if (elapsedTime < QUICK_CRASH_ELAPSE) {
            String currentVersion = TinkerApplicationHelper.getCurrentVersion(applicationLike);
            if (ShareTinkerInternals.isNullOrNil(currentVersion)) {
                return false;
            }

            SharedPreferences sp = applicationLike.getApplication().getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);
            int fastCrashCount = sp.getInt(currentVersion, 0) + 1;
            if (fastCrashCount >= MAX_CRASH_COUNT) {
                SampleTinkerReport.onFastCrashProtect();
                TinkerApplicationHelper.cleanPatch(applicationLike);
                TinkerLog.e(TAG, "tinker has fast crash more than %d, we just clean patch!", fastCrashCount);
                return true;
            } else {
                sp.edit().putInt(currentVersion, fastCrashCount).commit();
                TinkerLog.e(TAG, "tinker has fast crash %d times", fastCrashCount);
            }
        }

        return false;
    }
}
import com.tencent.tinker.entry.ApplicationLike;
import com.tencent.tinker.lib.listener.PatchListener;
import com.tencent.tinker.lib.patch.AbstractPatch;
import com.tencent.tinker.lib.patch.UpgradePatch;
import com.tencent.tinker.lib.reporter.LoadReporter;
import com.tencent.tinker.lib.reporter.PatchReporter;
import com.tencent.tinker.lib.tinker.TinkerInstaller;
import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.lib.util.UpgradePatchRetry;

/**
 * Created by zhangshaowen on 16/7/3.
 */
public class TinkerManager {
    private static final String TAG = "Tinker.TinkerManager";

    private static ApplicationLike applicationLike;
    private static SampleUncaughtExceptionHandler uncaughtExceptionHandler;
    private static boolean isInstalled = false;

    public static void setTinkerApplicationLike(ApplicationLike appLike) {
        applicationLike = appLike;
    }

    public static ApplicationLike getTinkerApplicationLike() {
        return applicationLike;
    }

    public static void initFastCrashProtect() {
        if (uncaughtExceptionHandler == null) {
            uncaughtExceptionHandler = new SampleUncaughtExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
        }
    }

    public static void setUpgradeRetryEnable(boolean enable) {
        UpgradePatchRetry.getInstance(applicationLike.getApplication()).setRetryEnable(enable);
    }


    /**
     * all use default class, simply Tinker install method
     */
    public static void sampleInstallTinker(ApplicationLike appLike) {
        if (isInstalled) {
            TinkerLog.w(TAG, "install tinker, but has installed, ignore");
            return;
        }
        TinkerInstaller.install(appLike);
        isInstalled = true;

    }

    /**
     * you can specify all class you want.
     * sometimes, you can only install tinker in some process you want!
     *
     * @param appLike
     */
    public static void installTinker(ApplicationLike appLike) {
        if (isInstalled) {
            TinkerLog.w(TAG, "install tinker, but has installed, ignore");
            return;
        }
        //or you can just use DefaultLoadReporter
        LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());
        //or you can just use DefaultPatchReporter
        PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());
        //or you can just use DefaultPatchListener
        PatchListener patchListener = new SamplePatchListener(appLike.getApplication());
        //you can set your own upgrade patch if you need
        AbstractPatch upgradePatchProcessor = new UpgradePatch();

        TinkerInstaller.install(appLike,
            loadReporter, patchReporter, patchListener,
            SampleResultService.class, upgradePatchProcessor);

        isInstalled = true;
    }
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Environment;
import android.os.StatFs;

import com.tencent.tinker.lib.util.TinkerLog;
import com.tencent.tinker.loader.shareutil.ShareConstants;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;

/**
 * Created by zhangshaowen on 16/4/7.
 */
public class Utils {
    private static final String TAG = "Tinker.Utils";

    /**
     * the error code define by myself
     * should after {@code ShareConstants.ERROR_PATCH_INSERVICE
     */
    public static final int ERROR_PATCH_GOOGLEPLAY_CHANNEL      = -20;
    public static final int ERROR_PATCH_ROM_SPACE               = -21;
    public static final int ERROR_PATCH_MEMORY_LIMIT            = -22;
    public static final int ERROR_PATCH_CRASH_LIMIT             = -23;
    public static final int ERROR_PATCH_CONDITION_NOT_SATISFIED = -24;

    public static final String PLATFORM = "platform";

    public static final int MIN_MEMORY_HEAP_SIZE = 45;

    private static boolean background = false;

    public static boolean isGooglePlay() {
        return false;
    }

    public static boolean isBackground() {
        return background;
    }

    public static void setBackground(boolean back) {
        background = back;
    }

    public static int checkForPatchRecover(long roomSize, int maxMemory) {
        if (Utils.isGooglePlay()) {
            return Utils.ERROR_PATCH_GOOGLEPLAY_CHANNEL;
        }
        if (maxMemory < MIN_MEMORY_HEAP_SIZE) {
            return Utils.ERROR_PATCH_MEMORY_LIMIT;
        }
        //or you can mention user to clean their rom space!
        if (!checkRomSpaceEnough(roomSize)) {
            return Utils.ERROR_PATCH_ROM_SPACE;
        }

        return ShareConstants.ERROR_PATCH_OK;
    }

    public static boolean isXposedExists(Throwable thr) {
        StackTraceElement[] stackTraces = thr.getStackTrace();
        for (StackTraceElement stackTrace : stackTraces) {
            final String clazzName = stackTrace.getClassName();
            if (clazzName != null && clazzName.contains("de.robv.android.xposed.XposedBridge")) {
                return true;
            }
        }
        return false;
    }

    @Deprecated
    public static boolean checkRomSpaceEnough(long limitSize) {
        long allSize;
        long availableSize = 0;
        try {
            File data = Environment.getDataDirectory();
            StatFs sf = new StatFs(data.getPath());
            availableSize = (long) sf.getAvailableBlocks() * (long) sf.getBlockSize();
            allSize = (long) sf.getBlockCount() * (long) sf.getBlockSize();
        } catch (Exception e) {
            allSize = 0;
        }

        if (allSize != 0 && availableSize > limitSize) {
            return true;
        }
        return false;
    }

    public static String getExceptionCauseString(final Throwable ex) {
        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        final PrintStream ps = new PrintStream(bos);

        try {
            // print directly
            Throwable t = ex;
            while (t.getCause() != null) {
                t = t.getCause();
            }
            t.printStackTrace(ps);
            return toVisualString(bos.toString());
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static String toVisualString(String src) {
        boolean cutFlg = false;

        if (null == src) {
            return null;
        }

        char[] chr = src.toCharArray();
        if (null == chr) {
            return null;
        }

        int i = 0;
        for (; i < chr.length; i++) {
            if (chr[i] > 127) {
                chr[i] = 0;
                cutFlg = true;
                break;
            }
        }

        if (cutFlg) {
            return new String(chr, 0, i);
        } else {
            return src;
        }
    }

    public static class ScreenState {
        public interface IOnScreenOff {
            void onScreenOff();
        }

        public ScreenState(final Context context, final IOnScreenOff onScreenOffInterface) {
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_OFF);

            context.registerReceiver(new BroadcastReceiver() {

                @Override
                public void onReceive(Context context, Intent in) {
                    String action = in == null ? "" : in.getAction();
                    TinkerLog.i(TAG, "ScreenReceiver action [%s] ", action);
                    if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                        if (onScreenOffInterface != null) {
                            onScreenOffInterface.onScreenOff();
                        }
                    }
                    context.unregisterReceiver(this);
                }
            }, filter);
        }
    }
}

5.配置清单文件

Application配置上刚才写的那个BaseApplication

然后配置service

        <service
            android:name=".app.tinker.SampleResultService"
            android:exported="false" />

6.版本号配置

gradle版本改为3.5.3

classpath 'com.android.tools.build:gradle:3.5.3'

gradle.zip版本修改(在gradle-wrapper.properties文件内)

distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

app.gradle中修改版本号

compileSdkVersion 29 //推荐
buildToolsVersion '29.0.3' //推荐
minSdkVersion 19//必须小于等于19,因为大于19之后就改了生成dex的方式,这个东西坑了我好久
minifyEnabled false//公司项目里没有用混淆,用的是加固,这里如果打开会有很多配置不相同,没有试过

7.app.gradle中增加如下代码(标记edit this的是可以需要更改的)

defaultConfig {
    ...
        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
        buildConfigField "String", "MESSAGE", "\"I am the base apk\""
        buildConfigField "String", "TINKER_ID", "\"${getTINKER_ID()}\""
        buildConfigField "String", "PLATFORM", "\"all\""
    }

    dexOptions {
        jumboMode = true
    }
def bakPath = file("${buildDir}/bakApk/")

def getTINKER_ID() {
    //todo 每次打包使用build->assembleAli命令,并保存release apk和release R.txt
    //todo 每次发布热更新先配置本文件,在使用tinker->tinkerPatchAliRelease命令,然后找到对应文件修改文件名为path.aaa
    return android.defaultConfig.versionName+"_1" //todo edit this,这里经我测试每次打基础包和增量包都要修改,所以我设置的是版本名称和追加热更版本
}

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //基准apk路径
    tinkerOldApkPath = "C:\\Users\\a\\Desktop\\oldApks\\ali-release_v0.8.6.apk"//todo edit this
    //未开启混淆,则不需要填写
    tinkerApplyMappingPath = "C:\\Users\\a\\Desktop\\oldApks"
    //基准apk中的R文件路径
    tinkerApplyResourcePath = "C:\\Users\\a\\Desktop\\oldApks\\app-ali-release-R.txt"//todo edit this
    //如果你修复了res文件,需要指定你bug版本的R.txt文件
    tinkerBuildFlavorDirectory = "C:\\Users\\a\\Desktop\\oldApks\\app-ali-release-R.txt"//todo edit this

}

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
}


if (buildWithTinker()) {
    apply plugin: 'com.tencent.tinker.patch'

    tinkerPatch {
        /**
         * 默认为null
         * 将旧的apk和新的apk建立关联
         * 从build / bakApk添加apk
         */
        oldApk = getOldApkPath()
        /**
         * 可选,默认'false'
         * 有些情况下我们可能会收到一些警告
         * 如果ignoreWarning为true,我们只是断言补丁过程
         * case 1:minSdkVersion低于14,但是你使用dexMode与raw。
         * case 2:在AndroidManifest.xml中新添加Android组件,
         * case 3:装载器类在dex.loader {}不保留在主要的dex,
         *          它必须让tinker不工作。
         * case 4:在dex.loader {}中的loader类改变,
         *          加载器类是加载补丁dex。改变它们是没有用的。
         *          它不会崩溃,但这些更改不会影响。你可以忽略它
         * case 5:resources.arsc已经改变,但是我们不使用applyResourceMapping来构建
         */
        ignoreWarning = true

        /**
         * 可选,默认为“true”
         * 是否签名补丁文件
         * 如果没有,你必须自己做。否则在补丁加载过程中无法检查成功
         * 我们将使用sign配置与您的构建类型
         */
        useSign = true

        /**
         * 可选,默认为“true”
         * 是否使用tinker构建
         */
        tinkerEnable = buildWithTinker()

        /**
         * 警告,applyMapping会影响正常的android build!
         */
        buildConfig {
            /**
             * 可选,默认为'null'
             * 如果我们使用tinkerPatch构建补丁apk,你最好应用旧的
             * apk映射文件如果minifyEnabled是启用!
             * 警告:你必须小心,它会影响正常的组装构建!
             */
            applyMapping = getApplyMappingPath()
            /**
             * 可选,默认为'null'
             * 最好保留R.txt文件中的资源id,以减少java更改
             */
            applyResourceMapping = getApplyResourceMappingPath()

            /**
             * 必需,默认'null'
             * 因为我们不想检查基地apk与md5在运行时(它是慢)
             * tinkerId用于在试图应用补丁时标识唯一的基本apk。
             * 我们可以使用git rev,svn rev或者简单的versionCode。
             * 我们将在您的清单中自动生成tinkerId
             */
            tinkerId = getTINKER_ID()

            /**
             * 如果keepDexApply为true,则表示dex指向旧apk的类。
             * 打开这可以减少dex diff文件大小。
             */
            keepDexApply = false

            /**
             * optional, default 'false'
             * Whether tinker should treat the base apk as the one being protected by app
             * protection tools.
             * If this attribute is true, the generated patch package will contain a
             * dex including all changed classes instead of any dexdiff patch-info files.
             *
             * 是否使用加固模式,仅仅将变更的类合成补丁。注意,这种模式仅仅可以用于加固应用中。
             */
            isProtectedApp = true //todo 注意这里,如果是debug测试或者app不进行加固,请关闭

            /**
             * optional, default 'false'
             * Whether tinker should support component hotplug (add new component dynamically).
             * If this attribute is true, the component added in new apk will be available after
             * patch is successfully loaded. Otherwise an error would be announced when generating patch
             * on compile-time.
             *
             * <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b>
             */
            supportHotplugComponent = false
        }

        dex {
            /**
             * 可选,默认'jar'
             * 只能是'raw'或'jar'。对于原始,我们将保持其原始格式
             * 对于jar,我们将使用zip格式重新包装dexes。
             * 如果你想支持下面14,你必须使用jar
             * 或者你想保存rom或检查更快,你也可以使用原始模式
             */
            dexMode = "jar"

            /**
             * 必需,默认'[]'
             * apk中的dexes应该处理tinkerPatch
             * 它支持*或?模式。
             */
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            /**
             * 必需,默认'[]'
             * 警告,这是非常非常重要的,加载类不能随补丁改变。
             * 因此,它们将从补丁程序中删除。
             * 你必须把下面的类放到主要的dex。
             * 简单地说,你应该添加自己的应用程序{@code tinker.sample.android.SampleApplication}
             * 自己的tinkerLoader,和你使用的类
             */
            loader = [
                    //use sample, let BaseBuildInfo unchangeable with tinker
                    "tinker.sample.android.app.BaseBuildInfo",
                    "com.xx.xx.view.BaseApplication"//todo 这里加上你的Application
            ]
        }

        lib {
            /**
             * 可选,默认'[]'
             * apk中的图书馆应该处理tinkerPatch
             * 它支持*或?模式。
             * 对于资源库,我们只是在补丁目录中恢复它们
             * 你可以得到他们在TinkerLoadResult与Tinker
             */
            pattern = ["lib/*/*.so"]
        }

        res {
            /**
             * 可选,默认'[]'
             * apk中的什么资源应该处理tinkerPatch
             * 它支持*或?模式。
             * 你必须包括你在这里的所有资源,
             * 否则,他们不会重新包装在新的apk资源。
             */
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]

            /**
             *  可选,默认'[]'
             *  资源文件排除模式,忽略添加,删除或修改资源更改
             * *它支持*或?模式。
             * *警告,我们只能使用文件没有relative与resources.arsc
             */
            ignoreChange = ["assets/sample_meta.txt"]

            /**
             *  默认100kb
             * *对于修改资源,如果它大于'largeModSize'
             * *我们想使用bsdiff算法来减少补丁文件的大小
             */
            largeModSize = 100
        }

        packageConfig {
            /**
             * 可选,默认'TINKER_ID,TINKER_ID_VALUE','NEW_TINKER_ID,NEW_TINKER_ID_VALUE'
             * 包元文件gen。路径是修补程序文件中的assets / package_meta.txt
             * 你可以在您自己的PackageCheck方法中使用securityCheck.getPackageProperties()
             * 或TinkerLoadResult.getPackageConfigByName
             * 我们将从旧的apk清单为您自动获取TINKER_ID,
             * 其他配置文件(如下面的patchMessage)不是必需的
             */
            configField("patchMessage", "tinker is sample to use")
            /**
             * 只是一个例子,你可以使用如sdkVersion,品牌,渠道...
             * 你可以在SamplePatchListener中解析它。
             * 然后你可以使用补丁条件!
             */
            configField("platform", "all")
            /**
             * 补丁版本通过packageConfig
             */
            configField("patchVersion", "1.0.2")
        }
        //或者您可以添加外部的配置文件,或从旧apk获取元值
        //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test"))
        //project.tinkerPatch.packageConfig.configField("test2", "sample")

        /**
         * 如果你不使用zipArtifact或者path,我们只是使用7za来试试
         */
        sevenZip {
            /**
             * 可选,默认'7za'
             * 7zip工件路径,它将使用正确的7za与您的平台
             */
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
            /**
             * 可选,默认'7za'
             * 你可以自己指定7za路径,它将覆盖zipArtifact值
             */
//        path = "/usr/local/bin/7za"
        }
    }

    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

                        if (variant.metaClass.hasProperty(variant, 'packageApplicationProvider')) {
                            def packageAndroidArtifact = variant.packageApplicationProvider.get()
                            if (packageAndroidArtifact != null) {
                                try {
                                    from new File(packageAndroidArtifact.outputDirectory.getAsFile().get(), variant.outputs.first().apkData.outputFileName)
                                } catch (Exception e) {
                                    from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().apkData.outputFileName)
                                }
                            } else {
                                from variant.outputs.first().mainOutputFile.outputFile
                            }
                        } else {
                            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"
                        from "${buildDir}/intermediates/symbol_list/${variant.dirName}/R.txt"
                        from "${buildDir}/intermediates/runtime_symbol_list/${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"
                        project.tinkerPatch.oldApk = getOldApkPath()
                        System.out.println("lllttt oldApk=" + project.tinkerPatch.oldApk)
                        project.tinkerPatch.buildConfig.applyMapping = ""
                        project.tinkerPatch.buildConfig.applyResourceMapping = originOldPath
                        System.out.println("lllttt old R=" + project.tinkerPatch.buildConfig.applyResourceMapping)
                    }

                }
            }

            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"
                        project.tinkerPatch.oldApk = getOldApkPath()
                        System.out.println("lllttt oldApk=" + project.tinkerPatch.oldApk)
                        project.tinkerPatch.buildConfig.applyMapping = ""
                        project.tinkerPatch.buildConfig.applyResourceMapping = originOldPath
                        System.out.println("lllttt old R=" + project.tinkerPatch.buildConfig.applyResourceMapping)
                    }

                }
            }
        }
    }
}

8.经过上面的配置后就可以用了,这一节我们说说怎么使用

双击该位置生成基准包,如果没有多渠道就点击assemble,这时会生成debug和release包和对应的R.txt文件

具体生成位置是:build-bakApk

然后修改app.gradle中的tinkerOldApkPath,tinkerApplyResourcePath,tinkerBuildFlavorDirectory三项配置和getTINKER_ID()方法中的后缀自增,并修改bug或者修改ui或逻辑

然后按照下面生成debug或者release包

生成的位置是:build-outputs-apk-tinkerPatch-patch_signed_7zip.apk(这个就是生成的差量包)

我们可以放到存储目录,也可以放到cache目录

然后调用下面的api来安装增量包,并且会在上面配置的SampleResultService中有回调,成功后重启app或者锁屏后就可以了

9.各种api

//安装增量包
//TinkerInstaller.onReceiveUpgradePatch(getContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk")
//安装增量so(未测试)
//TinkerLoadLibrary.installNavitveLibraryABI(getApplicationContext(), "armeabi");
//                System.loadLibrary("stlport_shared");
//清除增量包
//Tinker.with(getApplicationContext()).cleanPatch();
//是否安装了增量包
//Tinker.with(getApplicationContext()).isTinkerLoaded();
//兼容google play
//https://github.com/Tencent/tinker/issues/1314

然后就可以难过的玩耍了(其实听说bugly接入方式更简单,问题更少,不过这是后话了)

结语

第一次写文章不想写结语

完成这次接入并测试完成,我大概用了三个这样的浏览器tab(同时打开,因为都有用,没用的都关了)

如果有问题或者有问题(没有语句bug)请提交评论

end

对Kotlin或KMP感兴趣的同学可以进Q群 101786950

如果这篇文章对您有帮助的话

可以扫码请我喝瓶饮料或咖啡(如果对什么比较感兴趣可以在备注里写出来)

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值