Android增量自動(dòng)更新

抽取的Android自動(dòng)更新庫,目的是幾行代碼引入更新功能,含服務(wù)端代碼,歡迎Star羔杨,歡迎Fork,謝謝~

博客同步自:個(gè)人博客主頁
代碼github: https://github.com/itlwy/AppSmartUpdate

目錄

功能介紹

  • [x] 支持全量更新apk,直接升級(jí)到最新版本
  • [x] 支持增量更新,只下載補(bǔ)丁包升級(jí)
  • [x] 設(shè)置僅在wifi環(huán)境下更新
  • [x] 支持外部注入網(wǎng)絡(luò)框架(庫默認(rèn)使用okhttp)
  • [x] 支持前臺(tái)和后臺(tái)自動(dòng)更新
  • [x] 支持強(qiáng)制更新
  • [x] 支持對(duì)外定制更新提示和更新進(jìn)度界面
  • [ ] 記憶下載
  • [x] 含發(fā)布功能后臺(tái)服務(wù)端github (Node.js實(shí)現(xiàn))

流程圖

flowchart.jpg

效果圖與示例apk

示例1

示例2

點(diǎn)擊下載 smart-update.apk

如何引入

Gradle引入

step 1

Add the JitPack repository to your build file

    allprojects {
            repositories {
                ...
                maven { url 'https://jitpack.io' }
            }
        }

Step 2

Add the dependency

dependencies {
               implementation 'com.github.itlwy:AppSmartUpdate:v1.0.6'
    }

更新清單文件

該清單放置在靜態(tài)服務(wù)器以供App訪問豁鲤,主要用于判斷最新的版本,及要更新的版本資源信息等(示例見倉庫根目錄下的resources目錄或直接訪問后臺(tái)代碼 github)鲸沮,清單由服務(wù)端程序發(fā)布apk時(shí)生成琳骡,詳見后臺(tái)示例:github

{
  "minVersion": 100, // app最低支持的版本代碼(包含),低于此數(shù)值的app將強(qiáng)制更新
  "minAllowPatchVersion": 100, // 最低支持的差分版本(包含),低于此數(shù)值的app將采取全量更新,否則采用差量
  "newVersion": 101, // 當(dāng)前最新版本代碼
  "tip": "test update", // 更新提示
  "size": 1956631,  // 最新apk文件大小
  "apkURL": "https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/app/smart-update.apk", // 最新apk 絕對(duì)url地址灾挨,也可用相對(duì)地址您觉,如下方的"patchURL"字段
  "hash": "ea97c8efa490a2eaf7d10b37e63dab0e", // 最新apk文件的md5值
  "patchInfo": {  // 差分包信息
    "v100": { // v100表示-版本代碼100的apk需要下載的差分包
      "patchURL": "v100/100to101.patch", //差分包地址,相對(duì)此UpdateManifest.json文件的地址,也可用絕對(duì)地址
      "tip": "101 version", // 提示
      "hash": "ea97c8efa490a2eaf7d10b37e63dab0e", // 合成后apk(即版本代碼101)的文件md5值
      "size": 1114810 // 差分包大小
    }
  }
}

簡(jiǎn)單使用

1.初始化

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //推薦在Application中初始化
        Config config = new Config.Builder()
                .isDebug(true)
                .build(this);
        UpdateManager.getInstance().init(config);
    }
}

2.調(diào)用

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
 private Button mUpdateBtn;
    private String manifestJsonUrl = "https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/UpdateManifest.json";
    private IUpdateCallback mCallback;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mUpdateBtn = (Button) findViewById(R.id.update_btn);
        mUpdateBtn.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.update_btn:
                UpdateManager.getInstance().update(this, manifestJsonUrl, null);
                break;

        }
    }
}

詳細(xì)說明

注冊(cè)通知回調(diào)

  • 其他activity界面需要獲知后臺(tái)更新情況
public void register(IUpdateCallback callback) {...}

public void unRegister(IUpdateCallback callback) {...}

public interface IUpdateCallback {

    /**
     * 通知無新版本需要更新,運(yùn)行在主線程
     */
    void noNewApp();

    /**
     * 自動(dòng)更新準(zhǔn)備開始時(shí)回調(diào),運(yùn)行在主線程腕窥,可做一些提示等
     */
    void beforeUpdate();

