中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Android APK 更新之路

2018-07-20    來(lái)源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

一、前言

提到 APK 更新,大家可能會(huì)想到友盟(umeng)更新,市場(chǎng)上已有數(shù)萬(wàn)款應(yīng)用在使用友盟自動(dòng)更新的服務(wù)。但友盟于 2016 年 10 月 15 日起停止了更新服務(wù)。那么我們需要自己處理 APK 更新的業(yè)務(wù)。

本篇主要講解以下知識(shí)點(diǎn):

  • 使用 DownloadManager 更新

  • 基于 RxJava 和 retrofit 擴(kuò)展的 Android 線程安全 http 請(qǐng)求庫(kù)下載 APK 更新

  • 熱更新(AndFix)

我們來(lái)啾啾第一個(gè)知識(shí)點(diǎn)。

DownloadManager 更新

Android 2.3(API level 9)開始 Android 用系統(tǒng)服務(wù)(Service)的方式提供了DownloadManager 來(lái)優(yōu)化處理長(zhǎng)時(shí)間的下載操作。DownloadManager 對(duì)后臺(tái)下載,下載狀態(tài)回調(diào),斷點(diǎn)續(xù)傳,下載環(huán)境設(shè)置,下載文件的操作等都有很好的支持。

本篇基于 Android 4.0 ~7.0 (SDK 14~24) 開發(fā),眾所周知 Android 6.0 的 Runtime Permissions (運(yùn)行時(shí)權(quán)限)。

下面具體來(lái)看看 DownloadManager 更新的具體流程。

AndroidManifest 清單文件配置權(quán)限

下載文件需要使用到網(wǎng)絡(luò)權(quán)限,文件讀寫權(quán)限:

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

獲取當(dāng)前的版本號(hào)

getPackageManager().getPackageInfo(getPackageName(), 0).versionName;

后臺(tái)需要提供查詢最新版本號(hào)的接口,獲取接口數(shù)據(jù)與當(dāng)前版本號(hào)對(duì)比,判定是否需要更新。

獲取 DownloadManager 實(shí)例

DownloadManager manager = (DownloadManager)
            appContext.getSystemService(Context.DOWNLOAD_SERVICE);

下面來(lái)看看 DownloadManager 提供哪些接口:

  • public long enqueue(Request request) 執(zhí)行下載,返回 downloadId,downloadId 可用于后面查詢下載信息。若網(wǎng)絡(luò)不滿足條件、Sdcard 掛載中、超過(guò)最大并發(fā)數(shù)等異常則會(huì)等待下載,正常則直接下載。

  • int remove(long… ids) 刪除下載,若下載中取消下載。會(huì)同時(shí)刪除下載文件和記錄。參數(shù) ids 為 enqueue 返回的 downloadId 集合。

  • Cursor query(Query query) 查詢下載信息。

  • getMaxBytesOverMobile(Context context) 返回移動(dòng)網(wǎng)絡(luò)下載的最大值

  • rename(Context context, long id, String displayName) 重命名已下載項(xiàng)的名字

  • getRecommendedMaxBytesOverMobile(Context context) 獲取建議的移動(dòng)網(wǎng)絡(luò)下載的大小

  • 其它:通過(guò)查看代碼我們可以發(fā)現(xiàn)還有個(gè) CursorTranslator 私有靜態(tài)內(nèi)部類。這個(gè)類主要對(duì) Query 做了一層代理。將 DownloadProvider 和 DownloadManager之間做了個(gè)映射。

接著來(lái)看看 DownloadManager.Request 的請(qǐng)求參數(shù)。

組裝 DownloadManager.Request 請(qǐng)求參數(shù)

//獲取Request的實(shí)例對(duì)象 
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(appUrl));

顯示信息:

//設(shè)置一些基本顯示信息
    request.setTitle(name); //通知欄標(biāo)題
    request.setDescription(description);//通知欄內(nèi)容
    request.setMimeType("application/vnd.android.package-archive");//文件的類型

網(wǎng)絡(luò)類型:

//NETWORK_MOBILE移動(dòng)網(wǎng)絡(luò)
//NETWORK_WIFI  wifi網(wǎng)絡(luò)
//NETWORK_BLUETOOTH 藍(lán)牙
req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);

通知欄顯示類型:

request.setNotificationVisibility(DownloadManager.Request
            .VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
  • VISIBILITY_HIDDEN 下載UI不會(huì)顯示,也不會(huì)顯示在通知中,如果設(shè)置該值,
    需要聲明android.permission.DOWNLOAD_WITHOUT_NOTIFICATION
  • VISIBILITY_VISIBLE 當(dāng)處于下載中狀態(tài)時(shí),可以在通知欄中顯示;當(dāng)下載完成后,通知欄中不顯示
  • VISIBILITY_VISIBLE_NOTIFY_COMPLETED 當(dāng)處于下載中狀態(tài)和下載完成時(shí)狀態(tài),均在通知欄中顯示
  • VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION 只在下載完成時(shí)顯示在通知欄中。

文件的保存位置:

  • 保存到外部環(huán)境的私有目錄:file:///storage/emulated/0/Android/data/your-package/files/Download/app.apk
request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, "app.apk");
  • 保存到外部環(huán)境的共有目錄: file:///storage/emulated/0/Download/app.apk
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app.apk");
  • 自定義文件路徑
setDestinationUri(Uri uri)

添加請(qǐng)求下載的網(wǎng)絡(luò)鏈接的http頭,比如User-Agent,gzip壓縮等:

request.addRequestHeader(String header, String value)

漫游:

//true  允許
//false  不允許
request.setAllowedOverRoaming(false);

其他:

setAllowedOverMetered(boolean allow) //是否允許計(jì)量
setRequiresCharging(boolean requiresCharging)//是否在充電環(huán)境下
setVisibleInDownloadsUi(boolean isVisible)//是否顯示下載界面
...

下面是本文創(chuàng)建Request的示例代碼:

request.setTitle(name);
    request.setDescription(description);
    //在通知欄顯示下載進(jìn)度
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        request.allowScanningByMediaScanner();
        request.setNotificationVisibility(DownloadManager.Request
                .VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    }

    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
    request.setDestinationInExternalPublicDir(SAVE_APP_LOCATION, SAVE_APP_NAME);

加入下載隊(duì)列

DownloadManager manager = (DownloadManager)                appContext.getSystemService(Context.DOWNLOAD_SERVICE);

manager.enqueue(request);

下載信息查詢

DownloadManager 下載工具并沒(méi)有提供相應(yīng)的回調(diào)接口用于返回實(shí)時(shí)的下載進(jìn)度狀態(tài)。可以通過(guò) DownloadManager.query 方法進(jìn)行查詢,該方法返回一個(gè) Cursor 對(duì)象,具體看以下代碼:

private void queryDownloadManager(long id) {
        DownloadManager mDownloadManager = (DownloadManager)
                this.getSystemService(Context.DOWNLOAD_SERVICE);
        DownloadManager.Query query = new DownloadManager.Query().setFilterById(id);
        //可以對(duì)query設(shè)置一些過(guò)濾條件
        //setFilterById(long… ids)根據(jù)下載id進(jìn)行過(guò)濾
        //setFilterByStatus(int flags)根據(jù)下載狀態(tài)進(jìn)行過(guò)濾
        Cursor cursor = mDownloadManager.query(query);

        if (cursor != null) {

            while (cursor.moveToNext()) {

                String bytesDownload = cursor.getString(cursor.getColumnIndex(DownloadManager
                        .COLUMN_BYTES_DOWNLOADED_SO_FAR));
                String description = cursor.getString(cursor.getColumnIndex(DownloadManager
                        .COLUMN_DESCRIPTION));
                String cid = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
                String localUri = cursor.getString(cursor.getColumnIndex(DownloadManager
                        .COLUMN_LOCAL_URI));
                String mimeType = cursor.getString(cursor.getColumnIndex(DownloadManager
                        .COLUMN_MEDIA_TYPE));
                String title = cursor.getString(cursor.getColumnIndex(DownloadManager
                        .COLUMN_TITLE));
                String status = cursor.getString(cursor.getColumnIndex(DownloadManager
                        .COLUMN_STATUS));
                String totalSize = cursor.getString(cursor.getColumnIndex(DownloadManager
                        .COLUMN_TOTAL_SIZE_BYTES));

                Log.i("MainActivity", "bytesDownload:" + bytesDownload);
                Log.i("MainActivity", "description:" + description);
                Log.i("MainActivity", "cid:" + cid);
                Log.i("MainActivity", "localUri:" + localUri);
                Log.i("MainActivity", "mimeType:" + mimeType);
                Log.i("MainActivity", "title:" + title);
                Log.i("MainActivity", "status:" + status);
                Log.i("MainActivity", "totalSize:" + totalSize);
            }

        }
    }

