1喉刘、需求:
版本更新瞧柔,一個經(jīng)常用到的功能。寫一個工具類饱搏,拿去直接用非剃。
2、實現(xiàn)思路:
- 1推沸、請求數(shù)據(jù) 拿到后臺的apk的版本號备绽、版本名、更新內(nèi)容鬓催、下載地址(可能還會包括是否強制更新的標志位)肺素。這里有一點需要注意,如果有強制更新的概念宇驾,這時候請求數(shù)據(jù)的時候要給服務(wù)器你當前的版本號倍靡。如果你現(xiàn)在的版本號是1 ,用戶很久沒有操作课舍,期間版本更新了2(強制更新)塌西、3(不強制更新)。這時候用戶打開版本號為1的版本筝尾,應(yīng)該是要強制更新的捡需。
- 2、和本地的apk版本號對比時候需要更新
- 3筹淫、這里用戶可能會忽略此版本站辉,這時候我要存儲一個版本號,用戶忽略的版本號,如果用戶的是主動更新的饰剥,都要提示殊霞。如果不是主動更新的判斷一下,判斷一下汰蓉。
- 4绷蹲、在步驟2以后 如果需要更新,先判斷步驟3里邊存儲的版本號古沥,比較大小瘸右, 如果用戶忽略更新這個版本,那就不更新了岩齿;如果用戶沒有忽略過此版本太颤,繼續(xù)往下
- 5、這時候給用戶一個提示盹沈,展示內(nèi)容是有新版本是否更新龄章,更新內(nèi)容
- 5.1 這里可能涉及到一個強制更新,如果是強制更新的乞封,那就要求做裙,用戶沒有更新,就退出程序肃晚;不是強制更新的锚贱,可以忽略此版本
- 6、啟動下載服務(wù)
- 7关串、下載完成之后的自動安裝
- 8拧廊、不要忘記兼容7.0 在res/xml文件中添加 file_paths.xml 文件,并且在AndroidManifest.xml文件中添加
3晋修、代碼實現(xiàn)
這里使用DownLoadManager來做下載吧碾,在UpdateAppService類中。
在UpdateAppUtils檢測是否要下載
UpdateAppUtils.java
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import com.example.lql.updateappdemo.message.EventMessage;
import com.example.lql.updateappdemo.service.UpdateAppService;
import com.example.lql.updateappdemo.utils.FinalData;
import com.example.lql.updateappdemo.utils.PreferenceUtils;
import com.example.lql.updateappdemo.utils.T;
import org.greenrobot.eventbus.EventBus;
import java.lang.ref.WeakReference;
/**
* 類描述:版本更新工具類<br>
* 這里使用DownLoadManager來做下載墓卦,在UpdateAppService類中<br>
* 在UpdateAppUtils檢測是否要下載<br>
* 邏輯關(guān)系說明:<br>
* 1倦春、請求數(shù)據(jù) 拿到后臺的apk的版本號、版本名落剪、更新內(nèi)容睁本、下載地址(可能還會包括是否強制更新的標志位)<br>
* 這里有一點需要注意,如果有強制更新的概念忠怖,這時候請求數(shù)據(jù)的時候要給服務(wù)器你當前的版本號呢堰,<br>
* 如果你現(xiàn)在的版本號是1 ,用戶很久沒有操作脑又,期間版本更新了2(強制更新)暮胧、3(不強制更新)。這時候用戶打開版本號為1的版本问麸,應(yīng)該是要強制更新的往衷。<br>
* 2、和本地的apk版本號對比時候需要更新<br>
* 3严卖、這里用戶可能會忽略此版本席舍,這時候我要存儲一個版本號,用戶忽略的版本號哮笆,如果用戶的是主動更新的来颤,都要提示。如果不是主動更新的判斷一下稠肘,判斷一下福铅。<br>
* 4、在步驟2以后 如果需要更新项阴,先判斷步驟3里邊存儲的版本號滑黔,比較大小,<br>
* 如果用戶忽略更新這個版本环揽,那就不更新了略荡;如果用戶沒有忽略過此版本,繼續(xù)往下<br>
* 5歉胶、這時候給用戶一個提示汛兜,展示內(nèi)容是有新版本是否更新,更新內(nèi)容<br>
* 5.1 這里可能涉及到一個強制更新通今,如果是強制更新的粥谬,那就要求,用戶沒有更新衡创,就退出程序帝嗡,不是強制更新的,可以忽略此版本<br>
* 6璃氢、啟動下載服務(wù)<br>
* 7哟玷、下載完成之后的自動安裝<br>
* 8、不要忘記兼容7.0 在res/xml文件中添加 file_paths.xml 文件一也,并且在AndroidManifest.xml文件中添加
* 使用說明:<br>
* 在外部直接調(diào)用UpdateApp()方法<br>
* 作 者:李清林<br>
* 時 間:2017.5.12<br>
* 更新時間:2018.5.2<br>
* 修改備注:增加申請權(quán)限功能<br>
* 更新時間:2018.7.23<br>
* 修改備注:兼容8.0<br>
*/
public class UpdateAppUtils {
/**
* 當前版本號
*/
private static int mVersionCode = 0;
private static Activity mActivity;
private static Fragment mFragment;
/**
* 下載地址
*/
private static String mDownloadUrl = "";
/**
* 寫SD卡權(quán)限
*/
public static int REQUEST_PERMISSION_SDCARD_6_0 = 0x56;
/**
* 允許安裝未知來源權(quán)限
*/
public static int REQUEST_PERMISSION_SDCARD_8_0 = 0x58;
/**
* 去設(shè)置頁面
*/
public static int REQUEST_PERMISSION_SDCARD_SETTING = 0x57;
/**
* 服務(wù)器的版本號碼
*/
private static int serviceVersionCode = 0;
/**
* 服務(wù)器的版本號名稱
*/
private static String serviceVersionName = "";
/**
* 是否強制更新
*/
private static boolean IsUpdate = false;
/**
* 更新說明
*/
private static String content = "";
public UpdateAppUtils() {
}
/**
* @param activity 上下文
* @param newVersionCode 服務(wù)器的版本號
* @param newVersionName 服務(wù)器的版本名稱
* @param content 更新了的內(nèi)容
* @param downUrl 下載地址
* @param IsUpdate 是否強制更新
* @param IsToast 是否提示用戶當前已經(jīng)是最新版本
*/
public static void UpdateApp(Activity activity, Fragment fragment, int newVersionCode, String newVersionName,
String content, String downUrl, boolean IsUpdate, boolean IsToast) {
UpdateAppUtils.mActivity = activity;
UpdateAppUtils.mFragment = fragment;
UpdateAppUtils.mDownloadUrl = downUrl;
UpdateAppUtils.serviceVersionCode = newVersionCode;
UpdateAppUtils.serviceVersionName = newVersionName;
UpdateAppUtils.IsUpdate = IsUpdate;
UpdateAppUtils.content = content;
//首先拿到當前的版本號和版本名
try {
UpdateAppUtils.mActivity = activity;
PackageManager pm = mActivity.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mActivity.getPackageName(), 0);
mVersionCode = pi.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (mVersionCode < serviceVersionCode) {
//第2步驟
if (PreferenceUtils.getInt(FinalData.VERSIONCODE, 0) < serviceVersionCode || IsToast) {
//第3步驟
//這時候要去更新题诵,展示下載的對話框
showDownLoadDialog();
}
} else {
if (IsToast) {
T.shortToast(mActivity, "當前已是最新版本");
}
}
}
/**
* 下載對話框叫胖,并且請求權(quán)限
*/
private static void showDownLoadDialog() {
AlertDialog dialog = new AlertDialog.Builder(mActivity).
setCancelable(false).
setTitle("更新到 " + serviceVersionName).
setMessage(content).
setPositiveButton("下載", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {//第6步驟绷跑,下載
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkPermission();
} else {
downLoad();
}
} else {
downLoad();
}
}
}).
setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//這里涉及到下載的強制更新,是不是強制更新 強制更新吩谦,點取消按鈕芭挽,退出程序
if (IsUpdate) {
T.shortToast(mActivity, "此版本需要更新,程序即將退出");
MyHandler myHandler = new MyHandler(new UpdateAppUtils());
myHandler.sendEmptyMessageDelayed(0, 1000 * 3);
} else {
PreferenceUtils.setInt(FinalData.VERSIONCODE, serviceVersionCode);
dialog.dismiss();
}
}
}).
create();
dialog.show();
}
/**
* 檢查6.0權(quán)限
*/
private static void checkPermission() {
if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
checkInstallPermission();
} else {
//有權(quán)限去下載
downLoad();
}
} else {
// 該方法在用戶上次拒絕后調(diào)用,因為已經(jīng)拒絕了這次你還要申請授權(quán)你得給用戶解釋一波
// 一般建議彈個對話框告訴用戶 該方法在6.0之前的版本永遠返回的是fasle
if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, Manifest.permission.READ_EXTERNAL_STORAGE)) {
getPermissionDialog(0);
} else {// 申請授權(quán)
if (mFragment != null) {
mFragment.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_SDCARD_6_0);
} else {
ActivityCompat.requestPermissions(mActivity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_SDCARD_6_0);
}
}
}
}
/**
* 檢查8.0權(quán)限
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private static void checkInstallPermission() {
boolean b = mActivity.getPackageManager().canRequestPackageInstalls();
if (b) {
downLoad();
} else {
getPermissionDialog(1);
}
}
/**
* 正式去下載
*/
private static void downLoad() {
T.shortToast(mActivity, "正在下載...");
//如果要更新缴挖,并且是強制更新映屋,這里發(fā)一個事件,不讓其他的頁面做操作了
EventBus.getDefault().post(new EventMessage(EventMessage.CheckApp, true));
new Thread(new Runnable() {
@Override
public void run() {
//啟動服務(wù)
Intent service = new Intent(mActivity, UpdateAppService.class);
service.putExtra("downLoadUrl", mDownloadUrl);
mActivity.startService(service);
}
}).start();
}
/**
* 申請權(quán)限的對話框
*
* @param type 0:6.0 1:8.0
*/
private static void getPermissionDialog(int type) {
String message = "更新軟件需要您允許我們獲取您的數(shù)據(jù)讀寫權(quán)限,否則將無法更新";
if (type == 1) {
message = "為了正常升級APP倔毙,請點擊設(shè)置-高級設(shè)置-允許安裝未知來源應(yīng)用乙濒,本功能只限用于APP版本升級";
}
AlertDialog dialog = new AlertDialog.Builder(mActivity).
setCancelable(false).
setTitle("權(quán)限提醒").
setMessage(message).
setPositiveButton("權(quán)限設(shè)置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {//第6步驟,下載
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", mActivity.getPackageName(), null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//注意這個是8.0新API,直接跳轉(zhuǎn)到允許安裝位置來源的頁面
intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, uri);
} else {
intent.setData(uri);
}
if (mFragment != null) {
mFragment.startActivityForResult(intent, REQUEST_PERMISSION_SDCARD_SETTING);
} else {
mActivity.startActivityForResult(intent, REQUEST_PERMISSION_SDCARD_SETTING);
}
}
}).
setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//這里涉及到下載的強制更新傻丝,是不是強制更新 強制更新,點取消按鈕滤愕,退出程序
if (IsUpdate) {
T.shortToast(mActivity, "此版本需要更新间影,程序即將退出");
MyHandler myHandler = new MyHandler(new UpdateAppUtils());
myHandler.sendEmptyMessageDelayed(0, 3000);
} else {
PreferenceUtils.setInt(FinalData.VERSIONCODE, serviceVersionCode);
dialog.dismiss();
}
}
}).
create();
dialog.show();
}
/**
* 申請權(quán)限返回結(jié)果時調(diào)用,用戶是否同意
*
* @param requestCode 之前申請權(quán)限的請求碼
* @param permissions 申請的權(quán)限
* @param grantResults 依次申請的結(jié)果
*/
public static void onActRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults, Activity activity, Fragment fragment) {
mActivity = activity;
mFragment = fragment;
if (requestCode == REQUEST_PERMISSION_SDCARD_6_0) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
checkInstallPermission();
} else {
//有權(quán)限去下載
downLoad();
}
} else {
getPermissionDialog(0);
}
} else if (requestCode == REQUEST_PERMISSION_SDCARD_8_0) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
downLoad();
} else {
getPermissionDialog(1);
}
} else if (requestCode == REQUEST_PERMISSION_SDCARD_SETTING) {
//設(shè)置頁面
checkPermission();
}
}
static class MyHandler extends Handler {
WeakReference<UpdateAppUtils> mWeakReference;
public MyHandler(UpdateAppUtils mUpdateAppUtils) {
mWeakReference = new WeakReference<UpdateAppUtils>(mUpdateAppUtils);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
exitApp();
}
}
/**
* 這里使用EventBus像Activity發(fā)送消息,當然你也可以使用廣播
*/
private static void exitApp() {
EventBus.getDefault().post(new EventMessage(EventMessage.Exitapp));
}
}
UpdateAppService.java
import android.app.DownloadManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import com.example.lql.updateappdemo.R;
import com.example.lql.updateappdemo.utils.T;
import java.io.File;
/**
* 類描述:下載服務(wù)
* 作 者:李清林
* 時 間:
* 修改備注:兼容7.0
*/
public class UpdateAppService extends Service {
public UpdateAppService() {
}
/**
* 安卓系統(tǒng)下載類
**/
DownloadManager manager;
/**
* 接收下載完的廣播
**/
DownloadCompleteReceiver receiver;
/**
* 初始化下載器
**/
private void initDownManager(String downLoadUrl) {
manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
receiver = new DownloadCompleteReceiver();
if (TextUtils.isEmpty(downLoadUrl)) {
T.shortToast(UpdateAppService.this, "下載地址為空");
return;
}
//設(shè)置下載地址
Uri parse = Uri.parse(downLoadUrl);
DownloadManager.Request down = new DownloadManager.Request(parse);
down.setTitle(getResources().getString(R.string.app_name) + ".apk");
// 設(shè)置允許使用的網(wǎng)絡(luò)類型茄茁,這里是移動網(wǎng)絡(luò)和wifi都可以
down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
// 下載時魂贬,通知欄顯示途中
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
down.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
}
// 顯示下載界面
down.setVisibleInDownloadsUi(true);
// 設(shè)置下載后文件存放的位置
String apkName = parse.getLastPathSegment();
down.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, apkName);
// 將下載請求放入隊列
manager.enqueue(down);
//注冊下載廣播
registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String downLoadUrl = intent.getStringExtra("downLoadUrl");
// 調(diào)用下載
initDownManager(downLoadUrl);
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
// 注銷下載廣播
if (receiver != null) {
unregisterReceiver(receiver);
}
super.onDestroy();
}
/**
* 接受下載完成后的intent
*/
class DownloadCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
DownloadManager.Query query = new DownloadManager.Query();
// 在廣播中取出下載任務(wù)的id
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
query.setFilterById(id);
Cursor c = manager.query(query);
if (c.moveToFirst()) {
int fileUriIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String fileUri = c.getString(fileUriIdx);
String fileName = null;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
if (fileUri != null) {
fileName = Uri.parse(fileUri).getPath();
}
} else {
//Android 7.0以上的方式:請求獲取寫入權(quán)限,這一步報錯
int fileNameIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
fileName = c.getString(fileNameIdx);
}
if (null != fileName && !TextUtils.isEmpty(fileName)) {
install1(context, fileName);
}
//停止服務(wù)并關(guān)閉廣播
UpdateAppService.this.stopSelf();
}
}
}
private boolean install1(Context context, String filePath) {
Intent i = new Intent(Intent.ACTION_VIEW);
File file = new File(filePath);
if (file != null && file.length() > 0 && file.exists() && file.isFile()) {
//判斷是否是AndroidN以及更高的版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
i.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
i.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(i);
return true;
}
return false;
}
}
}
最后別忘了在清單文件中注冊服務(wù)裙顽。
AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lql.updateappdemo">
<!--讀存儲的權(quán)限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--寫存儲的權(quán)限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<!--8.0未知來源的應(yīng)用權(quán)限 更新App-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".application.MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<!--兼容7.x-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.lql.updateappdemo.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity android:name=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.UpdateAppService"
android:enabled="true"></service>
</application>
</manifest>
4、更新內(nèi)容(2018.7.23):
這次更新為了兼容android8.0。在8.0中增加了允許安裝位置來源應(yīng)用的權(quán)限娱两。使用時需要首先在AndroidManifest文件中添加響應(yīng)權(quán)限阻荒,然后在去檢查權(quán)限。整體過程和android6.0動態(tài)申請權(quán)限的過程相似漩怎,但是在具體代碼中有所區(qū)別萝嘁。具體使用請查看代碼UpdateAppUtils .checkInstallPermission()方法。這里提一句扬卷,在申請權(quán)限的時候牙言,不會像6.0權(quán)限那樣有系統(tǒng)提示,這里需要自己去寫一個dialog去提示用戶怪得。并且跳轉(zhuǎn)到設(shè)置頁面中手動開啟咱枉。具體跳轉(zhuǎn)頁面邏輯請查看UpdateAppUtils.getPermissionDialog()方法卑硫。可以直接跳轉(zhuǎn)到具體頁面蚕断。