一、前言

app在线更新是一个比较常见需求,新版本发布时,用户进入我们的app,就会弹出更新提示框,第一时间更新新版本app。在线更新分为以下几个步骤:

 1, 通过接口获取线上版本号,versionCode
2, 比较线上的versionCode 和本地的versionCode,弹出更新窗口
3, 下载APK文件(文件下载)
4,安装APK

在线更新就上面几个步骤,前2步比较简单,重要的就是后2个步骤,而由于Android 各个版本对权限和隐私的收归和保护,因此,会出现各种的适配问题,因此本文就总结一下app 在线更新方法和遇到的一些适配问题。

二、apk 下载

apk下载其实就是文件下载,而文件下载有很多方式:

  1,很多三方框架都有文件上传下载功能,可以借助三方框架(比如Volley,OkHttp)
2,也可以开启一个线程去下载,(可以用IntentService)
3,最简单的一种方式:Android SDK 其实给我们提供了下载类DownloadManager,只需要简单的配置项设置,就能轻松实现下载功能。

本文就用第三种方式,用 DownloadManager 来下载apk。

1. 使用DownloadManager 下载apk

DownloadManager 是SDK 自带的,大概流程如下:

(1)创建一个Request,进行简单的配置(下载地址,和文件保存地址等)
(2)下载完成后,系统会发送一个下载完成的广播,我们需要监听广播。
(3)监听到下载完成的广播后,根据id查找下载的apk文件
(4)在代码中执行apk安装。

public void downloadApk(String apkUrl, String title, String desc) {
// fix bug : 装不了新版本,在下载之前应该删除已有文件
File apkFile = new File(weakReference.get().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "test.apk"); if (apkFile != null && apkFile.exists()) { apkFile.delete(); } DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
//设置title
request.setTitle(title);
// 设置描述
request.setDescription(desc);
// 完成后显示通知栏
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalFilesDir(weakReference.get(), Environment.DIRECTORY_DOWNLOADS, "test.apk");
//在手机SD卡上创建一个download文件夹
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdir() ;
//指定下载到SD卡的/download/my/目录下
// request.setDestinationInExternalPublicDir("/codoon/","test.apk"); request.setMimeType("application/vnd.android.package-archive");
//记住reqId
mReqId = mDownloadManager.enqueue(request);
}

如上代码所示,首先构建一个Request,设置下载地址,标题、描述、apk存放目录等,最后,调用mDownloadManager.enqueue(request) 开始下载。

注意:这里我们需要记住这个mReqId,因为下载完成之后,我们需要根据这个ID 去查找apk文件,然后安装apk.

2.更新下载进度

下载文件,我们一般需要知道下载的进度,在界面给用户一个友好的提示,app 更新也是一样,我们需要在界面上显示当前下载进度和总进度,让用户知道大概会等待多久。那么如果获取下载进度呢?

在下载之前,我们需要在Activity 中注册一个Observer,就是一个观察者,当下载进度变化的时候,就会通知观察者,从而更新进度。步骤如下:

1, 首先我们先定义一个观察者DownloadChangeObserver来观察下载进度
2,在DownloadChangeObserver 中更新UI进度,给用户提示
3,下载之前,在Activity 中注册Observer

