主頁 > 移動端開發 > Android 熱更新集成

Android 熱更新集成

2021-04-27 18:14:59 移動端開發

安卓熱更新技術方案

方案集成之前的預設問題:
??如果考慮付費,推薦選擇阿里的Sophix,Sophix是綜合優化的產物,功能完善、開發簡單透明、提供分發及監控管理,
??如果不考慮付費,只需支持方法級別的Bug修復,不支持資源及so,推薦使用Robust,
??如果不考慮付費,但要考慮需要同時支持資源及so,推薦使用Tinker,

正常開發流程:
新版本上線,發現問題或用戶反饋bug,緊急修復,上線版本,用戶重新安裝,如下圖:
圖一
熱修復流程:
新版本上線,發現問題或用戶反饋,緊急修復,上線補丁,自動修復,如下圖:
圖二

方案對比分析:

方案TinkerRobustSophix
類替換yesnoyes
so替換yesnoyes
資源替換yesnoyes
全平臺支持yesyesyes
即時生效noyesyes
補丁包大小較小一般較小
復雜度一般復雜傻瓜式接入
成功率較高非常高非常高
收費nonoyes

Sophix集成實作:

流程

1.添加工程依賴

gradle遠程倉庫依賴, 打開專案找到app的build.gradle檔案,添加如下配置:添加maven倉庫地址:

repositories {
   maven {
       url "http://maven.aliyun.com/nexus/content/repositories/releases"
   }
}

添加gradle坐標版本依賴:

android {
    ......
    defaultConfig {
        applicationId "com.xxx.xxx" //包名
        ......
        ndk {
            //選擇要添加的對應cpu型別的.so庫,
            //熱修復支持五種
            abiFilters 'arm64-v8a', 'armeabi', 'armeabi-v7a', 'x86', 'x86_64'
        }
        ......
    }
    ......
}
dependencies {
    ......
        compile 'com.aliyun.ams:alicloud-android-hotfix:3.2.18'
    ......
}

2.添加應用權限:

Sophix SDK使用到以下權限,使用maven依賴或者aar依賴可以不用配置,具體配置在AndroidManifest.xml中,

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

3.配置AndroidManifest檔案

在AndroidManifest.xml中間的application節點下添加如下配置:

<meta-data
android:name="com.taobao.android.hotfix.IDSECRET"
android:value="App ID" />
<meta-data
android:name="com.taobao.android.hotfix.APPSECRET"
android:value="App Secret" />
<meta-data
android:name="com.taobao.android.hotfix.RSASECRET"
android:value="RSA密鑰" />

4. 混淆配置

#基線包使用,生成mapping.txt
-printmapping mapping.txt
#生成的mapping.txt在app/build/outputs/mapping/release路徑下,移動到/app路徑下
#修復后的專案使用,保證混淆結果一致
#-applymapping mapping.txt
#hotfix
-keep class com.taobao.sophix.**{*;}
-keep class com.ta.utdid2.device.**{*;}
#防止inline
-dontoptimize

5.初始化

初始化的呼叫應該盡可能的早,必須在Application.attachBaseContext()的最開始(在super.attachBaseContext之后,如果有Multidex,也需要在Multidex.install之后)進行SDK初始化操作,初始化之前不能用到其他自定義類,否則極有可能導致崩潰,而查詢服務器是否有可用補丁的操作可以在后面的任意地方,不建議在Application.onCreate()中初始化,因為如果帶有ContentProvider,就會使得Sophix初始化時機太遲從而引發問題,
Sophix最新版本引入了新的初始化方式,
原來的初始化方式仍然可以使用,只是新方式可以提供更全面的功能修復支持,將會帶來以下優點:
初始化與應用原先業務代碼完全隔離,使得原先真正的Application可以修復,并且減少了補丁預加載時間等等,
新方式能夠更完美地兼容Android 8.0以后版本,

具體而言,是需要用戶自行加入以下這個類:

package com.my.pkg;
import android.app.Application;
import android.content.Context;
import android.support.annotation.Keep;
import android.util.Log;
import com.taobao.sophix.PatchStatus;
import com.taobao.sophix.SophixApplication;
import com.taobao.sophix.SophixEntry;
import com.taobao.sophix.SophixManager;
import com.taobao.sophix.listener.PatchLoadStatusListener;
import com.my.pkg.MyRealApplication;
/**
 * Sophix入口類,專門用于初始化Sophix,不應包含任何業務邏輯,
 * 此類必須繼承自SophixApplication,onCreate方法不需要實作,
 * 此類不應與專案中的其他類有任何互相呼叫的邏輯,必須完全做到隔離,
 * AndroidManifest中設定application為此類,而SophixEntry中設為原先Application類,
 * 注意原先Application里不需要再重復初始化Sophix,并且需要避免混淆原先Application類,
 * 如有其它自定義改造,請咨詢官方后妥善處理,
 */
public class SophixStubApplication extends SophixApplication {
    private final String TAG = "SophixStubApplication";
    // 此處SophixEntry應指定真正的Application,并且保證RealApplicationStub類名不被混淆,
    @Keep
    @SophixEntry(MyRealApplication.class)
    static class RealApplicationStub {}
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
//         如果需要使用MultiDex,需要在此處呼叫,
//         MultiDex.install(this);
        initSophix();
    }
    private void initSophix() {
        String appVersion = "0.0.0";
        try {
            appVersion = this.getPackageManager()
                             .getPackageInfo(this.getPackageName(), 0)
                             .versionName;
        } catch (Exception e) {
        }
        final SophixManager instance = SophixManager.getInstance();
        instance.setContext(this)
                .setAppVersion(appVersion)
                .setSecretMetaData(null, null, null)
                .setEnableDebug(true)
                .setEnableFullLog()
                .setPatchLoadStatusStub(new PatchLoadStatusListener() {
                    @Override
                    public void onLoad(final int mode, final int code, final String info, final int handlePatchVersion) {
                        if (code == PatchStatus.CODE_LOAD_SUCCESS) {
                            Log.i(TAG, "sophix load patch success!");
                        } else if (code == PatchStatus.CODE_LOAD_RELAUNCH) {
                            // 如果需要在后臺重啟,建議此處用SharePreference保存狀態,
                            Log.i(TAG, "sophix preload patch success. restart app to make effect.");
                        }
                    }
                }).initialize();
    }
}
// queryAndLoadNewPatch不可放在attachBaseContext 中,否則無網路權限,建議放在后面任意時刻,如onCreate中
SophixManager.getInstance().queryAndLoadNewPatch();

這其中,關鍵一點是:

    @Keep
    @SophixEntry(MyRealApplication.class)
    static class RealApplicationStub {}

SophixEntry應指定專案中原先真正的Application(原專案里application的android::name指定的),這里用MyRealApplication指代,并且保證RealApplicationStub類名不被混淆,而SophixStubApplication的類名和包名可以自行取名,
這里的Keep是android.support包中的類,目的是為了防止這個內部靜態類的類名被混淆,因為sophix內部會反射獲取這個類的SophixEntry,如果專案中沒有依賴android.support的話,就需要在progurad里面手動指定RealApplicationStub不被混淆,

在proguard檔案里面需要加上下面內容:

-keepclassmembers class com.my.pkg.MyRealApplication {
    public <init>();
}
-keep class com.my.pkg.SophixStubApplication$RealApplicationStub

目的是防止真正Application的構造方法被proguard混淆,
最后,需要把AndroidManifest里面的application改為這個新增的SophixStubApplication類:

    <application
        android:name="com.my.pkg.SophixStubApplication"
        ... .../>
        ... ...
        ... ...
        ... ...

Bugly熱更新集成實作

1.添加插件依賴

工程根目錄下“build.gradle”檔案中添加:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // tinkersupport插件, 其中lastest.release指拉取最新版本,也可以指定明確版本號,例如1.0.4
        classpath "com.tencent.bugly:tinker-support:1.1.5"
    }
}

注意:自tinkersupport 1.0.3版本起無需再配tinker插件的classpath,

版本對應關系:
tinker-support 1.1.3 對應 tinker 1.9.8
tinker-support 1.1.2 對應 tinker 1.9.6
tinker-support 1.1.1 對應 tinker 1.9.1
tinker-support 1.0.9 對應 tinker 1.9.0
tinker-support 1.0.8 對應 tinker 1.7.11
tinker-support 1.0.7 對應 tinker 1.7.9
tinker-support 1.0.4 對應 tinker 1.7.7
tinker-support 1.0.3 對應 tinker 1.7.6
tinker-support 1.0.2 對應 tinker 1.7.5(需配置tinker插件的classpath)