本篇示例的打印結(jié)果如下:

man

注冊(cè)廣播監(jiān)聽通知欄點(diǎn)擊事件和下載完成事件

當(dāng)用戶點(diǎn)擊通知欄中的下載列表時(shí),系統(tǒng)會(huì)發(fā)出 ACTION_NOTIFICATION_CLICKED 事件廣播;下載完成時(shí)會(huì)發(fā)出 ACTION_DOWNLOAD_COMPLETE 事件廣播,那么我們就可以實(shí)現(xiàn)一個(gè)廣播接收器處理點(diǎn)擊和完成時(shí)的狀態(tài)。請(qǐng)看下面代碼:

public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {

            installApk(context);

        } else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
            //Toast.makeText(context, "Clicked", Toast.LENGTH_SHORT).show();

        }
    }

如文本下載 apk 文件,下載完成時(shí)就自動(dòng)安裝,使用意圖進(jìn)行 apk 安裝:

// 安裝Apk
    private void installApk(Context context) {
        try {
            Intent i = new Intent(Intent.ACTION_VIEW);
            String filePath = DownloadManagerUtils.APP_FILE_NAME;
            i.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android" +
                    ".package-archive");
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(i);
        } catch (Exception e) {
            Log.e(TAG, "安裝失敗");
            e.printStackTrace();
        }
    }

DownloadManager 更新就講到這里了,源碼在文章的后面會(huì)附上。

基于 RxJava 和 retrofit 擴(kuò)展的 Android 線程安全 http 請(qǐng)求庫(kù)下載 APK 更新

針對(duì) DownloadManager 更新,我們還可以通過(guò) http 請(qǐng)求庫(kù)下載 apk 文件進(jìn)行更新。

提到 http 請(qǐng)求庫(kù),就不得不提到 Novate 庫(kù),功能非常強(qiáng)大,使用便利,看看它有哪些功能:

  • 加入基礎(chǔ)API,減少Api冗余
  • 支持離線緩存
  • 支持多種方式訪問(wèn)網(wǎng)絡(luò)(get,put,post ,delete)
  • 支持Json字符串,表單提交
  • 支持文件下載和上傳
  • 支持請(qǐng)求頭統(tǒng)一加入
  • 支持對(duì)返回結(jié)果的統(tǒng)一處理
  • 支持自定義的擴(kuò)展API
  • 支持統(tǒng)一請(qǐng)求訪問(wèn)網(wǎng)絡(luò)的流程控制

我下載了源碼,并修改了進(jìn)度條的接口。下載文件相信大家都比較熟悉了,我這里就不再細(xì)講了。如果有什么疑問(wèn)請(qǐng)鏈接上面地址查看。

新建通知

以下給出本篇用到的消息代碼:

private NotificationCompat.Builder buildNotification() {
        final Resources res = mContext.getResources();

        // This image is used as the notification's large icon (thumbnail).
        // TODO: Remove this if your notification has no relevant thumbnail.
        final Bitmap picture = BitmapFactory.decodeResource(res, R.mipmap.ic_launcher);

        return new NotificationCompat.Builder(mContext).
                setContentTitle("更新包下載中...")
                .setTicker("準(zhǔn)備下載...")
                .setProgress(100, 0, false)
                .setContentText(String.format(mContext.getResources()
                        .getString(R.string.apk_progress), 0) + "%")
                .setLargeIcon(picture)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setAutoCancel(false);
    }

    //更新消息進(jìn)度
    public void showProgressNotification(int progress) {
        if (mBuilder == null) {
            mBuilder = buildNotification();
        }
        Notification notification = mBuilder.setProgress(100, progress, false)
                .setContentText(String.format(mContext.getResources().getString(R.string
                        .apk_progress), progress) + "%")
                .build();
        notify(mContext, notification);
    }

apk下載