    /**
     * 自動(dòng)更新的進(jìn)度回調(diào)(分增量和全量更新),運(yùn)行在主線程
     *
     * @param percent     當(dāng)前總進(jìn)度百分比
     * @param totalLength 更新總大小(全量為apk大小,增量為全部補(bǔ)丁大小和)
     * @param patchIndex  當(dāng)前更新的補(bǔ)丁索引(從1開始)
     * @param patchCount  需要更新的總補(bǔ)丁數(shù)(當(dāng)為0時(shí)表示是增量更新)
     */
    void onProgress(int percent, long totalLength, int patchIndex, int patchCount);

    /**
     * 下載完成怒坯,準(zhǔn)備更新,運(yùn)行在主線程
     */
    void onCompleted();

    /**
     * 異踌庞回調(diào),運(yùn)行在主線程
     *
     * @param error 異常信息
     */
    void onError(String error);

    /**
     * 用戶取消了詢問更新對(duì)話框
     */
    void onCancelUpdate();

    /**
     * 取消了更新進(jìn)度對(duì)話框,壓入后臺(tái)自動(dòng)更新,此時(shí)由通知欄通知進(jìn)度
     */
    void onBackgroundTrigger();
}

網(wǎng)絡(luò)框架注入

默認(rèn)使用okhttp,也可由外部注入,只需實(shí)現(xiàn)如下的IHttpManager接口,然后通過new Config.Builder().httpManager(new OkhttpManager())注入即可

public interface IHttpManager {


    IResponse syncGet(@NonNull String url, @NonNull Map<String, String> params) throws IOException;

    /**
     * 異步get
     *
     * @param url      get請(qǐng)求地址
     * @param params   get參數(shù)
     * @param callBack 回調(diào)
     */
    void asyncGet(@NonNull String url, @NonNull Map<String, String> params, @NonNull Callback callBack);


    /**
     * 異步post
     *
     * @param url      post請(qǐng)求地址
     * @param params   post請(qǐng)求參數(shù)
     * @param callBack 回調(diào)
     */
    void asyncPost(@NonNull String url, @NonNull Map<String, String> params, @NonNull Callback callBack);

    /**
     * 下載
     *
     * @param url      下載地址
     * @param path     文件保存路徑
     * @param fileName 文件名稱
     * @param callback 回調(diào)
     */
    void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull FileCallback callback);
}

定制更新交互界面

每個(gè)應(yīng)用的風(fēng)格都可能是不一樣的敬肚,因此這里也支持自定義彈出的提示框和進(jìn)度框毕荐,詳細(xì)見如下代碼示例:

  1. 初始化config時(shí)需要將內(nèi)部默認(rèn)的彈框屏蔽掉

     public class MyApplication extends Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
             Config config = new Config.Builder()
                    .isShowInternalDialog(false)
                    .build(this);
            UpdateManager.getInstance().init(config);
        }
    }
    
  2. 自定義對(duì)話框,如下(詳細(xì)代碼在MainActivity.java里):

 public void registerUpdateCallbak() {
        mCallback = new IUpdateCallback() {
            @Override
            public void noNewApp() {
                Toast.makeText(MainActivity.this, "當(dāng)前已是最新版本!", Toast.LENGTH_LONG).show();
            }

            @Override
            public void hasNewApp(AppUpdateModel appUpdateModel, UpdateManager updateManager, final int updateMethod) {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                mDialog = builder.setTitle("自動(dòng)更新提示")
                        .setMessage(appUpdateModel.getTip())
                        .setPositiveButton("更新", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                UpdateManager.getInstance().startUpdate(updateMethod);
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        }).create();
                mDialog.show();
            }

            @Override
            public void beforeUpdate() {
                // 更新開始
                mProgressDialog = new ProgressDialog(MainActivity.this);
                mProgressDialog.setTitle("更新中...");
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.setMessage("正在玩命更新中...");
                mProgressDialog.setMax(100);
                mProgressDialog.setProgress(0);
                mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        // 退到后臺(tái)自動(dòng)更新艳馒,進(jìn)度由通知欄顯示
                        if (UpdateManager.getInstance().isRunning()) {
                            UpdateManager.getInstance().onBackgroundTrigger();
                        }
                    }
                });
                mProgressDialog.show();
            }

            @Override
            public void onProgress(int percent, long totalLength, int patchIndex, int patchCount) {
                String tip;
                if (patchCount > 0) {
                    tip = String.format("正在下載補(bǔ)丁%d/%d", patchIndex, patchCount);
                } else {
                    tip = "正在下載更新中...";
                }
                mProgressDialog.setProgress(percent);
                mProgressDialog.setMessage(tip);
            }

            @Override
            public void onCompleted() {
                mProgressDialog.dismiss();
            }

            @Override
            public void onError(String error) {
                Toast.makeText(MainActivity.this, error, Toast.LENGTH_LONG).show();
                mProgressDialog.dismiss();
            }

            @Override
            public void onCancelUpdate() {

            }

            @Override
            public void onBackgroundTrigger() {
                Toast.makeText(MainActivity.this, "轉(zhuǎn)為后臺(tái)更新憎亚,進(jìn)度由通知欄提示!", Toast.LENGTH_LONG).show();
            }
        };
        UpdateManager.getInstance().register(mCallback);
    }

