UpdateAppUtils一行代碼實現(xiàn)app在線更新

UpdateAppUtils 已適配Android6.0瘪匿、7.0、8.0

一行代碼寻馏,快速實現(xiàn)app在線下載更新A simple library for Android update app

當前文章是老版本介紹棋弥,請看最新文章

先看效果圖:


update.gif

快速使用

先來看看怎樣一行代碼實現(xiàn)更新:

dependencies {
    compile 'com.teprinciple:updateapputils:1.5.1'
}
UpdateAppUtils.from(this)
                .checkBy(UpdateAppUtils.CHECK_BY_VERSION_NAME) //更新檢測方式,默認為VersionCode
                .serverVersionCode(2)
                .serverVersionName("2.0")
                .apkPath(apkPath)
                .showNotification(false) //是否顯示下載進度到通知欄诚欠,默認為true
                .updateInfo(info)  //更新日志信息 String
                .downloadBy(UpdateAppUtils.DOWNLOAD_BY_BROWSER) //下載方式:app下載顽染、手機瀏覽器下載。默認app下載
                .isForce(true) //是否強制更新轰绵,默認false 強制更新情況下用戶不同意更新則不能使用app
                .update();

實現(xiàn)原理

使用很簡單吧粉寞,其實實現(xiàn)過程也很簡單,大致分為三步:
1左腔、根據(jù)初入?yún)?shù)判斷是否需要更新
2唧垦、下載服務(wù)器上的最新apk(通過DownloadManager下載)
3、安裝最新apk

下面我們來看看源碼:
源碼已更新翔悠,最新代碼請看 UpdateAppDemo

第一步:初始化參數(shù)并判斷。根據(jù)傳入的服務(wù)器版本號與本地版本號做出判斷是否需要更新野芒,并配置好下載地址蓄愁,下載方式等參數(shù)。

/**
 * Created by Teprinciple on 2016/11/15.
 */
public class UpdateAppUtils {

    private final String TAG = "UpdateAppUtils";
    public static final int CHECK_BY_VERSION_NAME = 1001;
    public static final int CHECK_BY_VERSION_CODE = 1002;
    public static final int DOWNLOAD_BY_APP = 1003;
    public static final int DOWNLOAD_BY_BROWSER = 1004;

    private Activity activity;
    private int checkBy = CHECK_BY_VERSION_CODE;
    private int downloadBy = DOWNLOAD_BY_APP;
    private int serverVersionCode = 0;
    private String apkPath="";
    private String serverVersionName="";
    private boolean isForce = false; //是否強制更新
    private int localVersionCode = 0;
    private String localVersionName="";
    public static boolean showNotification = true;
    private String updateInfo = "";


    private UpdateAppUtils(Activity activity) {
        this.activity = activity;
        getAPPLocalVersion(activity);
    }

    public static UpdateAppUtils from(Activity activity){
        return new UpdateAppUtils(activity);
    }

    public UpdateAppUtils checkBy(int checkBy){
        this.checkBy = checkBy;
        return this;
    }

    public UpdateAppUtils apkPath(String apkPath){
        this.apkPath = apkPath;
        return this;
    }

    public UpdateAppUtils downloadBy(int downloadBy){
        this.downloadBy = downloadBy;
        return this;
    }

    public UpdateAppUtils showNotification(boolean showNotification){
        this.showNotification = showNotification;
        return this;
    }

    public UpdateAppUtils updateInfo(String updateInfo){
        this.updateInfo = updateInfo;
        return this;
    }
    
    public UpdateAppUtils serverVersionCode(int serverVersionCode){
        this.serverVersionCode = serverVersionCode;
        return this;
    }

    public UpdateAppUtils serverVersionName(String  serverVersionName){
        this.serverVersionName = serverVersionName;
        return this;
    }

    public UpdateAppUtils isForce(boolean  isForce){
        this.isForce = isForce;
        return this;
    }