具体代码如下:
DownloadChangeObserver.class:

 class DownloadChangeObserver extends ContentObserver {

        /**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public DownloadChangeObserver(Handler handler) {
super(handler);
} @Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
updateView();
}
}

updateView()方法中,查询下载进度。

 private void updateView() {
int[] bytesAndStatus = new int[]{0, 0, 0};
DownloadManager.Query query = new DownloadManager.Query().setFilterById(mReqId);
Cursor c = null;
try {
c = mDownloadManager.query(query);
if (c != null && c.moveToFirst()) {
//已经下载的字节数
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
//总需下载的字节数
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
//状态所在的列索引
bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
} finally {
if (c != null) {
c.close();
}
} if (mUpdateListener != null) {
mUpdateListener.update(bytesAndStatus[0], bytesAndStatus[1]);
} Log.i(TAG, "下载进度:" + bytesAndStatus[0] + "/" + bytesAndStatus[1] + "");
}

根据前面我们记录的ID去查询进度,代码中已经注释了,不再多讲。

要想获取到进度,在下载之前,还得先注册DownloadChangeObserver,代码如下:

weakReference.get().getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true,
mDownLoadChangeObserver);

3. 获取下载结果

DownloadManager在下载完成之后,会发送一个下载完成的广播DownloadManager.ACTION_DOWNLOAD_COMPLETE,我们只需要监听这个广播,收到广播后, 获取apk文件安装。

定义一个广播DownloadReceiver

class DownloadReceiver extends BroadcastReceiver {

        @Override
public void onReceive(final Context context, final Intent intent) {
// 安装APK
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Logger.e(TAG, "收到广播");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW); if (completeDownLoadId == mReqId) {
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
} intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
} }

在下载之前注册广播

 // 注册广播,监听APK是否下载完成
weakReference.get().registerReceiver(mDownloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

通过上面的几个步骤,基本上就完成app在线更新功能,在Android 6.0以下可以正常运行。但是别忙,本文还没有结束,Android每一个版本都有一些改动,导致我们需要适配不同的版本,不然的话,就会出问题,结下来就看一下Android 6.0,7.0,8.0 的相关适配。

三、适配Android 6.0

通过前面讲的几个步骤,app 在线更新在6.0以下已经可以正常运行,在Android6.0上,安装的时候会报出以下错误:

 
Caused by:
5 android.content.ActivityNotFoundException:No Activity found to handle Intent { act=android.intent.action.VIEW typ=application/vnd.android.package-archive flg=0x10000000 }

为什么会报上面的错误,经过debug发现,在Android6.0以下和Android6.0上,通过DownloadManager 获取到的Uri不一样。

区别如下:(1)Android 6.0,getUriForDownloadedFile得到 值为: content://downloads/my_downloads/10
(2) Android6.0以下,getUriForDownloadedFile得到的值为:file:///storage/emulated/0/Android/data/packgeName/files/Download/xxx.apk

可以看到,Android6.0得到的apk地址为:content:// 开头的一个地址,安装的时候就会报上面的错误。怎么解决呢?经过查找资料找到了解决办法:

   //通过downLoadId查询下载的apk,解决6.0以后安装的问题
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;
}

代码如上所示,不通过getUriForDownloadedFile去获取Uri,通过DownloadManager.COLUMN_LOCAL_URI 这个字段去获取apk地址。

适配Android 6.0后,安装apk 的代码如下:

 /**
* @param context
* @param intent
*/
private void installApk(Context context, Intent intent) {
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Logger.e(TAG, "收到广播");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW); if (completeDownLoadId == mReqId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0以下
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { // 6.0 - 7.0
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
} // 安装应用
Logger.e("zhouwei", "下载完成了"); intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
}
四、适配Android 7.0

刚适配完6.0,在7.0以上的机子上又出问题了,为什么呢?因为在Android 7.0上,对文件的访问权限作出了修改,不能在使用file://格式的Uri 访问文件 ,Android 7.0提供 FileProvider,应该使用这个来获取apk地址,然后安装apk。如下进行简单的适配:

(1) 在res 目录下,新建一个xml 文件夹,在xml 下面创建一个文件provider_paths文件:

<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="" />
<external-files-path
name="Download"
path="" />
</paths>

(2) 在AndroidManifest.xml清单文件中申明Provider:

<!-- Android 7.0 照片、APK下载保存路径-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="packgeName.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>

(3) Android 7.0上的文件地址获取:

 uri = FileProvider.getUriForFile(context,
"packageNam.fileProvider",
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "xxx.apk"));

好了,就这样7.0适配工作就完成了,适配后的安装代码如下:

 /**
* @param context
* @param intent
*/
private void installApk(Context context, Intent intent) {
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Logger.e(TAG, "收到广播");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW); if (completeDownLoadId == mReqId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0以下
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { // 6.0 - 7.0
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
} else { // Android 7.0 以上
uri = FileProvider.getUriForFile(context,
"packgeName.fileProvider",
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "xxx.apk"));
intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} // 安装应用
Logger.e("zhouwei", "下载完成了"); intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
}

注意:把上面的packageNam 换成你自己的包名,把xxx.apk 换成你自己的apk的名字。
关于更多FileProvider的东西,这儿就不展开讲了,想要了解的可以看一下鸿洋的文章:Android 7.0 行为变更 通过FileProvider在应用间共享文件吧
,讲的很清楚。

五、适配Android 8.0:未知来源的应用权限

好特么累,继续适配Android 8.0, 由于没有Android 8.0的手机,一直没有注意,前些天一个华为用户反馈在线更新不了新版本,具体表现就是:apk下载完成,一闪而过,没有跳转到apk安装界面。经过排查,确定了是Android 8.0权限问题。

