UpdateAppUtils 已適配Android6.0瘪匿、7.0、8.0
一行代碼寻馏,快速實現(xiàn)app在線下載更新A simple library for Android update app
當前文章是老版本介紹棋弥,請看最新文章
先看效果圖:
快速使用
先來看看怎樣一行代碼實現(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做的更好幢哨。
具體原理及源碼可見 代碼傳送門