Android 強升邏輯和實現

「強制升級」會中斷用戶操做,阻礙正常使用,看似是一個不光彩的行爲,可是智者千慮必有一失,咱們沒法保證 App 的正確性,在某些緊急狀況下,強制升級仍是很是必要的,並且接入的時間越早越好。html

有贊微商城 App 早期版本只提供了一個更新提示的對話框,並不會強制用戶更新。隨着後端網關升級,一些老的服務須要下線,可是新版本到達率並不理想,繼續維護老接口帶來必定成本,並且新功能也沒法觸及用戶。java

爲了提高版本到達率,咱們從新梳理了強制升級的邏輯。android

升級過程當中首先要保證 apk 的下載成功率,下載完成以後要及時彈出安裝頁面,爲了防止下載失敗,也要提供市場下載的選項,這樣必定程度上也能保證升級以後渠道的一致性。git

  • 更新對話框須要展現標題、內容和動做按鈕。github

提示升級

強制升級

  • 狀態欄下載通知須要展現應用名字和描述。後端

狀態欄通知

構造參數

業務方須要提供的參數:app

public class AppUpdater {
    public static class Builder {
        private Context context;

        private String url;     // apk 下載連接
        private String title;   // 更新對話框 title
        private String content; // 更新內容
        private boolean force;  // 是否強制更新

        private String app; // app 名字
        private String description; // app 描述
    }

    private AppUpdater(final Builder builder) {
        this.builder = builder;
    }

    public void update() {
        Intent intent = new Intent(builder.context, DownloadActivity.class);
        intent.putExtra(DownloadActivity.EXTRA_STRING_APP_NAME,  builder.app);
        intent.putExtra(DownloadActivity.EXTRA_STRING_URL, builder.url);
        intent.putExtra(DownloadActivity.EXTRA_STRING_TITLE, builder.title);
        intent.putExtra(DownloadActivity.EXTRA_STRING_CONTENT, builder.content);
        intent.putExtra(DownloadActivity.EXTRA_STRING_DESCRIPTION,  builder.description);
        intent.putExtra(DownloadActivity.EXTRA_BOOLEAN_FORCE, builder.force);
        builder.context.startActivity(intent);
    }

使用 DownloadManager 下載 apk

爲了提升下載成功率,咱們使用了系統 Service - DownloadManager,由於是獨立進程,不會增長 App 佔用的系統開銷。ide

private void downloadApk() {
    if (TextUtils.isEmpty(downloadUrl)) return;

    // check dir
    File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
    if (!path.exists() && !path.mkdirs()) {
        Toast.makeText(this, String.format(getString(R.string.app_updater_dir_not_found),
                path.getPath()), Toast.LENGTH_SHORT).show();
        return;
    }

    /** construct request */
    final DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE
            | DownloadManager.Request.NETWORK_WIFI);
    request.setAllowedOverRoaming(false);

    request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,
            appName + ".apk");


    if (!TextUtils.isEmpty(appName)) {
        request.setTitle(appName);
    }

    if (!TextUtils.isEmpty(description)) {
        request.setDescription(description);
    } else {
        request.setDescription(downloadUrl);
    }

    /** start downloading */
    downloadId = downloadManager.enqueue(request);
    setStatus(STATUS_DOWNLOADING);
}

註冊監聽下載完成的 Receiver

咱們經過一個全局的 Receiver 來接收下載完成的廣播,這樣即便 App 進程被殺死,依然能夠安裝界面。gradle

<receiver
    android:name=".DownloadReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
    </intent-filter>
</receiver>

接收到廣播以後,彈出安裝界面。ui

private void installApk(final Context context, final Uri uri) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    Uri apkUri = uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        apkUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider",
                new File(uri.getPath()));
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
    intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
    context.startActivity(intent);
}

注意此處有坑,在 SDK >= 24 的系統中,Intent 不容許攜帶 file:// 格式的數據,只能經過 provider 的形式共享數據。

因此咱們還須要註冊一個 FileProvider

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

${applicationId}$AndroidManifest.xml 中的佔位符,gradle 會進行替換。

android:authorities="${applicationId}.provider"

對應 Java 代碼:

FileProvider.getUriForFile(context, context.getPackageName() + ".provider", new File(uri.getPath()))

注意:Java 代碼中 getPackageName() 的返回值是 ApplicationId

關於 package name 和 application id 的區別,能夠參考 http://blog.csdn.net/feelang/...

完整版代碼:https://github.com/LyndonChin...

DownloadManager

原文地址:https://youzanmobile.github.i...

相關文章
相關標籤/搜索