Android8.0以上,未知来源的应用是不可以通过代码来执行安装的(在sd卡中找找到apk,手动安装是可以的),未知应用安装权限的开关被除掉,取而代之的是未知来源应用的管理列表,需要列表里面开启你的应用的未知来源的安装权限。Google这么做是为了防止一开始正经的应用后来开始通过升级来做一些不合法的事情,侵犯用户权益。

知道问题了,我们就适配吧:

(1) 在清单文件中申明权限:REQUEST_INSTALL_PACKAGES

  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

(2) 在代码中判断用户是否已经受过权限了,如果已经授权,可以直接安装,如果没有授权,则跳转到授权列表,让用户开启未知来源应用安装权限,开启后,再安装应用。

在监听apk下载状态的广播中添加如下代码:

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, intent);
} @Override
public void permissionFail() {
ToastUtils.shortToast(context, "授权失败,无法安装应用");
}
}; AndroidOPermissionActivity.sListener = listener;
Intent intent1 = new Intent(context, AndroidOPermissionActivity.class);
context.startActivity(intent1); } else {
installApk(context, intent);
}
} else {
installApk(context, intent);
}

因为授权时需要弹框提示,我们用一个Activity来代理创建了一个Activity:AndroidOPermissionActivity 来申请权限,用户点击设置后,跳转到权限设置界面,然后我们再onActivityResult 里判断是都授权成功。

AndroidOPermissionActivity 代码如下:

/**
* 兼容Android 8。0 APP 在线更新,权限申请界面
* Created by zhouwei on 2018/3/23.
*/ public class AndroidOPermissionActivity extends BaseActivity {
public static final int INSTALL_PACKAGES_REQUESTCODE = 1;
private AlertDialog mAlertDialog;
public static AppDownloadManager.AndroidOInstallPermissionListener sListener; @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // 弹窗 if (Build.VERSION.SDK_INT >= 26) {
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("为了正常升级 xxx APP,请点击设置按钮,允许安装未知来源应用,本功能只限用于 xxx APP版本升级");
builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onClick(DialogInterface dialogInterface, int i) {
startInstallPermissionSettingActivity();
mAlertDialog.dismiss();
}
});
builder.setNegativeButton("取消", 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;
}
}

注意:当通过Intent 跳转到未知应用授权列表的时候,一定要加上包名,这样就能直接跳转到你的app下,不然只能跳转到列表。

 

好了,这样Android 8.0 上也可以在线更新了。

六、完整代码,封装了一个类AppDownloadManager

为了不依赖于某个Activity ,因此封装了一个AppDownloadManager
,少量几行代码就可以实现在线更新,给出完整代码:

