前言
1. 用户使用 App 的时候升级提醒有两种方式获得:
- 通过应用市场获取;
- 打开应用之后提醒用户更新升级。
2. 更新操作一般是在用户点击了升级按钮之后开始执行的,这里的升级操作也分为两种形式:
- 一般升级
- 强制升级
3. App 升级操作
-
应用市场的 app 升级
在 App Store 中升级需要为 App Store 上传新版 App ,我们在新版本完成之后都会上传到 App Store 中,在审核完成之后就相当于完成了这个应用市场的发布了,也就是发布上线了。这时候如果用户安装了这个应用市场,那么就能看到我们的 App 有新版本的升级提醒。 -
应用内升级
在应用内升级主要是通过调用服务器端接口获取应用的升级信息,然后通过获取的服务器升级应用信息与本地的 App 版本比对,若服务器下发的最新的 App 版本高于本地的版本号,则说明有新版本发布,那么我们就可以执行更新操作了,否则忽略掉即可。
实战
1. 权限申请
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
...
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.AppUpdate"
android:name=".MyApp"
tools:ignore="UnusedAttribute">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.hkt.appupdate.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"/>
</provider>
</application>
2. network_security_config.xml
<?xml version ="1.0" encoding ="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
3. filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external" path="."/>
</paths>
4. 服务端
json 文件:http://59.110.162.30/app_updater_version.json
5. 封装 updatelib, 对外的统一接口
XDown 为程序入口,它提供以下方法:
- pause() 暂停任务
- start() 开始任务
- stopTask() 停止任务 ,如果你的apk更新不是在 activity 使用,建议在app退出的时候,使用该方法,防止内存泄漏
- stopTaskAndDeleteCache() 停止任务,并删除已文件和数据库,当任务失败时,可以使用
- isTaskExists() 任务是否存在
- isRunning() 是否正在下载
- updateListener() 从后台退回来,如果任务正在下载,直接更新接口就可以了,UI就不会乱了
- deleteCacheAndStart() 当任务失败时,可以用这个把缓存文件和数据删了
/**
* Created on 2021/12/29 9:41
* 对外的统一接口
* @author Gong Youqiang
*/
public class XDown {
public static CheckRequest checkWith(Context context){
return CheckRequest.get(context);
}
public static RequestManager with(Context context){
XDBManager.getInstance().config(context.getApplicationContext());
return RequestManager.getInstance().with(context);
}
/**
* 暂停
*/
public static void pause(){
if (RequestManager.getInstance().mTask != null) {
RequestManager.getInstance().mTask.pause();
}
}
/**
* 开始
*/
public static void start(){
if (RequestManager.getInstance().mTask != null) {
RequestManager.getInstance().mTask.start();
}
}
/**
* 暂停任务,虽然已经监听activity的生命周期了,但是有些特殊情况还需要用户自己去判断
*/
public static void stopTask(){
if (RequestManager.getInstance().mTask != null) {
RequestManager.getInstance().mTask.pause();
RequestManager.getInstance().mTask = null;
}
}
/**
* 暂停任务,和删除缓存
*/
public static void stopTaskAndDeleteCache() {
if (RequestManager.getInstance().mTask != null) {
RequestManager.getInstance().mTask.pause();
RequestManager.getInstance().mTask.deleteCache();
RequestManager.getInstance().mTask = null;
}
}
/**
* 是否存在
* @return
*/
public static boolean isTaskExists(){
return RequestManager.getInstance().mTask != null;
}
/**
* 是否正在下载
* @return
*/
public static boolean isRunning(){
if (RequestManager.getInstance().mTask != null) {
return RequestManager.getInstance().mTask.isRunning();
}
return false;
}
/**
* 当存在时,可以直接跟新接口就行了
* @param listener
*/
public static void updateListener(BaseListener listener){
if (RequestManager.getInstance().mTask != null) {
RequestManager.getInstance().mTask.updateListener(listener);
}
}
/**
* 当存在错误下载不了,尝试把缓存文件和数据库删除了使用
*/
public static void deleteCacheAndStart(){
stopTaskAndDeleteCache();
start();
}
}
6. 使用
/**
* Created on 2022/2/11 10:24
*
* @author Gong Youqiang
*/
public class UpdateApk {
private static final String TAG = "UpdateApk";
private Activity mContext;
private String mPath;
public UpdateApk(Activity context) {
mContext = context;
mPath = Environment.getExternalStorageDirectory().getAbsolutePath();
MyApp.HANDLER.postDelayed(() -> {
if (NetUtils.isNetworkConnected()) {
XDown.checkWith(context)
.url("http://59.110.162.30/app_updater_version.json")
.get()
.listener(new CheckListener<CheckBean>() {
@Override
public void onCheck(CheckBean data) {
checkAndDown(data);
}
@Override
public void onFail(String errorMsg) {
Log.d(TAG, "onFail: " + errorMsg);
}
}).check();
}
},500);
}
private void checkAndDown(CheckBean bean) {
if (bean != null) {
if (Integer.parseInt(bean.getVersionCode()) > PackageUtils.getVersionCode(mContext) ) {
final CusDialog dialog = new CusDialog.Builder()
.setContext(mContext)
.setLayoutId(R.layout.update_layout)
.showAlphaBg(true)
.setWidth(480)
.setHeight(620)
.builder();
String[] split = bean.getContent().split("\\|");
StringBuffer sb = new StringBuffer();
for (int i = 0; i < split.length; i++) {
sb.append(split[i]);
sb.append("\n");
}
dialog.setTextView(R.id.update_info, sb.toString());
dialog.setTextView(R.id.update_version, mContext.getString(R.string.update_version, bean.getTitle()));
dialog.setDismissByid(R.id.update_dismiss);
final TextView updateBtn = dialog.getViewbyId(R.id.tv_update);
final NumberProgressBar progressBar = dialog.getViewbyId(R.id.number_progress_bar);
dialog.setOnClickListener(R.id.tv_update, new View.OnClickListener() {
@Override
public void onClick(View v) {
v.setEnabled(false);
XDown.with(mContext)
.url(bean.getUrl())
.threadCount(3)
.reFreshTime(500)
.filePath(mPath)
.listener(new TaskListener() {
@Override
public void onSuccess(String filePath, String md5Msg) {
installApk(mContext,filePath);
dialog.dismiss();
}
@Override
public void onDownloading(XBean bean) {
int progress = (int) bean.progress;
updateBtn.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(progress);
}
@Override
public void onFail(String errorMsg) {
Log.d(TAG, "onFail: " + errorMsg);
dialog.dismiss();
}
}).down();
}
});
}
}
}
public static void installApk(Activity context, String filePath){
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
File apkFile = new File(filePath);
Uri uri = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile);
} else {
uri = Uri.fromFile(apkFile);
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intent);
}
}
demo 传送门:AppUpdate.rar