这篇文章也是在项目完成之后,项目中用到的一种APP的升级方式,主要效果就是升级之后再通知栏这个栏位中显示下载进度,等下载完成之后自动安装。完美适配Android 8.0。记录下来供后续参考使用。
其中主要用到了几个关键的技术点:
1、权限申请
2、fileProvider的使用
3、Broadcast Receiver的使用。
4、文件下载和安装的工具类
不再赘述,直接看代码。
AndroidManifest.xml中添加的部分:
1、添加权限申请
2、添加provider,其中吧package_name换成自己的APP的包名
3、添加activity
4、添加receiver
下边开始实现上边提到的每个部分,首先工具类:
1、文件下载工具类
import android.app.DownloadManager;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
public class FileDownloadManager {
private DownloadManager downloadManager;
private Context context;
private static FileDownloadManager instance;
private FileDownloadManager(Context context) {
downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
this.context = context.getApplicationContext();
}
public static FileDownloadManager getInstance(Context context) {
if (instance == null) {
instance = new FileDownloadManager(context);
}
return instance;
}
/**
* @param uri
* @param title
* @param description
* @return download id
*/
public long startDownload(String uri, String title, String description,String appName) {
DownloadManager.Request req = new DownloadManager.Request(Uri.parse(uri));
//req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
req.setAllowedOverRoaming(false);
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//设置文件的保存的位置[三种方式]
//第一种
//file:///storage/emulated/0/Android/data/your-package/files/Download/update.apk
req.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, appName+".apk");
//第二种 这种保存位置会导致Android 8.0安装的时候出现”解析软件包时出现问题“的问题
//file:///storage/emulated/0/Download/update.apk
//req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, appName+".apk");
//第三种 自定义文件路径
//req.setDestinationUri()
// 设置一些基本显示信息
req.setTitle(title);
req.setDescription(description);
//req.setMimeType("application/vnd.android.package-archive");
//异步
return downloadManager.enqueue(req);
//dm.openDownloadedFile()
}
/**
* 获取文件保存的路径
*
* @param downloadId an ID for the download, unique across the system.
* This ID is used to make future calls related to this download.
* @return file path
* @see FileDownloadManager#getDownloadUri(long)
*/
public String getDownloadPath(long downloadId) {
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = downloadManager.query(query);
if (c != null) {
try {
if (c.moveToFirst()) {
return c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
}
} finally {
c.close();
}
}
return null;
}
/**
* 获取保存文件的地址
*
* @param downloadId an ID for the download, unique across the system.
* This ID is used to make future calls related to this download.
* @see FileDownloadManager#getDownloadPath(long)
*/
public Uri getDownloadUri(long downloadId) {
return downloadManager.getUriForDownloadedFile(downloadId);
}
public DownloadManager getDownloadManager() {
return downloadManager;
}
/**
* 获取下载状态
*
* @param downloadId an ID for the download, unique across the system.
* This ID is used to make future calls related to this download.
* @return int
* @see DownloadManager#STATUS_PENDING
* @see DownloadManager#STATUS_PAUSED
* @see DownloadManager#STATUS_RUNNING
* @see DownloadManager#STATUS_SUCCESSFUL
* @see DownloadManager#STATUS_FAILED
*/
public int getDownloadStatus(long downloadId) {
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = downloadManager.query(query);
if (c != null) {
try {
if (c.moveToFirst()) {
return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
}
} finally {
c.close();
}
}
return -1;
}
}
2、安装工具类:
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log;
public class DownLoadApk {
public static final String TAG = DownLoadApk.class.getSimpleName();
public static void download(Context context, String url, String title,final String appName) {
// 获取存储ID
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
long downloadId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L);
if (downloadId != -1L) {
FileDownloadManager fdm = FileDownloadManager.getInstance(context);
int status = fdm.getDownloadStatus(downloadId);
if (status == DownloadManager.STATUS_SUCCESSFUL) {
//启动更新界面
Uri uri = fdm.getDownloadUri(downloadId);
if (uri != null) {
if (compare(getApkInfo(context, uri.getPath()), context)) {
startInstall(context, uri);
return;
} else {
fdm.getDownloadManager().remove(downloadId);
}
}
start(context, url, title,appName);
} else if (status == DownloadManager.STATUS_FAILED) {
start(context, url, title,appName);
} else {
Log.d(TAG, "apk is already downloading");
start(context, url, title,appName);
}
} else {
start(context, url, title,appName);
}
}
private static void start(Context context, String url, String title,String appName) {
long id = FileDownloadManager.getInstance(context).startDownload(url,
title, "下载完成后点击打开",appName);
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
sp.edit().putLong(DownloadManager.EXTRA_DOWNLOAD_ID,id).commit();
Log.d(TAG, "apk start download " + id);
}
public static void startInstall(Context context, Uri uri) {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(uri, "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
/**
* 获取apk程序信息[packageName,versionName...]
*
* @param context Context
* @param path apk path
*/
private static PackageInfo getApkInfo(Context context, String path) {
PackageManager pm = context.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
if (info != null) {
return info;
}
return null;
}
/**
* 下载的apk和当前程序版本比较
*
* @param apkInfo apk file's packageInfo
* @param context Context
* @return 如果当前应用版本小于apk的版本则返回true
*/
private static boolean compare(PackageInfo apkInfo, Context context) {
if (apkInfo == null) {
return false;
}
String localPackage = context.getPackageName();
if (apkInfo.packageName.equals(localPackage)) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0);
if (apkInfo.versionCode > packageInfo.versionCode) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
return false;
}
}
在res中新建xml文件夹和update_provider_paths.xml
新建AndroidOPermissionActivity 文件
import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import com.xxx.xxx.R;
import com.xxx.xxx.interfaces.AndroidOInstallPermissionListener;
public class AndroidOPermissionActivity extends Activity {
public static final int INSTALL_PACKAGES_REQUESTCODE = 1;
private AlertDialog mAlertDialog;
public static AndroidOInstallPermissionListener sListener;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 弹窗
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, INSTALL_PACKAGES_REQUESTCODE);
} else {
finish();
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case INSTALL_PACKAGES_REQUESTCODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (sListener != null) {
sListener.permissionSuccess();
finish();
}
} else {
//startInstallPermissionSettingActivity();
showDialog();
}
break;
}
}
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.app_name);
builder.setMessage(R.string.permission_notify);
builder.setPositiveButton(R.string.setting, new DialogInterface.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onClick(DialogInterface dialogInterface, int i) {
startInstallPermissionSettingActivity();
mAlertDialog.dismiss();
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (sListener != null) {
sListener.permissionFail();
}
mAlertDialog.dismiss();
finish();
}
});
mAlertDialog = builder.create();
mAlertDialog.show();
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallPermissionSettingActivity() {
//注意这个是8.0新API
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == RESULT_OK) {
// 授权成功
if (sListener != null) {
sListener.permissionSuccess();
}
} else {
// 授权失败
if (sListener != null) {
sListener.permissionFail();
}
}
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
sListener = null;
}
}
新建interface文件用于回调申请权限的结构
public interface AndroidOInstallPermissionListener {
void permissionSuccess();
void permissionFail();
}
新建receiver用于接收APP下载完成之后的广播,需要注意的是provider的位置要和manifest中的一直
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import android.widget.Toast;
import com.xxx.xxx.activity.AndroidOPermissionActivity;
import com.xxx.xxx.interfaces.AndroidOInstallPermissionListener;
import java.io.File;
public class ApkInstallReceiver extends BroadcastReceiver {
private static final String FILE_PROVIDER = "com.xxx.xxx.fileProvider";
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){
long downloadApkId =intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
// installApk(context, downloadApkId);
checkPermission(context,downloadApkId);
}
}
/**
* 安装apk
*/
private void installApk(Context context, long downloadApkId) {
// 获取存储ID
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
Uri downloadFileUri = null;
Intent install= new Intent(Intent.ACTION_VIEW);
long downId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L);
if(downloadApkId == downId){
DownloadManager downManager= (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// 6.0以下
downloadFileUri = downManager.getUriForDownloadedFile(downloadApkId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
// 6.0 - 7.0
File apkFile = queryDownloadedApk(context, downloadApkId);
downloadFileUri = Uri.fromFile(apkFile);
}else { // Android 7.0 以上
downloadFileUri = FileProvider.getUriForFile(context, FILE_PROVIDER,
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "update.apk"));
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
//Uri downloadFileUri = downManager.getUriForDownloadedFile(downloadApkId);
if (downloadFileUri != null) {
install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}else{
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
}
public static File queryDownloadedApk(Context context, long downloadId) {
File targetApkFile = null;
DownloadManager downloader = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (downloadId != -1) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
Cursor cur = downloader.query(query);
if (cur != null) {
if (cur.moveToFirst()) {
String uriString = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
if (!TextUtils.isEmpty(uriString)) {
targetApkFile = new File(Uri.parse(uriString).getPath());
}
}
cur.close();
}
}
return targetApkFile;
}
private void checkPermission(final Context context, final long downloadApkId){
boolean haveInstallPermission;
// 兼容Android 8.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
haveInstallPermission = context.getPackageManager().canRequestPackageInstalls();
if (!haveInstallPermission) {
//没有权限,弹窗,并去设置页面授权
final AndroidOInstallPermissionListener listener = new AndroidOInstallPermissionListener() {
@Override
public void permissionSuccess() {
installApk(context, downloadApkId);
}
@Override
public void permissionFail() {
//ToastUtils.shortToast(context, "授权失败,无法安装应用");
}
};
AndroidOPermissionActivity.sListener = listener;
Intent intent1 = new Intent(context, AndroidOPermissionActivity.class);
context.startActivity(intent1);
} else {
installApk(context, downloadApkId);
}
}else {
installApk(context, downloadApkId);
}
}
}
OK,剩下的事情就是直接去查看版本信息,直接调用DownLoadApk.download的方法去做升级了。大功告成!