public class AppDownloadManager {
public static final String TAG = "AppDownloadManager";
private WeakReference<Activity> weakReference;
private DownloadManager mDownloadManager;
private DownloadChangeObserver mDownLoadChangeObserver;
private DownloadReceiver mDownloadReceiver;
private long mReqId;
private OnUpdateListener mUpdateListener; public AppDownloadManager(Activity activity) {
weakReference = new WeakReference<Activity>(activity);
mDownloadManager = (DownloadManager) weakReference.get().getSystemService(Context.DOWNLOAD_SERVICE);
mDownLoadChangeObserver = new DownloadChangeObserver(new Handler());
mDownloadReceiver = new DownloadReceiver();
} public void setUpdateListener(OnUpdateListener mUpdateListener) {
this.mUpdateListener = mUpdateListener;
} public void downloadApk(String apkUrl, String title, String desc) {
// fix bug : 装不了新版本,在下载之前应该删除已有文件
File apkFile = new File(weakReference.get().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_name.apk"); if (apkFile != null && apkFile.exists()) {
apkFile.delete();
} DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl));
//设置title
request.setTitle(title);
// 设置描述
request.setDescription(desc);
// 完成后显示通知栏
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalFilesDir(weakReference.get(), Environment.DIRECTORY_DOWNLOADS, "app_name.apk");
//在手机SD卡上创建一个download文件夹
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).mkdir() ;
//指定下载到SD卡的/download/my/目录下
// request.setDestinationInExternalPublicDir("/codoon/","codoon_health.apk"); request.setMimeType("application/vnd.android.package-archive");
//
mReqId = mDownloadManager.enqueue(request);
} /**
* 取消下载
*/
public void cancel() {
mDownloadManager.remove(mReqId);
} /**
* 对应 {@link Activity }
*/
public void resume() {
//设置监听Uri.parse("content://downloads/my_downloads")
weakReference.get().getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true,
mDownLoadChangeObserver);
// 注册广播,监听APK是否下载完成
weakReference.get().registerReceiver(mDownloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
} /**
* 对应{@link Activity#onPause()} ()}
*/
public void onPause() {
weakReference.get().getContentResolver().unregisterContentObserver(mDownLoadChangeObserver);
weakReference.get().unregisterReceiver(mDownloadReceiver);
} private void updateView() {
int[] bytesAndStatus = new int[]{0, 0, 0};
DownloadManager.Query query = new DownloadManager.Query().setFilterById(mReqId);
Cursor c = null;
try {
c = mDownloadManager.query(query);
if (c != null && c.moveToFirst()) {
//已经下载的字节数
bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
//总需下载的字节数
bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
//状态所在的列索引
bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
} finally {
if (c != null) {
c.close();
}
} if (mUpdateListener != null) {
mUpdateListener.update(bytesAndStatus[0], bytesAndStatus[1]);
} Log.i(TAG, "下载进度:" + bytesAndStatus[0] + "/" + bytesAndStatus[1] + "");
} class DownloadChangeObserver extends ContentObserver { /**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public DownloadChangeObserver(Handler handler) {
super(handler);
} @Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
updateView();
}
} class DownloadReceiver extends BroadcastReceiver { @Override
public void onReceive(final Context context, final Intent intent) {
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, intent);
} @Override
public void permissionFail() {
ToastUtils.shortToast(context, "授权失败,无法安装应用");
}
}; AndroidOPermissionActivity.sListener = listener;
Intent intent1 = new Intent(context, AndroidOPermissionActivity.class);
context.startActivity(intent1); } else {
installApk(context, intent);
}
} else {
installApk(context, intent);
} }
} /**
* @param context
* @param intent
*/
private void installApk(Context context, Intent intent) {
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Logger.e(TAG, "收到广播");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW); if (completeDownLoadId == mReqId) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0以下
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { // 6.0 - 7.0
File apkFile = queryDownloadedApk(context, completeDownLoadId);
uri = Uri.fromFile(apkFile);
} else { // Android 7.0 以上
uri = FileProvider.getUriForFile(context,
"package_name.fileProvider",
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app_name.apk"));
intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} // 安装应用
Logger.e("zhouwei", "下载完成了"); intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
} //通过downLoadId查询下载的apk,解决6.0以后安装的问题
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;
} public interface OnUpdateListener {
void update(int currentByte, int totalByte);
} public interface AndroidOInstallPermissionListener {
void permissionSuccess(); void permissionFail();
}
}

使用就很简单了,如下:

(1) 弹出更新提示框:提示用户更新

 private void showUpdateDialog(final AppUpdateInfo updateInfo) {
AppUpdateDialog dialog = new AppUpdateDialog(getContext());
dialog.setAppUpdateInfo(updateInfo);
dialog.setOnUpdateClickListener(new AppUpdateDialog.OnUpdateClickListener() {
@Override
public void update(final AppUpdateDialog updateDialog) {
String title = "app name";
String desc = "版本更新"; mDownloadManager.setUpdateListener(new AppDownloadManager.OnUpdateListener() {
@Override
public void update(int currentByte, int totalByte) {
updateDialog.setProgress(currentByte, totalByte);
if ((currentByte == totalByte) && totalByte != 0) {
updateDialog.dismiss();
} }
}); mDownloadManager.downloadApk(updateInfo.download_url, title, desc);
} });
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
dialog.show(); }

(2) 注意在 onResume 和 onPause 调用对应方法:

 @Override
public void onResume() {
super.onResume();
if (mDownloadManager != null) {
mDownloadManager.resume();
}
}
 @Override
public void onPause() {
super.onPause();
if (mDownloadManager != null) {
mDownloadManager.onPause();
} }

[图片上传失败...(image-d8b11a-1524880080337)]

七、总结

本文总结了项目中app在线更新遇到的一些适配问题,关于Android 6.0 的适配,如果你没有使用DownloadManager,可能不会遇到这个问题。7.0 和 8.0 的适配不管用哪种方式,都会有。

关于app在线更新版本适配就此结束,如果有啥问题,欢迎指出。

链接:https://www.jianshu.com/p/85913ed97af5 ,转载请注明来源

如果对技术开发的可以参考下面的文章

微信小程序开发<一>

微信小程序开发<二>