2.集成SDK

gradle配置
在app module的“build.gradle”檔案中添加(示例配置):

android {
        defaultConfig {
          ndk {
            //設定支持的SO庫架構
            abiFilters 'armeabi' //, 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
          }
        }
      }
      dependencies {
          compile "com.android.support:multidex:1.0.1" // 多dex配置
          //注釋掉原有bugly的倉庫
          //compile 'com.tencent.bugly:crashreport:latest.release'//其中latest.release指代最新版本號,也可以指定明確的版本號,例如1.3.4
          compile 'com.tencent.bugly:crashreport_upgrade:1.3.6'
          // 指定tinker依賴版本(注:應用升級1.3.5版本起,不再內置tinker)
          compile 'com.tencent.tinker:tinker-android-lib:1.9.9'
          compile 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新版本號,也可以指定明確的版本號,例如2.2.0
      }

后續更新升級SDK時,只需變更配置腳本中的版本號即可,

注意: 升級SDK已經集成crash上報功能,已經集成Bugly的用戶需要注釋掉原來Bugly的jcenter庫; 已經配置過符號表的Bugly用戶保留原有符號表配置; Bugly SDK(2.1.5及以上版本)已經將Java Crash和Native Crash捕獲功能分開,如果想使用NDK庫,需要配置: compile ‘com.tencent.bugly:nativecrashreport:latest.release’

在app module的“build.gradle”檔案中添加:

// 依賴插件腳本
apply from: 'tinker-support.gradle'

tinker-support.gradle內容如下所示(示例配置):

注:您需要在同級目錄下創建tinker-support.gradle這個檔案哦,

apply plugin: 'com.tencent.bugly.tinker-support'

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

/**
 * 此處填寫每次構建生成的基準包目錄
 */
def baseApkDir = "app-0208-15-10-00"

/**
 * 對于插件各引數的詳細決議請參考
 */
tinkerSupport {

    // 開啟tinker-support插件,默認值true
    enable = true

    // 指定歸檔目錄,默認值當前module的子目錄tinker
    autoBackupApkDir = "${bakPath}"

    // 是否啟用覆寫tinkerPatch配置功能,默認值false
    // 開啟后tinkerPatch配置不生效,即無需添加tinkerPatch
    overrideTinkerPatchConfiguration = true

    // 編譯補丁包時,必需指定基線版本的apk,默認值為空
    // 如果為空,則表示不是進行補丁包的編譯
    // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/app-release.apk"

    // 對應tinker插件applyMapping
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"

    // 對應tinker插件applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"

    // 構建基準包和補丁包都要指定不同的tinkerId,并且必須保證唯一性
    tinkerId = "base-1.0.1"

    // 構建多渠道補丁時使用
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

    // 是否啟用加固模式,默認為false.(tinker-spport 1.0.7起支持)
    // isProtectedApp = true

    // 是否開啟反射Application模式
    enableProxyApplication = false

    // 是否支持新增非export的Activity(注意:設定為true才能修改AndroidManifest檔案)
    supportHotplugComponent = true

}

