android集成热更新就是这么简单!

项目1.0开始发布,到项目稳定。这期间会有多次版本的迭代。如果每次都是发布版本,会耗费大量的时间成本,因为在版本迭代的过程中,旧的bug解决了,新的bug也可能会出现。即使是最牛逼的程序猿也不敢说自己的项目没有bug。还有就是有的时候项目中虽然存在bug,但是bug级别很低,也没有必要去发布版本。此时,为了满足这种需求。android的热更新就起了大作用了。

热更新原理:
android程序在打包之后,会将class文件打包成dex字节码文件,每个类都对应有自己的字节码文件,手机按照程序流程来读取字节码,这样程序响应的界面就展示在用户眼前了。微信的tinker实际上就是基于这个原理,将修改后的类生成对应的字节码文件,把修改的字节码文件放到服务器,app在运行中请求到服务器的数据(其实就是个文件下载)之后,替换掉原有的字节码文件,这样,程序就实现了bug的修改,也就是热更新。阿里的andfix核心技术是用c++实现的,它的本质上是方法的替换,这个更偏向底层一些。这里简单的介绍一下,java中方法的运行涉及到方法区、java堆、java栈这几个内存区域。堆用来存放对象实例,栈存放的是引用,方法区里面存放了类的字节码,每个类的字节码文件都对应一个方法表,这个方法表中对应的就是类中的方法,类中的方法压栈之后就是栈中的一个栈帧,这样一帧一帧的执行完毕之后,方法也就执行完了。andfix就是基于这个原理,替换方法表中的方法,从而完成更新。而java无法做到方法的替换,所以andfix是用c++来实现的。

热更新使用:

第一步:compile ‘com.alipay.euler:andfix:0.5.0@aar’ 添加aar依赖之后,不用在添加so等文件,因为本省aar中包含资源文件。

第二步:初始化PatchManager

public class App extends Application {

    public static PatchManager mPatchManager;

    @Override
    public void onCreate() {
        super.onCreate();
        mPatchManager = new PatchManager(this);
        mPatchManager.init(AppUtils.getVersionName(this));
        mPatchManager.loadPatch();
    }
}

第三步:生成apatch文件,使用阿里提供的工具
解压后在当前目录下执行以下命令apkpatch.bat -f 新apk -t 旧apk -o output -k 签名文件 -p 密码 -a 别名 -e 别名密码

第四部步:将apatch部署到服务器,app下载,这里我就直接放到sd卡下模拟一下,

private void update() {
        String patchFileStr = Environment.getExternalStorageDirectory().getAbsolutePath() +"/fix.apatch" ;
        try {
            mPatchManager.addPatch(patchFileStr);
            T.showShort(this,"修复成功!!!");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

调用addpatch方法就可以替换我们想要改变的方法了。这样bug就解决了,是不是很easy,确实很easy。andfix还没有很好的支持7.0的手机,亲测华为荣耀8运行失败。解决办法:可以把targetSdk版本改到22,不去兼容6.0以上的特性,这样热更新使用是没问题的。andfix也有一些不足,就是无法增加类和变量,部分方法无法替换(自己试试)。但是即使这样对与一般app的迭代作用也很大了。

Tinker的集成与常见问题的解决

第一步:

  //可选,生成application
 provided 'com.tencent.tinker:tinker-android-anno:1.7.11'
 //核心jar包
 compile 'com.tencent.tinker:tinker-android-lib:1.7.11'

第二步:


@DefaultLifeCycle(application = ".SimpleTinkerInApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)

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);

    }

    /**
     * 绑定了application的生命周期,
     * 在application中的操作移植到此方法
     */
    @Override
    public void onCreate() {
        super.onCreate();
    }

}

添加ApplicationLike的子类,这个类在作用就是绑定application的生命周期,看名字就能知道,是像application的一个类,但是真正的application在哪里呢?看@DefaultLifeCycle(…),通过注解生成了我们的application,这个application的名称叫SimpleTinkerInApplication,所以在xml文件就要相应的改变。

<application
        android:name=".SimpleTinkerInApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
          <!-- 别忘了配置一下tinkerId-->
        <meta-data
            android:name="TINKER_ID"
            android:value="100" />
</application>

第三步:

生成patch文件,patch.apk是新的apk和旧的apk做diff运算生成的,官方提供了命令行工具
java -jar tinker-patch-cli-1.7.11.jar -old 1.0.apk -new 1.0(新).apk -config tinker_config.xml -out output