这个时代,作为程序员,我什么要学习小程序

来开发一个wanandroid快应用吧

“大话架构”阿里架构师分享的Java程序员需要突破的技术要点

技术

Android app 在线更新那点事儿(适配Android6.0、7.0、8.0)的更多相关文章

  1. android app内部更新适配到8.0

    app 内部跟新是app中必须要有的功能,在app出现改变时,app内部更新能以最快的速度将应用提升到最新版本. 步骤: 1.获取本地app的版本号 int versionCode = 0; try ...

  2. Android APP 多端适配

    Android APP 多端适配 传统的多终端适配方案,是为大尺寸 Pad开发一个特定的 HD版本. 但是目前支持 Android 系统的设备类型越来越丰富,不同类型的设备尺寸也越来越多样化,特定的H ...

  3. 关于Android App开发技术分类的一个小总结

     前言 本文从热更新.异步并发.性能优化.网络请求等多个方面对Android App开发的技术进行了一个分类总结.欢迎大家沟通交流. 热更新 [原]热更新开源项目Tinker源码解析之Dex热更新 [ ...

  4. 关于Android App开发知识体系的一个小总结

     前言 本文从热更新.异步并发.性能优化.网络请求等多个方面对Android App开发的知识体系进行了一个分类总结.欢迎大家沟通交流. 热更新 [原]热更新开源项目Tinker源码解析之Dex热更新 ...

  5. 【腾讯优测干货分享】Android 相机预览方向及其适配探索

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583ba1df25d735cd2797004d 由于Android系统的开放策略 ...

  6. 【Bugly安卓开发干货分享】Android APP 快速 Pad 化实现

    项目背景 采用最新版本手机 APP(之后称为 MyApp)代码,实现其 Pad 化,为平板和大屏手机用户提供更好的体验.为实现 MyApp 的 Pad 化工作,需要我们首先来了解一下 MyApp 项目 ...

  7. 安卓app设计规范整理和Android APP设计篇(转)

    随着安卓智能手机不停的更新换代.安卓手机系统越来越完美,屏幕尺寸也越来越大啦!比如最近小米的miui 6的发布和魅族手机系统的更新等等. 以小米MIUI6的安卓手机来说,MIUI6进行了全新设计,坚持 ...

  8. 携程Android App插件化和动态加载实践

    携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实 ...

  9. android 开发对gif解码(适配android 4.2、4.3、4.4版本)

    android 开发对gif解码(适配android 4.2.4.3.4.4版本) 使用方法: public class ImageInputActivity extends Activity imp ...

随机推荐

  1. quartz简单定时任务【可以处理完一个任务才开启下一个线程】【我】

    maven jar project项目一个 pom文件: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:x ...

  2. 常用linux命令(项目部署)

    centos 图形 命令行 界面切换 如果在图形界面下,按:Ctrl+Alt+F2进入如下命令行界面 -------------- 看当前目录的路径: pwd ................... ...

  3. poj 2186 "Popular Cows"(强连通分量入门题)

    传送门 参考资料: [1]:挑战程序设计竞赛 题意: 每头牛都想成为牛群中的红人. 给定N头牛的牛群和M个有序对(A, B),(A, B)表示牛A认为牛B是红人: 该关系具有传递性,所以如果牛A认为牛 ...

  4. jQuery中json中关于带有html代码网页的处理

    昨天在使用jQuery的get方式请求返回json格式数据.然后使用jQuery自带的parseJSON 处理.总是出现这样的错误. 如果返回数据中content改成普通的数据可以通过.找了半天错误, ...

  5. k8s部署prometheus

    https://www.kancloud.cn/huyipow/prometheus/527092 https://songjiayang.gitbooks.io/prometheus/content ...

  6. 《Linux就该这么学》第二期视频

    Linux就该这么学--第二期学习笔记... ------------- 你的未来取决于你现在点点滴滴的努力 需要用到的一些工具: Vm11激活码 ---------- root在Linux系统中相当 ...

  7. JavaSE学习总结(十八)—— 多线程

    多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...

  8. 所有HTTP请求参数及报文查看SERVLET【原】

    HttpRequestServlet.java 说明: 用于接受所有http形式的请求,并把接受到的request中param及getInputStream全打印出来. package king.se ...

  9. CentOS6.8下安装redis并配置开机自启动

    参考资料:http://www.bubuko.com/infodetail-1006383.html   http://www.cnblogs.com/skyessay/p/6433349.html ...

  10. 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue

    概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...