private void downloadApk() {

        RetrofitClient.getInstance(this).createBaseApi()
                .download(DOWN_URL, new CallBack() {
                    @Override
                    public void onError(Throwable e) {
                        Log.e("HttpActivity", "onError--------2222" + e.getMessage());
                        mHttpNotification.removeProgressNotification();
                    }

                    @Override
                    public void onStart() {
                        super.onStart();
                        mHttpNotification.showProgressNotification(0);
                    }

                    @Override
                    public void onSucess(String path, String name, long fileSize) {
                        mHttpNotification.removeProgressNotification();
                        installApk(HttpActivity.this);
                    }

                    @Override
                    public void onProgress(int progress) {
                        super.onProgress(progress);
                        mCircleProgressView.setProgress(progress);
                        mHttpNotification.showProgressNotification(progress);
                    }
                });

    }

如果你還有疑問(wèn),在文章結(jié)尾處下載源碼進(jìn)行查看。

更新全過(guò)程效果圖:

 

熱更新(AndFix)

熱更新技術(shù)近段時(shí)間非;鸨,各個(gè)大公司都相繼開發(fā)自己的熱更新框架。由于公司主要項(xiàng)目基于電商商城,所以我選擇了阿里巴巴的 AndFix 熱更新的實(shí)現(xiàn),使用起來(lái)也比較簡(jiǎn)單。至少在我的測(cè)試下修改一些小的 BUG 是沒(méi)有問(wèn)題的。

我的開發(fā)工具是 Android Studio ,第一步導(dǎo)包:

app 的 dependencies 的節(jié)點(diǎn)下:

compile 'com.alipay.euler:andfix:[email protected]'

第二步配置 MyApplication 類:

@TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void onCreate() {
        super.onCreate();

        String version = "";
        try {
            version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        mPatchManager = new PatchManager(getApplicationContext());
        mPatchManager.init(version);
        mPatchManager.loadPatch();
        try {
            String patchFileString = "/sdcard" + APATCH_PATH;
            mPatchManager.addPatch(patchFileString);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

首先獲取到版本號(hào),系統(tǒng)會(huì)判斷版本號(hào),只有相同的版本號(hào)的時(shí)候會(huì)執(zhí)行熱更新。其中 String patchFileString = "/sdcard" + APATCH_PATH; 是我測(cè)試的補(bǔ)丁存放路徑。你需要替換成你自己的存放路徑。

注意:文件的權(quán)限。

然后在 MainActivity 中寫一個(gè)打印吐司的方法:

private void showToast() {
        Toast.makeText(this, "你好啊", Toast.LENGTH_LONG).show();
    }

然后打包,重命名為 old.apk

接著修改吐司的內(nèi)容:

private void showToast() {
        Toast.makeText(this, "你好啊,世界", Toast.LENGTH_LONG).show();
    }

重新打包,命名為 new.apk

下載apkpatch工具

下面是我的目錄結(jié)構(gòu):

用紅線框框住的是簽名文件,補(bǔ)丁包,舊包。

打開 cmd ->cd 到 apkpatch 的目錄,如我 F:\AndroidTools\apkpatch 目錄下,下圖我已用紅框圈。

然后輸入:

apkpatch.bat -f new.apk -t old.apk -o output -k demo.jks -p 123456 -a boby -e 123456

其中:

  • -f 是新apk的名字

  • -t 是舊apk的名字

  • -o 是輸出補(bǔ)丁的文件夾位置

  • -k 是 keystore(jks)文件的名稱

  • -p 是keystore文件的密碼

  • -a 是項(xiàng)目的別名

  • -e 別名的密碼

回車,不出現(xiàn)錯(cuò)誤,補(bǔ)丁打包成功。

打開 output 目錄,則可以看到 out.apatch 文件。

補(bǔ)丁文件上傳到后臺(tái),然后通過(guò)接口下載到 /sdcard/out.apatch 目錄下。

注意 /sdcard/out.apatch 路徑,跟 MyApplication 中的一致。

看看效果:

安裝 old.apk 包:

安裝補(bǔ)丁,接著運(yùn)行:

 

 

來(lái)自:http://www.jianshu.com/p/61336c6f750a

 

標(biāo)簽: isp 安全 代碼 電商 權(quán)限 網(wǎng)絡(luò) 移動(dòng)網(wǎng)絡(luò)

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:Java開發(fā)代碼性能優(yōu)化總結(jié)

下一篇:Web API 設(shè)計(jì)之最佳實(shí)踐