    //獲取apk的版本號 currentVersionCode
    private  void getAPPLocalVersion(Context ctx) {
        PackageManager manager = ctx.getPackageManager();
        try {
            PackageInfo info = manager.getPackageInfo(ctx.getPackageName(), 0);
            localVersionName = info.versionName; // 版本名
            localVersionCode = info.versionCode; // 版本號
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    public void update(){

        switch (checkBy){
            case CHECK_BY_VERSION_CODE:
                if (serverVersionCode >localVersionCode){
                    toUpdate();
                }else {
                    Log.i(TAG,"當前版本是最新版本"+serverVersionCode+"/"+serverVersionName);
                }
                break;

            case CHECK_BY_VERSION_NAME:
                if (!serverVersionName.equals(localVersionName)){
                    toUpdate();
                }else {
                    Log.i(TAG,"當前版本是最新版本"+serverVersionCode+"/"+serverVersionName);
                }
                break;
        }

    }

    private void toUpdate() {
        realUpdate();
    }

    private void realUpdate() {
        ConfirmDialog dialog = new ConfirmDialog(activity, new Callback() {
            @Override
            public void callback(int position) {
                switch (position){
                    case 0:  //cancle
                        if (isForce)System.exit(0);
                        break;
                    case 1:  //sure
                        if (downloadBy == DOWNLOAD_BY_APP) {
                            if (isWifiConnected(activity)){
                                DownloadAppUtils.downloadForAutoInstall(activity, apkPath, "demo.apk", serverVersionName);
                            }else {
                                new ConfirmDialog(activity, new Callback() {
                                    @Override
                                    public void callback(int position) {
                                        if (position==1){
                                            DownloadAppUtils.downloadForAutoInstall(activity, apkPath, "demo.apk", serverVersionName);
                                        }else {
                                            if (isForce)activity.finish();
                                        }
                                    }
                                }).setContent("目前手機不是WiFi狀態(tài)\n確認是否繼續(xù)下載更新狞悲?").show();
                            }

                        }else if (downloadBy == DOWNLOAD_BY_BROWSER){
                            DownloadAppUtils.downloadForWebView(activity,apkPath);
                        }
                        break;
                }
            }
        });

        String content = "發(fā)現(xiàn)新版本:"+serverVersionName+"\n是否下載更新?";
        if (!TextUtils.isEmpty(updateInfo)){
            content = "發(fā)現(xiàn)新版本:"+serverVersionName+"是否下載更新?\n\n"+updateInfo;
        }
        dialog .setContent(content);
        dialog.setCancelable(false);
        dialog.show();
    }

    //檢測wifi是否連接
    public static boolean isWifiConnected(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm != null) {
            NetworkInfo networkInfo = cm.getActiveNetworkInfo();
            if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                return true;
            }
        }
        return false;
    }
}

第二步:下載apk撮抓。UpdateAppUtils提供了 app內(nèi)部下載(DownloadManager)、第三方瀏覽器下載 兩種下載方式摇锋。

/**
 *Created by Teprinciple on 2016/12/13.
 */
public class DownloadAppUtils {
    private static final String TAG = DownloadAppUtils.class.getSimpleName();
    public static long downloadUpdateApkId = -1;//下載更新Apk 下載任務(wù)對應(yīng)的Id
    public static String downloadUpdateApkFilePath;//下載更新Apk 文件路徑

    /**
     * 通過瀏覽器下載APK包
     * @param context
     * @param url
     */
    public static void downloadForWebView(Context context, String url) {
        Uri uri = Uri.parse(url);
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }


    /**
     * 下載更新apk包
     * 權(quán)限:1,<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
     * @param context
     * @param url
     */
    public static void downloadForAutoInstall(Context context, String url, String fileName, String title) {
        if (TextUtils.isEmpty(url)) {
            return;
        }
        try {
            Uri uri = Uri.parse(url);
            DownloadManager downloadManager = (DownloadManager) context
                    .getSystemService(Context.DOWNLOAD_SERVICE);
            DownloadManager.Request request = new DownloadManager.Request(uri);
            //在通知欄中顯示
            request.setVisibleInDownloadsUi(true);
            request.setTitle(title);
            // VISIBILITY_VISIBLE:                   下載過程中可見, 下載完后自動消失 (默認)
           // VISIBILITY_VISIBLE_NOTIFY_COMPLETED:  下載過程中和下載完成后均可見
           // VISIBILITY_HIDDEN:                    始終不顯示通知
            if (!UpdateAppUtils.showNotification)
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
            String filePath = null;
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//外部存儲卡
                filePath = Environment.getExternalStorageDirectory().getAbsolutePath();
            } else {
                Log.i(TAG,"沒有SD卡");
                return;
            }
            downloadUpdateApkFilePath = filePath + File.separator + fileName;
            deleteFile(downloadUpdateApkFilePath);// 若存在丹拯,則刪除
            Uri fileUri = Uri.fromFile(new File(downloadUpdateApkFilePath));
            request.setDestinationUri(fileUri);
            downloadUpdateApkId = downloadManager.enqueue(request);

        } catch (Exception e) {
            e.printStackTrace();
            downloadForWebView(context, url);
        }
    }

    private static boolean deleteFile(String fileStr) {
        File file = new File(fileStr);
        return file.delete();
    }
}

第三步站超、安裝apk。DownloadManager下載完成后乖酬,會發(fā)送通知死相。我們在UpdateAppReceiver ,接受到通知后執(zhí)行安裝操作咬像。

/**
 * 注冊
 * <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
 * <action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"/>
 */
public class UpdateAppReceiver extends BroadcastReceiver {
    public UpdateAppReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // 處理下載完成
        Cursor c = null;

        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
            if (DownloadAppUtils.downloadUpdateApkId >= 0) {
                long downloadId = DownloadAppUtils.downloadUpdateApkId;
                DownloadManager.Query query = new DownloadManager.Query();
                query.setFilterById(downloadId);
                DownloadManager downloadManager = (DownloadManager) context
                        .getSystemService(Context.DOWNLOAD_SERVICE);
                c = downloadManager.query(query);
                if (c.moveToFirst()) {
                    int status = c.getInt(c
                            .getColumnIndex(DownloadManager.COLUMN_STATUS));
                    if (status == DownloadManager.STATUS_FAILED) {
                        downloadManager.remove(downloadId);
                    } else if (status == DownloadManager.STATUS_SUCCESSFUL) {
                        if (DownloadAppUtils.downloadUpdateApkFilePath != null) {
                            Intent i = new Intent(Intent.ACTION_VIEW);
                            File apkFile = new File(DownloadAppUtils.downloadUpdateApkFilePath);

                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                                i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                                Uri contentUri = FileProvider.getUriForFile(
                                        context, context.getPackageName() + ".fileprovider", apkFile);
                                i.setDataAndType(contentUri, "application/vnd.android.package-archive");
                            } else {
                                i.setDataAndType(Uri.fromFile(apkFile),
                                        "application/vnd.android.package-archive");
                            }
                            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            context.startActivity(i);
                        }
                    }
                }
                c.close();
            }
        }
    }
}

通過上面三個類就可以實現(xiàn)在線下載更新app了算撮。下面我們看看關(guān)于Android6.0以及Android7.0的適配。

適配Android7.0

安卓官方為了提高私有文件的安全性县昂,對于Android 7.0 及更高版本的應(yīng)用私有目錄被限制訪問肮柜。因此,在使用Intent方式安裝時倒彰,嘗試傳遞 file:// URI 會觸發(fā) FileUriExposedException审洞。解決方法是使用 FileProvider,如下:
1待讳、注冊provider

    <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/file_paths" />
    /provider>

2芒澜、新建file_paths.xml文件

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path path="Android/data/包名/"    name="files_root" />
    <external-path path="." name="external_storage_root" />
</paths>

