android app版本升級(DownloadManager、適配6.0、7.0)
說明:
1.本文使用系統DownloadManager在通知欄更新下載進度 2.動態許可權使用第三方庫EasyPermissions(https://github.com/googlesamples/easypermissions) 3.下載完成的App安裝適配7.0 4.提示下載框(AlertDialog)是依附於Activity(UpdateActivity)的,這樣做是為了解決“進入首頁後,開啟自動檢測升級,檢測到有升級的版本就隨時彈框提示使用者,但此時使用者可能已經在操作APP進入其他頁面,怎麼保證彈框可以正常彈出?”這一問題並適配各大機型
升級流程圖
MianActivity原始碼
public class MainActivity extends BaseActivity implements EasyPermissions.PermissionCallbacks, CheckUpdateManager.RequestPermissions { //... private Version mVersion;//版本控制bean private static final int RC_EXTERNAL_STORAGE = 0x04;//儲存許可權 //...
//BaseActivity方法,返回佈局檔案 @Override protected int getContentView() { return R.layout.activity_main_ui; }
//BaseActivity方法,初始化data @Override protected void initData() { super.initData();
// 檢查版本升級 checkUpdate();
}
//CheckUpdateManager回撥介面 CheckUpdateManager封裝了網路請求 @Override public void call(Version version) { this.mVersion = version; requestExternalStorage(); }
@AfterPermissionGranted(RC_EXTERNAL_STORAGE) public void requestExternalStorage() { if (EasyPermissions.hasPermissions(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { DownloadService.startService(this, mVersion.getDownloadUrl()); } else { EasyPermissions.requestPermissions(this, "", RC_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE); } }
@Override public void onPermissionsGranted(int requestCode, List<String> perms) {
}
@Override public void onPermissionsDenied(int requestCode, List<String> perms) {
for (String perm : perms) { if (perm.equals(Manifest.permission.READ_EXTERNAL_STORAGE)) { //DialogHelper:Alerdialog分裝類 DialogHelper.getConfirmDialog(this, "溫馨提示", "需要開啟您手機的儲存許可權才能下載安裝,是否現在開啟", "去開啟", "取消", true, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); } }, null).show();
} else { //SharedPreferences中儲存授權狀態 Setting.updateLocationPermission(getApplicationContext(), false); } }
}
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); }
private void checkUpdate() { CheckUpdateManager manager = new manager.checkUpdate(); }
} CheckUpdateManager 原始碼 public class CheckUpdateManager {
private ProgressDialog mWaitDialog; private Context mContext; private boolean mIsShowDialog;
public CheckUpdateManager(Context context, boolean showWaitingDialog) { this.mContext = context; mIsShowDialog = showWaitingDialog; if (mIsShowDialog) { mWaitDialog = DialogHelper.getProgressDialog(mContext); mWaitDialog.setMessage("正在檢查中..."); mWaitDialog.setCancelable(false); mWaitDialog.setCanceledOnTouchOutside(false); } }
public void checkUpdate() { if (mIsShowDialog) { mWaitDialog.show(); } OSChinaApi.checkUpdate(new TextHttpResponseHandler() { @Override public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { if (mIsShowDialog) { DialogHelper.getMessageDialog(mContext, "網路異常,無法獲取新版本資訊").show(); } }
@Override public void onSuccess(int statusCode, Header[] headers, String responseString) { //此處省略若干程式碼 int curVersionCode = TDevice.getVersionCode(AppContext .getInstance().getPackageName()); //version:伺服器解析後實體bean if (curVersionCode < version.getCode()) { UpdateActivity.show((Activity) mContext, version); } else { if (mIsShowDialog) { DialogHelper.getMessageDialog(mContext, "已經是新版本了").show(); } }
}
@Override public void onFinish() { super.onFinish(); if (mIsShowDialog) { mWaitDialog.dismiss(); } } }); }
}
UpdateActivity原始碼 public class UpdateActivity extends BaseActivity implements View.OnClickListener, EasyPermissions.PermissionCallbacks { @Bind(R.id.tv_update_info) TextView mTextUpdateInfo; private Version mVersion; private static final int RC_EXTERNAL_STORAGE = 0x04;//儲存許可權
public static void show(Activity activity, Version version) { Intent intent = new Intent(activity, UpdateActivity.class); intent.putExtra("version", version); activity.startActivityForResult(intent, 0x01); }
@Override protected int getContentView() { return R.layout.activity_update; }
@SuppressWarnings("deprecation") @Override protected void initData() { super.initData(); setTitle(""); getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); mVersion = (Version) getIntent().getSerializableExtra("version"); mTextUpdateInfo.setText(Html.fromHtml(mVersion.getMessage())); }
@OnClick({R.id.btn_update, R.id.btn_close}) @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_update: if (!TDevice.isWifiOpen()) { DialogHelper.getConfirmDialog(this, "當前非wifi環境,是否升級?", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { requestExternalStorage(); } }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }).show(); } else { requestExternalStorage();
} break; case R.id.btn_close: finish(); break; }
}
@AfterPermissionGranted(RC_EXTERNAL_STORAGE) public void requestExternalStorage() { if (EasyPermissions.hasPermissions(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
AppUpgradeManager.getInstance(this, mVersion).startDown(); finish(); } else { EasyPermissions.requestPermissions(this, "需要開啟對您手機的儲存許可權才能下載安裝", RC_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE); } }
@Override public void onPermissionsGranted(int requestCode, List<String> perms) {
}
@Override public void onPermissionsDenied(int requestCode, List<String> perms) { //當權限視窗不能彈出式呼叫-使用者勾選了不再提醒 if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { DialogHelper.getConfirmDialog(UpdateActivity.this, "溫馨提示", "需要開啟對您手機的儲存許可權才能下載安裝,是否現在開啟", "去開啟", "取消", true, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS)); } }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }).show(); } else { finish(); } }
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); }
}
AppUpgradeManager原始碼 public class AppUpgradeManager { private volatile static AppUpgradeManager sAppUpgradeManager; private DownloadManager downloader; private Context appContext; private NotificationClickReceiver mNotificationClickReceiver; private DownloadReceiver mDownloaderReceiver; private String apkName = AppConfig.APP_NAME; //apk下載檔案的路徑 private String downloadApkPath; // 伺服器返回的版本資訊 private Version latestVersion;
public AppUpgradeManager(Context context, Version version) { appContext = context.getApplicationContext(); latestVersion = version; mDownloaderReceiver = new DownloadReceiver(); mNotificationClickReceiver = new NotificationClickReceiver(); }
public static AppUpgradeManager getInstance(Context context, Version version) { if (sAppUpgradeManager == null) { synchronized (AppUpgradeManager.class) { if (sAppUpgradeManager == null) { sAppUpgradeManager = new AppUpgradeManager(context, version); } } } return sAppUpgradeManager; }
public void startDown() { //確定apk下載的絕對路徑 String dirPath = AppConfig.DEFAULT_SAVE_FILE_PATH_PUBLIC; //AppConfig.DEFAULT_SAVE_FILE_PATH_PUBLIC=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
dirPath = dirPath.endsWith(File.separator) ? dirPath : dirPath + File.separator; downloadApkPath = dirPath + apkName;
//先檢查本地是否已經有需要升級版本的安裝包,如有就不需要再下載 File targetApkFile = new File(downloadApkPath); if (targetApkFile.exists()) { PackageManager pm = appContext.getPackageManager(); PackageInfo info = pm.getPackageArchiveInfo(downloadApkPath, PackageManager.GET_ACTIVITIES); if (info != null) { String versionCode = String.valueOf(info.versionCode); //比較已下載到本地的apk安裝包,與伺服器上apk安裝包的版本號是否一致 if (String.valueOf(latestVersion.getCode()).equals(versionCode)) { installApk(); return; } } } //要檢查本地是否有安裝包,有則刪除重新下 File apkFile = new File(downloadApkPath); if (apkFile.exists()) { apkFile.delete(); }
if (downloader == null) { downloader = (DownloadManager) appContext.getSystemService(Context.DOWNLOAD_SERVICE); } //開始下載 DownloadManager.Query query = new DownloadManager.Query(); long downloadTaskId = TDevice.getDownloadTaskId(appContext); query.setFilterById(downloadTaskId); Cursor cur = downloader.query(query); // 檢查下載任務是否已經存在 if (cur.moveToFirst()) { int columnIndex = cur.getColumnIndex(DownloadManager.COLUMN_STATUS); int status = cur.getInt(columnIndex); if (DownloadManager.STATUS_PENDING == status || DownloadManager.STATUS_RUNNING == status || DownloadManager.STATUS_PAUSED == status) { cur.close(); Toast.makeText(appContext, "更新任務已在後臺進行中,無需重複更新", Toast.LENGTH_LONG).show(); return; } } cur.close(); DownloadManager.Request task = new DownloadManager.Request(Uri.parse(latestVersion.getDownloadUrl())); //定製Notification的樣式 String title = "最新版本:" + latestVersion.getCode(); task.setTitle(title); task.setDescription("本次更新:\n1.增強系統穩定性\n2.修復已知bug"); task.setVisibleInDownloadsUi(true); //設定是否允許手機在漫遊狀態下下載 //task.setAllowedOverRoaming(false); //限定在WiFi下進行下載 //task.setAllowedNetworkTypes(Request.NETWORK_WIFI); task.setMimeType("application/vnd.android.package-archive"); // 在通知欄通知下載中和下載完成 // 下載完成後該Notification才會被顯示 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { // 3.0(11)以後才有該方法 //在下載過程中通知欄會一直顯示該下載的Notification,在下載完成後該Notification會繼續顯示,直到使用者點選該Notification或者消除該Notification task.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); } // 可能無法建立Download資料夾,如無sdcard情況,系統會預設將路徑設定為/data/data/com.android.providers.downloads/cache/xxx.apk if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { task.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkName); } // 自定義檔案路徑 //task.setDestinationUri() downloadTaskId = downloader.enqueue(task); //TDevice SharedPreferences封裝類 TDevice.saveDownloadTaskId(appContext, downloadTaskId); //註冊下載完成廣播 appContext.registerReceiver(mDownloaderReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); appContext.registerReceiver(mNotificationClickReceiver, new IntentFilter(DownloadManager.ACTION_NOTIFICATION_CLICKED));
}
private void installApk() { if (TextUtils.isEmpty(downloadApkPath)) { Toast.makeText(appContext, "APP安裝檔案不存在或已損壞", Toast.LENGTH_LONG).show(); return; } File apkFile = new File(Uri.parse(downloadApkPath).getPath()); if (!apkFile.exists()) { Toast.makeText(appContext, "APP安裝檔案不存在或已損壞", Toast.LENGTH_LONG).show(); return; }
Intent intent = new Intent(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(appContext, "net.xxx.app.provider", apkFile); intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); } else { intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } appContext.startActivity(intent); }
/** * 下載完成的廣播 */ class DownloadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (downloader == null) { return; } long completeId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0); long downloadTaskId = TDevice.getDownloadTaskId(context); if (completeId != downloadTaskId) { return; }
DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadTaskId); Cursor cur = downloader.query(query); if (!cur.moveToFirst()) { return; }
int columnIndex = cur.getColumnIndex(DownloadManager.COLUMN_STATUS); if (DownloadManager.STATUS_SUCCESSFUL == cur.getInt(columnIndex)) { installApk(); } else { Toast.makeText(appContext, "下載App最新版本失敗!", Toast.LENGTH_LONG).show(); } // 下載任務已經完成,清除 TDevice.removeDownloadTaskId(context); cur.close(); }
}
/** * 點選通知欄下載專案,下載完成前點選都會進來,下載完成後點選不會進來。 */ public class NotificationClickReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { long[] completeIds = intent.getLongArrayExtra( DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS); //正在下載的任務ID long downloadTaskId = TDevice.getDownloadTaskId(context); if (completeIds == null || completeIds.length <= 0) { openDownloadsPage(appContext); return; }
for (long completeId : completeIds) { if (completeId == downloadTaskId) { openDownloadsPage(appContext); break; } } }
/** * Open the Activity which shows a list of all downloads. * * @param context 上下文 */ private void openDownloadsPage(Context context) { Intent pageView = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); pageView.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(pageView); } } } Android7.0適配 1.在manifest檔案application標籤中中加入
<provider android:name="android.support.v4.content.FileProvider" android:authorities="net.xxx.app.provider" /**注意引數統一性 FileProvider.getUriForFile(appContext, "net.xxx.app.provider", apkFile);*/ android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider> 2.res下的xml資料夾中的provider_paths原始碼
<?xml version="1.0" encoding="utf-8"?> <paths> <!--path:需要臨時授權訪問的路徑(.代表所有路徑) name:就是你給這個訪問路徑起個名字--> <external-path name="external_files" path="." /> </paths>