版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zsp_android_com/article/details/81937537
前言
更新分全量更新和增量更新(参考)。
库
UpdateAppDemo
引
FileDownloader
说明
下载可用系统自带DownloadManager。该类API level 9后出现,已处理下载失败、重新下载等。下载过程交系统负责,不需过多处理。国内某些机型阉割自带DownloadManager时可自实现下载类,也可如上引FileDownloader。
使用
依赖
implementation 'com.liulishuo.filedownloader:library:1.7.5'
权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
update
AppDownload
package update;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import com.liulishuo.filedownloader.BaseDownloadTask;
import com.liulishuo.filedownloader.FileDownloadListener;
import com.liulishuo.filedownloader.FileDownloader;
import util.LogUtils;
import util.ToastUtils;
/**
* @decs: AppDownload
* @date: 2018/8/21 21:09
* @version: v 1.0
*/
class AppDownload {
/**
* 地址
*/
public static String path;
/**
* 浏览器下载
*
* @param context 上下文
* @param url url
*/
public static void downloadByBrowser(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);
}
/**
* 应用内下载
*
* @param context 上下文
* @param url url
* @param versionName 版本名
*/
public static void downloadInApp(final Context context, String url, final String versionName) {
path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/dfs/update/" + versionName + ".apk";
// 注册定制组件
FileDownloader.setup(context);
// 执行
FileDownloader.getImpl().create(url)
.setPath(path)
.setListener(new FileDownloadListener() {
@Override
protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) {
}
@Override
protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) {
}
@Override
protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {
sendBroadcast(context, (int) (soFarBytes * 100.0 / totalBytes), versionName);
LogUtils.e((int) (soFarBytes * 100.0 / totalBytes));
}
@Override
protected void blockComplete(BaseDownloadTask task) {
}
@Override
protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) {
}
@Override
protected void completed(BaseDownloadTask task) {
sendBroadcast(context, 100, versionName);
ToastUtils.shortShow("下载完成");
}
@Override
protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) {
}
@Override
protected void error(BaseDownloadTask task, Throwable e) {
ToastUtils.shortShow("下载出错");
}
@Override
protected void warn(BaseDownloadTask task) {
}
}).start();
}
/**
* 发送广播
*
* @param context 上下文
* @param progress 进度
* @param versionName 版本名
*/
private static void sendBroadcast(Context context, int progress, String versionName) {
Intent intent = new Intent("com.self.zsp.dfs.AppUpdate");
intent.putExtra("progress", progress);
intent.putExtra("title", versionName);
context.sendBroadcast(intent);
}
}
AppUpdate
package update;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.text.TextUtils;
import util.LogUtils;
/**
* @decs: AppUpdate
* @date: 2018/8/21 21:24
* @version: v 1.0
*/
public class AppUpdate {
public static final int CHECK_BY_VERSION_NAME = 1001;
private static final int CHECK_BY_VERSION_CODE = 1002;
public static final int DOWNLOAD_BY_APP = 1003;
private 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 versionCode = 0;
private String apkPath = "";
private String versionName = "";
/**
* 强更否
*/
private boolean isForce = false;
private int localVersionCode = 0;
private String localVersionName = "";
/**
* false不适配7.0(默true)
*/
public static boolean needFitAndroidN = true;
public static boolean showNotification = true;
private String updateInfo = "";
public AppUpdate fitAndroidN(boolean fitAndroidN) {
AppUpdate.needFitAndroidN = fitAndroidN;
return this;
}
private AppUpdate(Activity activity) {
this.activity = activity;
localVersionInfo(activity);
}
public static AppUpdate from(Activity activity) {
return new AppUpdate(activity);
}
public AppUpdate checkBy(int checkBy) {
this.checkBy = checkBy;
return this;
}
public AppUpdate apkPath(String apkPath) {
this.apkPath = apkPath;
return this;
}
public AppUpdate downloadBy(int downloadBy) {
this.downloadBy = downloadBy;
return this;
}
public AppUpdate showNotification(boolean showNotification) {
AppUpdate.showNotification = showNotification;
return this;
}
public AppUpdate updateInfo(String updateInfo) {
this.updateInfo = updateInfo;
return this;
}
public AppUpdate versionCode(int versionCode) {
this.versionCode = versionCode;
return this;
}
public AppUpdate versionName(String versionName) {
this.versionName = versionName;
return this;
}
public AppUpdate isForce(boolean isForce) {
this.isForce = isForce;
return this;
}
/**
* 本地版信息
*
* @param context 上下文
*/
private void localVersionInfo(Context context) {
PackageManager manager = context.getPackageManager();
try {
PackageInfo info = manager.getPackageInfo(context.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 (versionCode > localVersionCode) {
updateExecute();
} else {
LogUtils.e("当前为最新版" + versionCode + "/" + versionName);
}
break;
case CHECK_BY_VERSION_NAME:
if (!versionName.equals(localVersionName)) {
updateExecute();
} else {
LogUtils.e("当前版本最新" + versionCode + "/" + versionName);
}
break;
default:
break;
}
}
private void updateExecute() {
AppUpdateDialog dialog = new AppUpdateDialog(activity, new Callback() {
@Override
public void callback(int position) {
switch (position) {
// cancel
case 0:
if (isForce) {
System.exit(0);
}
break;
// sure
case 1:
if (downloadBy == DOWNLOAD_BY_APP) {
if (isWifiConnect(activity)) {
AppDownload.downloadInApp(activity, apkPath, versionName);
} else {
new AppUpdateDialog(activity, new Callback() {
@Override
public void callback(int position) {
if (position == 1) {
AppDownload.downloadByBrowser(activity, apkPath);
} else {
if (isForce) {
activity.finish();
}
}
}
}).setContent("目前手机非WIFI\n继续下载更新吗?").show();
}
} else if (downloadBy == DOWNLOAD_BY_BROWSER) {
AppDownload.downloadByBrowser(activity, apkPath);
}
break;
default:
break;
}
}
});
String content = "发现新版本" + versionName + "\n下载更新吗?";
if (!TextUtils.isEmpty(updateInfo)) {
content = "发现新版本" + versionName + "下载更新吗?\n\n" + updateInfo;
}
dialog.setContent(content);
dialog.setCancelable(false);
dialog.show();
}
/**
* WIFI连状
*/
private static boolean isWifiConnect(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
}
return false;
}
}
AppUpdateDialog
package update;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.self.zsp.dfs.R;
/**
* @decs: ConfirmDialog
* @date: 2018/8/21 21:44
* @version: v 1.0
*/
public class AppUpdateDialog extends Dialog {
private TextView content;
private Callback callback;
/**
* constructor
*
* @param context 上下文
* @param callback 回调
*/
AppUpdateDialog(Context context, Callback callback) {
super(context, R.style.AppUpdateDialog);
this.callback = callback;
setCustomDialog();
}
private void setCustomDialog() {
@SuppressLint("InflateParams") View view = LayoutInflater.from(getContext()).inflate(R.layout.app_update_dialog, null);
TextView tvSure = view.findViewById(R.id.tvAppUpdateDialogSure);
TextView tvCancel = view.findViewById(R.id.tvAppUpdateDialogCancel);
content = view.findViewById(R.id.tvAppUpdateDialogTitle);
tvSure.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callback.callback(1);
AppUpdateDialog.this.cancel();
}
});
tvCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callback.callback(0);
AppUpdateDialog.this.cancel();
}
});
super.setContentView(view);
}
public AppUpdateDialog setContent(String s) {
content.setText(s);
return this;
}
}
AppUpdateReceiver
package update;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.FileProvider;
import java.io.File;
/**
* @decs: AppUpdateReceiver
* @date: 2018/8/21 21:21
* @version: v 1.0
*/
public class AppUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int notifyId = 1;
int progress = intent.getIntExtra("progress", 0);
String title = intent.getStringExtra("title");
NotificationManager nm = null;
if (AppUpdate.showNotification) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setContentTitle("正在下载" + title);
builder.setSmallIcon(android.R.mipmap.sym_def_app_icon);
builder.setProgress(100, progress, false);
Notification notification = builder.build();
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(notifyId, notification);
}
if (progress == 100) {
if (nm != null) {
nm.cancel(notifyId);
}
if (AppDownload.path != null) {
Intent i = new Intent(Intent.ACTION_VIEW);
File file = new File(AppDownload.path);
if (AppUpdate.needFitAndroidN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(context, "com.self.zsp.dfs.fileprovider", file);
i.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
i.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
}
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
}
}
Callback
package update;
/**
* @decs: 回调
* @date: 2018/8/21 21:15
* @version: v 1.0
*/
public interface Callback {
/**
* 回调
*
* @param position 位
*/
void callback(int position);
}
drawable
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="40dp"
android:height="20dp" />
<solid android:color="#EFEFEF" />
<corners android:radius="12dp" />
</shape>
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="270dp"
android:layout_height="wrap_content"
android:background="@drawable/app_update_dialog"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="15dp"
android:gravity="center_horizontal"
android:text="@string/hint"
android:textColor="@color/fontHint"
android:textSize="19sp" />
<TextView
android:id="@+id/tvAppUpdateDialogTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:lineSpacingExtra="5dp"
android:textColor="@color/fontInput"
android:textSize="17sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="13dp"
android:background="#DFDFDF" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvAppUpdateDialogCancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="@string/cancel"
android:textColor="@color/fontInput"
android:textSize="17sp" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#DFDFDF" />
<TextView
android:id="@+id/tvAppUpdateDialogSure"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:text="@string/sure"
android:textColor="@color/fontInput"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>
styles
<style name="AppUpdateDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@drawable/app_update_dialog</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
主代码
private AppUpdateReceiver appUpdateReceiver;
/**
* 初始应用更新接收器
*/
private void stepAppUpdateReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.self.zsp.dfs.AppUpdate");
appUpdateReceiver = new AppUpdateReceiver();
registerReceiver(appUpdateReceiver, intentFilter);
}
AppUpdate.from(this)
// 更检方式(默VersionCode)
.checkBy(AppUpdate.CHECK_BY_VERSION_NAME)
.versionCode(2)
.versionName("2.0")
.apkPath("http://yapkwww.cdn.anzhi.com/data4/apk/201808/09/8e0274d7643e6898fab820bc1cf7eba6_73867900.apk")
// 下载进度至通知栏(默true)
.showNotification(true)
// 更日志
.updateInfo("测试")
// 下载方式(应用内下载、手机浏览器下载。默应用内下载)
.downloadBy(AppUpdate.DOWNLOAD_BY_APP)
// 强更(默false,强更用户不同意不可用)
.isForce(true)
.update();
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(appUpdateReceiver);
}
注意
- 注意6.0运行时权限、7.0FileProvider
- 8.0限制静态注册广播,改动态注册。参考
- 通知需适配8.0