tinker-patch-cli-1.7.11.jar的生成也很简单,在官方的demo里面找到一下gradle,然后运行即可。

运行命令行生成如下:

这里写图片描述

第四步:加载patch文件,和andfix文件是一样的,这个应该在服务器端下载到手机本地,然后在手机本地读取。

 TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
                Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed.apk");

这样我们就可以很愉快玩耍了。但是运行之后会发现app加载完patch文件后会重启,这个可定时不爽的吗,总不能用户在使用着app的时候突然就重启了吧,鬼知道是那里出了问题。好在官方也给出了解决方法,就是自定义SampleResultService。然后在SimpleTinkerInApplicationLike的onBaseContextAttached()方法中添加如下代码:

  @Override
    public void onBaseContextAttached(Context base) {

        super.onBaseContextAttached(base);
        TinkerManager.setTinkerApplicationLike(this);
        //should set before tinker is installed
        TinkerManager.setUpgradeRetryEnable(true);
        TinkerManager.installTinker(this);

    }

TinkerManager是官方demo中提供的一个tinker管理类,可以直接在拿出来用主要看

 /**
     * 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;
        }
        //you can set your own upgrade patch if you need
        AbstractPatch upgradePatchProcessor = new UpgradePatch();
        TinkerInstaller.install(appLike,new DefaultLoadReporter(appLike.getApplication()),new DefaultPatchReporter(appLike.getApplication())
        ,new DefaultPatchListener(appLike.getApplication()),SampleResultService.class,upgradePatchProcessor);

        isInstalled = true;
    }

实际上我们使用了TinkerInstaller中install的多个参数的方法,这个方法就是微信提供给我们自定义的。这个方法传入了ApplicationLike,那么ApplicationLike 是如何起作用的呢? 我们简单看一下源码。
通过注解会生成

/**
 *
 * Generated application for tinker life cycle
 *
 */
public class SimpleTinkerInApplication extends TinkerApplication {

    public SimpleTinkerInApplication() {
        super(7, "com.iwintrue.todoapplication.SimpleTinkerInApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
    }

}

进入TinkerApplication,其继承了Application。

/**
 * Created by zhangshaowen on 16/3/8.
 * <p/>
 * A base class for implementing an Application that delegates to an {@link ApplicationLifeCycle}
 * instance. This is used in conjunction with secondary dex files so that the logic that would
 * normally live in the Application class is loaded after the secondary dexes are loaded.
 */
public abstract class TinkerApplication extends Application {

    //oh, we can use ShareConstants, because they are loader class and static final!
    private static final int    TINKER_DISABLE         = ShareConstants.TINKER_DISABLE;
    private static final String INTENT_PATCH_EXCEPTION = ShareIntentUtil.INTENT_PATCH_EXCEPTION;
    private static final String TINKER_LOADER_METHOD   = "tryLoad";
    /**
     * tinkerFlags, which types is supported
     * dex only, library only, all support
     * default: TINKER_ENABLE_ALL
     */
    private final int     tinkerFlags;
    /**
     * whether verify md5 when we load dex or lib
     * they store at data/data/package, and we had verity them at the :patch process.
     * so we don't have to verity them every time for quicker!
     * default:false
     */
    private final boolean tinkerLoadVerifyFlag;
    private final String  delegateClassName;
    private final String  loaderClassName;

    /**
     * if we have load patch, we should use safe mode
     */
    private       boolean useSafeMode;
    private       Intent  tinkerResultIntent;

    private ApplicationLike applicationLike = null;

    private long applicationStartElapsedTime;
    private long applicationStartMillisTime;

    /**
     * current build.
     */
    protected TinkerApplication(int tinkerFlags) {
        this(tinkerFlags, "com.tencent.tinker.loader.app.DefaultApplicationLike", TinkerLoader.class.getName(), false);
    }

    /**
     * @param delegateClassName The fully-qualified name of the {@link ApplicationLifeCycle} class
     *                          that will act as the delegate for application lifecycle callbacks.
     */
    protected TinkerApplication(int tinkerFlags, String delegateClassName,
                                String loaderClassName, boolean tinkerLoadVerifyFlag) {
        this.tinkerFlags = tinkerFlags;
        this.delegateClassName = delegateClassName;
        this.loaderClassName = loaderClassName;
        this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag;
    }

