Android增量自動更新

抽取的Android自動更新庫,目的是幾行代碼引入更新功能,含服務端代碼,歡迎Star,歡迎Fork,謝謝~javascript

博客同步自:我的博客主頁 代碼github: github.com/itlwy/AppSm…java

目錄

功能介紹

  • 支持全量更新apk,直接升級到最新版本
  • 支持增量更新,只下載補丁包升級
  • 設置僅在wifi環境下更新
  • 支持外部注入網絡框架(庫默認使用okhttp)
  • 支持前臺和後臺自動更新
  • 支持強制更新
  • 支持對外定製更新提示和更新進度界面
  • 記憶下載
  • 含發佈功能後臺服務端github (Node.js實現)

流程圖

flowchart.jpg

效果圖與示例apk

示例1
示例2

點擊下載 smart-update.apkgit

如何引入

Gradle引入

step 1

Add the JitPack repository to your build filegithub

allprojects {
			repositories {
				...
				maven { url 'https://jitpack.io' }
			}
		}
複製代碼

Step 2

Add the dependencyjson

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

複製代碼

更新清單文件

該清單放置在靜態服務器以供App訪問,主要用於判斷最新的版本,及要更新的版本資源信息等(示例見倉庫根目錄下的resources目錄或直接訪問後臺代碼 github),清單由服務端程序發佈apk時生成,詳見後臺示例:githubbash

{
  "minVersion": 100, // app最低支持的版本代碼(包含),低於此數值的app將強制更新
  "minAllowPatchVersion": 100, // 最低支持的差分版本(包含),低於此數值的app將採起全量更新,不然採用差量
  "newVersion": 101, // 當前最新版本代碼
  "tip": "test update",	// 更新提示
  "size": 1956631,	// 最新apk文件大小
  "apkURL": "https://raw.githubusercontent.com/itlwy/AppSmartUpdate/master/resources/app/smart-update.apk", // 最新apk 絕對url地址,也可用相對地址,以下方的"patchURL"字段
  "hash": "ea97c8efa490a2eaf7d10b37e63dab0e", // 最新apk文件的md5值
  "patchInfo": {  // 差分包信息
    "v100": { // v100表示-版本代碼100的apk須要下載的差分包
      "patchURL": "v100/100to101.patch", //差分包地址,相對此UpdateManifest.json文件的地址,也可用絕對地址
      "tip": "101 version", // 提示
      "hash": "ea97c8efa490a2eaf7d10b37e63dab0e", // 合成後apk(即版本代碼101)的文件md5值
      "size": 1114810 // 差分包大小
    }
  }
}
複製代碼

簡單使用

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.調用

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;

        }
    }
}
複製代碼

詳細說明

註冊通知回調

  • 其餘activity界面須要獲知後臺更新狀況
public void register(IUpdateCallback callback) {...}

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

public interface IUpdateCallback {

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

    /** * 自動更新準備開始時回調,運行在主線程,可作一些提示等 */
    void beforeUpdate();

    /** * 自動更新的進度回調(分增量和全量更新),運行在主線程 * * @param percent 當前總進度百分比 * @param totalLength 更新總大小(全量爲apk大小,增量爲所有補丁大小和) * @param patchIndex 當前更新的補丁索引(從1開始) * @param patchCount 須要更新的總補丁數(當爲0時表示是增量更新) */
    void onProgress(int percent, long totalLength, int patchIndex, int patchCount);

    /** * 下載完成,準備更新,運行在主線程 */
    void onCompleted();

    /** * 異常回調,運行在主線程 * * @param error 異常信息 */
    void onError(String error);

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

    /** * 取消了更新進度對話框,壓入後臺自動更新,此時由通知欄通知進度 */
    void onBackgroundTrigger();
}
複製代碼

網絡框架注入

默認使用okhttp,也可由外部注入,只需實現以下的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請求地址 * @param params get參數 * @param callBack 回調 */
    void asyncGet(@NonNull String url, @NonNull Map<String, String> params, @NonNull Callback callBack);


    /** * 異步post * * @param url post請求地址 * @param params post請求參數 * @param callBack 回調 */
    void asyncPost(@NonNull String url, @NonNull Map<String, String> params, @NonNull Callback callBack);

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

定製更新交互界面

每一個應用的風格均可能是不同的,所以這裏也支持自定義彈出的提示框和進度框,詳細見以下代碼示例:網絡

  1. 初始化config時須要將內部默認的彈框屏蔽掉app

    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. 自定義對話框,以下(詳細代碼在MainActivity.java裏):框架

public void registerUpdateCallbak() {
        mCallback = new IUpdateCallback() {
            @Override
            public void noNewApp() {
                Toast.makeText(MainActivity.this, "當前已經是最新版本!", 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("自動更新提示")
                        .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) {
                        // 退到後臺自動更新,進度由通知欄顯示
                        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("正在下載補丁%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, "轉爲後臺更新,進度由通知欄提示!", Toast.LENGTH_LONG).show();
            }
        };
        UpdateManager.getInstance().register(mCallback);
    }
複製代碼

差分包合成(jni)

​ 此部分採用的差分工具爲開源bsdiff,用於生成.patch補丁文件,採用jni方式封裝一個.so庫供java調用,詳見"smartupdate"庫裏的main/cpp目錄源碼,過程比較簡單,就是寫個jni的方法來直接調用bsdiff庫,目錄結構以下:

main

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

複製代碼

由於bsdiff還依賴了bzip2,因此這裏涉及多個源文件編譯連接問題,須要在CMakeLists.txt稍做修改:

# 將當前 "./src/main/cpp" 目錄下的全部源文件保存到 "NATIVE_SRC" 中,而後在 add_library 方法調用。
aux_source_directory( . NATIVE_SRC )
# 將 "./src/main/cpp/bzip2" 目錄下的子目錄bzip2保存到 "BZIP2_BASE" 中,而後在 add_library 方法調用。
aux_source_directory( ./bzip2 BZIP2_BASE )
# 將 BZIP2_BASE 增長到 NATIVE_SRC 中,這樣目錄的源文件也加入了編譯列表中,固然也能夠不加到 NATIVE_SRC,直接調用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})
複製代碼

差分包生成

​ 服務端見github ,使用時將manifestJsonUrl改爲部署的服務器地址便可,以下示例代碼片斷的註釋處

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";
    ...
}
複製代碼

依賴

  • okhttp : com.squareup.okhttp3:okhttp:3.11.0
  • gson : com.google.code.gson:gson:2.8.0
  • numberprogressbar : com.daimajia.numberprogressbar:library:1.4@aar

本文由Owen Lee原創,轉載請註明來源

相關文章
相關標籤/搜索