/**
 * 一般來說,我們無需對下面的引數做任何的修改
 * 對于各引數的詳細介紹請參考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    //oldApk ="${bakPath}/${appName}/app-release.apk"
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }

    res {
        pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }

    packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
//        path = "/usr/local/bin/7za"
    }
    buildConfig {
        keepDexApply = false
        //tinkerId = "1.0.1-base"
        //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" //  可選,設定mapping檔案,建議保持舊apk的proguard混淆方式
        //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可選,設定R.txt檔案,通過舊apk檔案保持ResId的分配
    }
}


3.初始化SDK

enableProxyApplication = false 的情況

這是Tinker推薦的接入方式,一定程度上會增加接入成本,但具有更好的兼容性,

集成Bugly升級SDK之后,我們需要按照以下方式自定義ApplicationLike來實作Application的代碼(以下是示例):

自定義Application
public class SampleApplication extends TinkerApplication {
    public SampleApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "xxx.xxx.SampleApplicationLike",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }
}

注意:這個類集成TinkerApplication類,這里面不做任何操作,所有Application的代碼都會放到ApplicationLike繼承類當中
引數決議
引數1:tinkerFlags 表示Tinker支持的型別 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
引數2:delegateClassName Application代理類 這里填寫你自定義的ApplicationLike
引數3:loaderClassName Tinker的加載器,使用默認即可
引數4:tinkerLoadVerifyFlag 加載dex或者lib是否驗證md5,默認為false

將以前的Applicaton配置為繼承TinkerApplication的類:
在這里插入圖片描述

自定義ApplicationLike
public class SampleApplicationLike extends DefaultApplicationLike {

    public static final String TAG = "Tinker.SampleApplicationLike";

    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();
        // 這里實作SDK初始化,appId替換成你的在Bugly平臺申請的appId
        // 除錯時,將第三個引數改為true
        Bugly.init(getApplication(), "900029763", false);
    }


    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base);

        // 安裝tinker
        // TinkerManager.installTinker(this); 替換成下面Bugly提供的方法
        Beta.installTinker(this);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
        getApplication().registerActivityLifecycleCallbacks(callbacks);
    }

}

注意:tinker需要你開啟MultiDex,你需要在dependencies中進行配置compile "com.android.support:multidex:1.0.1"才可以使用MultiDex.install方法; SampleApplicationLike這個類是Application的代理類,以前所有在Application的實作必須要全部拷貝到這里,在onCreate方法呼叫SDK的初始化方法,在onBaseContextAttached中呼叫Beta.installTinker(this);,

enableProxyApplication = true 的情況

注:無須你改造Application,主要是為了降低接入成本,我們插件會動態替換AndroidMinifest檔案中的Application為我們定義好用于反射真實Application的類(需要您接入SDK 1.2.2版本 和 插件版本 1.0.3以上),

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 這里實作SDK初始化,appId替換成你的在Bugly平臺申請的appId
        // 除錯時,將第三個引數改為true
        Bugly.init(this, "900029763", false);
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base);


        // 安裝tinker
        Beta.installTinker();
    }

}

4. AndroidManifest.xml配置

在AndroidMainfest.xml中進行以下配置:

1. 權限配置
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2. Activity配置
<activity
    android:name="com.tencent.bugly.beta.ui.BetaActivity"
    android:configChanges="keyboardHidden|orientation|screenSize|locale"
    android:theme="@android:style/Theme.Translucent" />
3. 配置FileProvider

注意:如果您想兼容Android N或者以上的設備,必須要在AndroidManifest.xml檔案中配置FileProvider來訪問共享路徑的檔案,

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

如果使用的第三方庫也配置了同樣的FileProvider, 可以通過繼承FileProvider類來解決合并沖突的問題,示例如下:

<provider
    android:name=".utils.BuglyFileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true"
    tools:replace="name,authorities,exported,grantUriPermissions">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"
        tools:replace="name,resource"/>
</provider>

FileProvider類是在support-v4包中的,檢查你的工程是否引入該類別庫,

在res目錄新建xml檔案夾,創建provider_paths.xml檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>
</paths>

注意:這里配置的兩個外部存盤路徑是升級SDK下載的檔案可能存在的路徑,一定要按照上面格式配置,不然可能會出現錯誤,
1.3.1及以上版本,可以不用進行以上配置,aar已經在AndroidManifest配置了,并且包含了對應的資源檔案,

5.混淆配置

為了避免混淆SDK,在Proguard混淆檔案中增加以下配置:

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}
# tinker混淆規則
-dontwarn com.tencent.tinker.**
-keep class com.tencent.tinker.** { *; }

如果使用了support-v4包,還需要配置以下混淆規則:


-keep class android.support.**{*;}

robust集成

1.添加插件依賴

1.在App的build.gradle,加入如下依賴:

apply plugin: 'com.android.application'
//制作補丁時將這個打開,auto-patch-plugin緊跟著com.android.application
//apply plugin: 'auto-patch-plugin'
apply plugin: 'robust'

compile 'com.meituan.robust:robust:0.4.99'
	

2.在整個專案的build.gradle加入classpath:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
         classpath 'com.meituan.robust:gradle-plugin:0.4.99'
         classpath 'com.meituan.robust:auto-patch-plugin:0.4.99'
   }
}

3.在工程專案的src同級目錄下配置robust.xml檔案

注意 gradle 3.6及以上版本默認啟用R8,會將插入的ChangeQuickRedirect變數優化掉,需要在混淆檔案proguard-rules.pro中加入以下代碼:
-keepclassmembers class **{ public static com.meituan.robust.ChangeQuickRedirect *; }

Robust補丁自動化,為Robust自動生成補丁

使用者只需要提交修改完bug后的代碼,運行和線上apk打包同樣的gradle命令即可,會在專案的app/build/outputs/robust目錄下生成補丁,

自動化補丁工具
gradle命令:./gradlew clean assembleRelease --stacktrace --no-daemon

2.修復使用

1.使用插件時,需要把auto-patch-plugin放置在com.android.application插件之后,其余插件之前,

apply plugin: 'com.android.application'
apply plugin: 'auto-patch-plugin'

2.將保存下來的mapping檔案和methodsMap.robust檔案放在app/robust/檔案夾下,
3.修改代碼,在改動的方法上面添加@Modify注解,對于Lambda運算式請在修改的方法里面呼叫RobustModify.modify()方法

@Modify
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
     }
     //或者是被修改的方法里面呼叫RobustModify.modify()方法
     protected void onCreate(Bundle savedInstanceState) {
        RobustModify.modify()
        super.onCreate(savedInstanceState);
     }
     

新增的方法和欄位使用@Add注解

   //增加方法
    @Add
    public String getString() {
        return "Robust";
    }
    //增加類
    @Add
    public class NewAddCLass {
        public static String get() {
           return "robust";
         }
    }

4.運行和生成線上apk同樣的命令,即可生成補丁,補丁目錄app/build/outputs/robust/patch.jar
5.補丁制作成功后會停止構建apk,出現類似于如下的提示,表示補丁生成成功
在這里插入圖片描述

3.樣例使用

  1. 生成樣例apk,執行gradle命令:

    ./gradlew clean assembleRelease --stacktrace --no-daemon

  2. 安裝樣例apk,保存mapping.txt檔案以及app/build/outputs/robust/methodsMap.robust檔案

  3. 修改代碼之后,加上**@Modify**注解或者呼叫RobustModify.modify()方法

  4. 把保存的mapping.txt和methodsMap.robust放到app/robust目錄下

  5. 執行與生成樣式apk相同的gradle命令:

    ./gradlew clean assembleRelease --stacktrace --no-daemon

  6. 補丁制作成功后會停止構建apk,出現類似于如下的提示,表示補丁生成成功
    在這里插入圖片描述

  7. 將補丁檔案copy到手機目錄/sdcard/robust下

    adb push ~/Desktop/code/robust/app/build/outputs/robust/patch.jar /sdcard/robust/patch.jar

    補丁的路徑/sdcard/robust是PatchManipulateImp中指定的

4. 注意事項:

1.內部類的構造方法是private(private會生成一個匿名的建構式)時,需要在制作補丁程序中手動修改構造方法的訪問域為public
2.對于方法的回傳值是this的情況現在支持不好,比如builder模式,但在制作補丁代碼時,可以通過如下方式來解決,增加一個類來包裝一下(如下面的B類),
method a(){ return this; }
改為
method a(){ return new B().setThis(this).getThis(); }
3. 欄位增加能力內測中,不過暫時可以通過增加新類,把欄位放到新類中的方式來實作欄位增加能力
4.新增的類支持包括靜態內部類和非內部類
5.對于只有欄位訪問的函式無法直接修復,可通過呼叫處間接修復

總結:

應用層面來說,阿里系的Sophix方案最優,但是該方案收費,美團的Robust不支持資源修改,所以有了很大限制,而且該技術官方已經很久不維護,并且集成對公司專案改動比較大,所以綜合來說性價比最高的方案是騰訊系的Tinker,針對Tinker熱更新,騰訊有Bugly針對tinker的集成,補丁檔案有一站式的管理,而且還可以獨立出Bugly,由公司自己的服務器去維護補丁檔案的管理,更加靈活,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/280728.html

標籤:其他

上一篇:android最全面的冷啟動優化方案

下一篇:關于Intent.ACTION_VIEW呼叫圖庫打開圖片放大問題

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more