    protected TinkerApplication(int tinkerFlags, String delegateClassName) {
        this(tinkerFlags, delegateClassName, TinkerLoader.class.getName(), false);
    }

    private ApplicationLike createDelegate() {
        try {
            // Use reflection to create the delegate so it doesn't need to go into the primary dex.
            // And we can also patch it
            Class<?> delegateClass = Class.forName(delegateClassName, false, getClassLoader());
            Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class,
                long.class, long.class, Intent.class);
            return (ApplicationLike) constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
                applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
        } catch (Throwable e) {
            throw new TinkerRuntimeException("createDelegate failed", e);
        }
    }

    private synchronized void ensureDelegate() {
        if (applicationLike == null) {
            applicationLike = createDelegate();
        }
    }

    /**
     * Hook for sub-classes to run logic after the {@link Application#attachBaseContext} has been
     * called but before the delegate is created. Implementors should be very careful what they do
     * here since {@link android.app.Application#onCreate} will not have yet been called.
     */
    private void onBaseContextAttached(Context base) {
        applicationStartElapsedTime = SystemClock.elapsedRealtime();
        applicationStartMillisTime = System.currentTimeMillis();
        loadTinker();
        ensureDelegate();
        applicationLike.onBaseContextAttached(base);
        //reset save mode
        if (useSafeMode) {
            String processName = ShareTinkerInternals.getProcessName(this);
            String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName;
            SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE);
            sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit();
        }
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));
        onBaseContextAttached(base);
    }

    private void loadTinker() {
        //disable tinker, not need to install
        if (tinkerFlags == TINKER_DISABLE) {
            return;
        }
        tinkerResultIntent = new Intent();
        try {
            //reflect tinker loader, because loaderClass may be define by user!
            Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());

            Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
            Constructor<?> constructor = tinkerLoadClass.getConstructor();
            tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
        } catch (Throwable e) {
            //has exception, put exception error code
            ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
            tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        ensureDelegate();
        applicationLike.onCreate();
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        if (applicationLike != null) {
            applicationLike.onTerminate();
        }
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        if (applicationLike != null) {
            applicationLike.onLowMemory();
        }
    }

    @TargetApi(14)
    @Override
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        if (applicationLike != null) {
            applicationLike.onTrimMemory(level);
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (applicationLike != null) {
            applicationLike.onConfigurationChanged(newConfig);
        }
    }

    @Override
    public Resources getResources() {
        Resources resources = super.getResources();
        if (applicationLike != null) {
            return applicationLike.getResources(resources);
        }
        return resources;
    }

    @Override
    public ClassLoader getClassLoader() {
        ClassLoader classLoader = super.getClassLoader();
        if (applicationLike != null) {
            return applicationLike.getClassLoader(classLoader);
        }
        return classLoader;
    }

    @Override
    public AssetManager getAssets() {
        AssetManager assetManager = super.getAssets();
        if (applicationLike != null) {
            return applicationLike.getAssets(assetManager);
        }
        return assetManager;
    }

    @Override
    public Object getSystemService(String name) {
        Object service = super.getSystemService(name);
        if (applicationLike != null) {
            return applicationLike.getSystemService(name, service);
        }
        return service;
    }

    @Override
    public Context getBaseContext() {
        Context base = super.getBaseContext();
        if (applicationLike != null) {
            return applicationLike.getBaseContext(base);
        }
        return base;
    }

    public void setUseSafeMode(boolean useSafeMode) {
        this.useSafeMode = useSafeMode;
    }

    public boolean isTinkerLoadVerifyFlag() {
        return tinkerLoadVerifyFlag;
    }

    public int getTinkerFlags() {
        return tinkerFlags;
    }
}

最终通过createDelegate() ,反射出SimpleTinkerInApplicationLike这个类,这正是TinkerApplication构造函数的第二个参数。在TinkerApplication的oncreate方法中也调用了SimpleTinkerInApplicationLike的oncrate方法。明白其中的逻辑之后,我们就可以自定义自己的SampleResultService了,官方给出的是判断了只用手机在锁屏和后台运行的时候合并patch文件,重启app使增加的patch生效。这样还不错,代码可以自己去demo中抠出来。最后别忘了在xml文件中注册你的servise!!!

猜你喜欢

转载自blog.csdn.net/ZACH_ZHOU/article/details/73742552