差分包合成(jni)

? 此部分采用的差分工具為開源bsdiff,用于生成.patch補(bǔ)丁文件弄慰,采用jni方式封裝一個(gè).so庫供java調(diào)用第美,詳見"smartupdate"庫里的main/cpp目錄源碼,過程比較簡(jiǎn)單陆爽,就是寫個(gè)jni的方法來直接調(diào)用bsdiff庫什往,目錄結(jié)構(gòu)如下:

main

    -cpp
    
        -bzip2
    
        -CMakeLists.txt
    
        -patchUtils.c
    
        -patchUtils.h
    
        -update-lib.cpp

因?yàn)閎sdiff還依賴了bzip2,所以這里涉及多個(gè)源文件編譯鏈接問題慌闭,需要在CMakeLists.txt稍作修改:

# 將當(dāng)前 "./src/main/cpp" 目錄下的所有源文件保存到 "NATIVE_SRC" 中别威,然后在 add_library 方法調(diào)用。
aux_source_directory( . NATIVE_SRC )
# 將 "./src/main/cpp/bzip2" 目錄下的子目錄bzip2保存到 "BZIP2_BASE" 中驴剔,然后在 add_library 方法調(diào)用省古。
aux_source_directory( ./bzip2 BZIP2_BASE )
# 將 BZIP2_BASE 增加到 NATIVE_SRC 中,這樣目錄的源文件也加入了編譯列表中丧失,當(dāng)然也可以不加到 NATIVE_SRC豺妓,直接調(diào)用add_library。
list(APPEND NATIVE_SRC ${BZIP2_BASE})

add_library( # Sets the name of the library.
        update-lib
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        ${NATIVE_SRC})

差分包生成

? 服務(wù)端見github ,使用時(shí)將manifestJsonUrl改成部署的服務(wù)器地址即可琳拭,如下示例代碼片段的注釋處

public class MainActivity extends AppCompatActivity {
    private String manifestJsonUrl = "https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/UpdateManifest.json";
//    private String manifestJsonUrl = "http://192.168.2.107:8000/app/UpdateManifest.json";
    ...
}

依賴

本文由Owen Lee原創(chuàng)训堆,轉(zhuǎn)載請(qǐng)注明來源:
http://www.reibang.com/p/058fd24bb2da

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市白嘁,隨后出現(xiàn)的幾起案子坑鱼,更是在濱河造成了極大的恐慌,老刑警劉巖权薯,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姑躲,死亡現(xiàn)場(chǎng)離奇詭異睡扬,居然都是意外死亡盟蚣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門卖怜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屎开,“玉大人,你說我怎么就攤上這事马靠⊙俪椋” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵甩鳄,是天一觀的道長(zhǎng)逞度。 經(jīng)常有香客問我,道長(zhǎng)妙啃,這世上最難降的妖魔是什么档泽? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮揖赴,結(jié)果婚禮上馆匿,老公的妹妹穿的比我還像新娘。我一直安慰自己燥滑,他們只是感情好渐北,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铭拧,像睡著了一般赃蛛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搀菩,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天呕臂,我揣著相機(jī)與錄音,去河邊找鬼秕磷。 笑死诵闭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疏尿,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瘟芝,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了褥琐?” 一聲冷哼從身側(cè)響起锌俱,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎敌呈,沒想到半個(gè)月后贸宏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磕洪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年吭练,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片析显。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鲫咽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谷异,到底是詐尸還是另有隱情分尸,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布歹嘹,位于F島的核電站箩绍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏尺上。R本人自食惡果不足惜材蛛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尖昏。 院中可真熱鬧仰税,春花似錦、人聲如沸抽诉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迹淌。三九已至河绽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唉窃,已是汗流浹背耙饰。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纹份,地道東北人苟跪。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓廷痘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親件已。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笋额,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354