如果你的版本沒有適配到Android7.0 為了不進行上述操作,可以直接這樣設(shè)置:

UpdateAppUtils.needFitAndroidN(false)
適配Android6.0

關(guān)于6.0適配耙箍,請自行在調(diào)用API時申請WRITE_EXTERNAL_STORAGE權(quán)限撰糠,可以參考demo中的代碼

目前不足之處

UpdateAppUtils很簡單方便實現(xiàn)了app的在線下載更新。但是本庫目前有存在有一些不足之處:
1辩昆、目前使用DownloadManager作為下載模塊阅酪,但是國內(nèi)部分手機DownloadManager功能已被閹割,造成不能下載汁针。
2术辐、目前更新彈窗暫時沒提供自定義UI接口。
3施无、目前每次檢查需更新后都執(zhí)行下載辉词,沒有判斷本地是否已有最新apk文件。
這些問題我會在后面進行完善猾骡。如果你發(fā)現(xiàn)本庫有其他的不足瑞躺,或者對本庫有好的建議都可以issue我。希望能通過大家的力量兴想,一起把UpdateAppUtils做的更好幢哨。

具體原理及源碼可見 代碼傳送門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市嫂便,隨后出現(xiàn)的幾起案子捞镰,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岸售,死亡現(xiàn)場離奇詭異践樱,居然都是意外死亡,警方通過查閱死者的電腦和手機凸丸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門拷邢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人甲雅,你說我怎么就攤上這事解孙。” “怎么了抛人?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵弛姜,是天一觀的道長。 經(jīng)常有香客問我妖枚,道長廷臼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任绝页,我火速辦了婚禮荠商,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘续誉。我一直安慰自己莱没,他們只是感情好,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布酷鸦。 她就那樣靜靜地躺著饰躲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪臼隔。 梳的紋絲不亂的頭發(fā)上嘹裂,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音摔握,去河邊找鬼寄狼。 笑死,一個胖子當著我的面吹牛氨淌,可吹牛的內(nèi)容都是我干的泊愧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盛正,長吁一口氣:“原來是場噩夢啊……” “哼删咱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛮艰,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤腋腮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后壤蚜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體即寡,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年袜刷,在試婚紗的時候發(fā)現(xiàn)自己被綠了聪富。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡著蟹,死狀恐怖墩蔓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萧豆,我是刑警寧澤奸披,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站涮雷,受9級特大地震影響阵面,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洪鸭,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一亏掀、第九天 我趴在偏房一處隱蔽的房頂上張望齿兔。 院中可真熱鬧,春花似錦、人聲如沸巩割。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苹支。三九已至,卻和暖如春梅肤,著一層夾襖步出監(jiān)牢的瞬間司蔬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工姨蝴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留俊啼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓左医,卻偏偏與公主長得像授帕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浮梢,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評論 25 707
  • 源碼 寫在前面 之前項目需要實現(xiàn)內(nèi)部更新功能跛十,看到了Android實現(xiàn)APP在線下載更新這篇文章,對于能夠一行代碼...
    沒事打打醬油落閱讀 11,138評論 10 1
  • 公司開發(fā)時候秕硝,應(yīng)該最常用的就是APP升級功能芥映,倒不是說的是熱修復(fù)等技術(shù),而是普通的檢測到服務(wù)器版本比本地手機版本高...
    青蛙要fly閱讀 4,558評論 12 86
  • 《連城訣》,查良鏞筆下著墨不算太多的作品奈偏,說是武俠小說坞嘀,實是一部人性解剖后再現(xiàn),赤裸裸的再現(xiàn)惊来。正如我之前與你所言丽涩,...
    通向馬達加斯加閱讀 427評論 0 0
  • “人人已變成萬科的小股東矢渊,隨時關(guān)心萬科動向⊥髦ぃ” 1 6月26日下午矮男,萬科發(fā)布公告,寶能系提請罷免萬科董事和監(jiān)事室谚,這...
    醒醒老師閱讀 500評論 0 2