android 版本更新适配8.0,解决8.0手机无法更新自动安装apk
随着android 7.0的普及android 8.0的也逐渐流行起来,那么google对权限方面又有了新的修改。而且我发现在android8.0中除了一些bug,比如说:在小米6(Android 8.0系统)中无法自动安装的问题,那么之前做的更新系统就要稍微调整下。
那根据android 8.0我们重新理一下更新的思路:
1、写一个接口调用是否有更新,可以读取版本号后台判断是否有更新,也可拉去最新信息,app内部判断:
2、创建网络监听,监听网络变化,在application中初始化,
3、创建更新service,根据service的机制,我们可以创建一个service不销毁,调用startService方法,service无论你创建多少次onCreate方法只会执行一次(当你没有stop的时候),而onStartCommand可以多次调用
4、调用更新接口,在回调中判断是否需要更新,如果需要更新就创建更新BroadcastReceiver广播,用来通知更新的开始,进度更新,结束,和各种失败的情况,然后创建更新提示dialog(这个不用我说了吧),为了防止更新按钮多次点击和重复下载的问题,我使用了三个全局静态变量来控制
public static boolean hasNewAppVersion = false;//是否有新版本
public static boolean isDownLoadApk = false;//apk是否正在下载
public static boolean isSuccessRequestUpdate = false;//更新请求是否成功了
5.点击更新弹框的,更新按钮,创建更新下载的service,注意下载需要在子线程中做。同时根据下载的开始,下载进度,或者结束,失败等情况利用广播器进行通知。
6、下载完成调用安装,这里一定要注意,android O更新了未知来源的权限,需要在配置文件中添加配置
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
那么接下来我们一步一步讲解
一、接口调用是否有更新,返回数据如下大致如下:
{
"status":true,
"code":200,
"data":{
"VersionName":"1.1.8",
"VersionCode":19,
"AppUrl":"https://********/appfile/*****.apk",//更新地址
"Content":"部分BUG修复",//提示内容
"Status":1,
"isMustUpdate":true//是否强制更新
},
"msg":"返回成功"
}
二、网络状态监听
创建网络状态变化广播器
/**
* Created by CherishTang on 2016/12/29.
* 自定义广播接收器,监听网络状态是否发生改变
*/
public class NetworkStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (NetworkStateUtil.isNetWorkConnected(context) && NetworkStateUtil.isWifiConnected(context)) {
WifiInfo wifiInfo = NetworkStateUtil.getWifiInfo(context);
// MThoast.showShort(context, "已连接到"+(wifiInfo.getSSID()==null?"wifi":wifiInfo.getSSID())+"网络");
processCustomMessage(context, 1);
} else if (NetworkStateUtil.isNetWorkConnected(context) && NetworkStateUtil.isMobileConnected(context)) {
// MThoast.showShort(context, "已连接到数据流量网络");
processCustomMessage(context, 2);
} else {
StaticUtil.isDownLoadApk = false;
processCustomMessage(context, 3);
// MThoast.showShort(context, "已进入无网络次元,请检查网络设置!");
}
}
//send msg to MainActivity
private void processCustomMessage(Context context, Integer message) {
// Intent mIntent = new Intent(BaseActivity.ACTION_INTENT_RECEIVER_MESSAGE);
// mIntent.putExtra("message", message);
// context.sendBroadcast(mIntent);
if (networkChangeListener != null) {
networkChangeListener.networkChanger(message);
}
}
private NetworkChangeListener networkChangeListener;
public void setNetworkChangeListener(NetworkChangeListener networkChangeListener) {
this.networkChangeListener = networkChangeListener;
}
}
添加网络状态变化监听权限
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
为了防止重复注册我们再application中注册广播
public final static String ACTION_INTENT_RECEIVER ="android.net.conn.CONNECTIVITY_CHANGE";
public final static String ACTION_INTENT_RECEIVER_8 = "android.net.wifi.SCAN_RESULTS";
/**
* 注册广播,在onCreate中调用
*/
private void initNetWorkReceiver() {
intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_INTENT_RECEIVER);
intentFilter.addAction(ACTION_INTENT_RECEIVER_8);
networkChangeReceiver = new NetworkStateReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
/**
* 回收广播
*/
@Override
public void onTerminate() {
super.onTerminate();
if (networkChangeReceiver != null)
unregisterReceiver(networkChangeReceiver);
}
/**
* 获取NetworkStateReceiver 实例
*/
public NetworkStateReceiver getNetworkChangeReceiver(){
return networkChangeReceiver;
}
三、创建更新service
在你的MainActivity中开启更新service,同时监听网络变化状态,在onStartConmmand中检查给更新
四、调用接口检查更新
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
checkVersionUpdate();//检查更新网络请求
//获取网络状态变化广播器
NetworkStateReceiver networkStateReceiver =
MyApplication.getInstance().getNetworkChangeReceiver();
//如果广播器不为空,在添加网络变化监听回调
if (networkStateReceiver != null)
networkStateReceiver.setNetworkChangeListener(this);
return super.onStartCommand(intent, flags, startId);
}
/**
* 注册更新BroadcastReceiver
*
* @param versionMessage 更新数据
*/
public void updateCheck(VersionMessage versionMessage) {
StaticUtil.hasNewAppVersion = true;
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DownloadApkService.ACTION_START);
intentFilter.addAction(DownloadApkService.ACTION_UPDATE);
intentFilter.addAction(DownloadApkService.ACTION_FINISHED);
intentFilter.addAction(DownloadApkService.ACTION_CANCEL);
intentFilter.addAction(DownloadApkService.ACTION_ERROR);
intentFilter.addAction(DownloadApkService.ACTION_REDIRECT_ERROR);
downloadApkReceiver = new DownloadApkReceiver();//创建更新下载广播器
registerReceiver(downloadApkReceiver, intentFilter);
//获取当前activity
Activity mContext = AppManager.getAppManager().currentActivity();
//如果程序在前台,并且当前activity没有销毁的话,创建更新弹框,
//我封装在UpdateManger方法中了
if (AppManager.isForeground(this) && mContext != null) {
UpdateManger.getInstance().checkUpdateInfo(mContext,
versionMessage.getAppUrl(), versionMessage.getContent(),
versionMessage.getVersionName(), versionMessage.getMustUpdate());
}
}
五、点击更新按钮下载新版app
首先判断任务是否正在下载,避免重复下载
if (StaticUtil.isDownLoadApk) {
MThoast.showShort(mContext, "已存在下载任务,请勿重复下载");
return;
}
检查并动态获取文件的读取权限
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
//申请WRITE_EXTERNAL_STORAGE权限
ActivityCompat.requestPermissions(mContext, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
0x01);
return;
}
创建更新service并关闭更新提示框
if (customDialog != null && customDialog.isShowing())
customDialog.dismiss();
Intent intent = new Intent(mContext, DownloadApkService.class);
intent.setAction(DownloadApkService.ACTION_START);
intent.putExtra("id", 0);
intent.putExtra("url", apkUrl);
intent.putExtra("name", "保存到本地的app名称");
mContext.startService(intent);
更新下载service代码:
/**
* Created by CherishTang on 2017/10/17.
* 更新app服务
*/
public class DownloadApkService extends Service {
public static final String ACTION_START = "ACTION_START";
public static final String ACTION_UPDATE = "ACTION_UPDATE";
public static final String ACTION_FINISHED = "ACTION_FINISHED";
public static final String ACTION_CANCEL = "ACTION_CANCEL";
public static final String ACTION_ERROR = "ACTION_ERROR";
public static final String ACTION_REDIRECT_ERROR = "ACTION_REDIRECT_ERROR";
public static final String HIDE_DIALOG = "HIDE_DIALOG";
// 文件的保存路径
public static final String path = Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator + StaticUtil.ROOTFILEPATH + File.separator + "download" + File.separator;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
if (ACTION_START.equals(intent.getAction())) {
new DownLoadApkThread(intent.getIntExtra("id", 0)
, intent.getStringExtra("url"), intent.getStringExtra("name")).start();
}
}
return super.onStartCommand(intent, flags, startId);
}
public class DownLoadApkThread extends Thread {
private int id;
private String downloadUrl;
private String fileName;
public DownLoadApkThread(int id, String downloadUrl, String fileName) {
this.id = id;
this.downloadUrl = downloadUrl;
this.fileName = fileName;
}
@Override
public void run() {
HttpURLConnection connLength = null;
HttpURLConnection connFile = null;
RandomAccessFile randomAccessFile = null;
InputStream inputStream = null;
URL url = null;
try {
url = new URL(downloadUrl);
//获取apk文件长度
connLength = (HttpURLConnection) url.openConnection();
connLength.setConnectTimeout(5000);
connLength.setRequestMethod("GET");
int code = connLength.getResponseCode();
int length = 0;
if (code == HttpURLConnection.HTTP_OK) {
length = connLength.getContentLength();
} else if (code == 301) {
sendBroadcast(new Intent().setAction(ACTION_REDIRECT_ERROR));
return;
} else {
sendBroadcast(new Intent().setAction(ACTION_ERROR));
return;
}
//判断文件是否存在,不存在则创建
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, fileName);
randomAccessFile = new RandomAccessFile(file, "rwd");
randomAccessFile.setLength(length);
//下载文件
connFile = (HttpURLConnection) url.openConnection();
connFile.setConnectTimeout(5000);
connFile.setRequestMethod("GET");
connFile.setRequestProperty("Range", "bytes=" + 0 + "-" + length);
code = connFile.getResponseCode();
//显示通知栏进度条
Intent intent = new Intent();
intent.setAction(ACTION_START);
intent.putExtra("id", id);
sendBroadcast(intent);
if (code == HttpURLConnection.HTTP_PARTIAL) {
inputStream = connFile.getInputStream();
int finished = 0;
byte[] bytes = new byte[1024 * 1024];
int len = -1;
long time = System.currentTimeMillis();
while ((len = inputStream.read(bytes)) != -1) {
//文件写入
randomAccessFile.write(bytes, 0, len);
//更新通知栏进度条
finished += len;
if (System.currentTimeMillis() - time > 1000) {
time = System.currentTimeMillis();
intent.setAction(ACTION_UPDATE);
int pro = (int) (((float) finished / length) * 100);
intent.putExtra("finished", pro);
sendBroadcast(intent);
}
}
}
//关闭通知栏
intent.setAction(ACTION_FINISHED);
sendBroadcast(intent);
} catch (MalformedURLException e) {
sendBroadcast(new Intent().setAction(ACTION_ERROR));
e.printStackTrace();
} catch (IOException e) {
sendBroadcast(new Intent().setAction(ACTION_ERROR));
e.printStackTrace();
} finally {
if (connLength != null) {
connLength.disconnect();
}
if (connFile != null) {
connFile.disconnect();
}
try {
if (inputStream != null) {
inputStream.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
super.run();
}
}
}
更新下载通知广播:
/**
* Created by 方舟 on 2017/10/17.
* 更新下载广播接收器
*/
public class DownloadApkReceiver extends BroadcastReceiver {
private UpdateNotificationUtil mNotificationUtil;
@Override
public void onReceive(Context context, Intent intent) {
mNotificationUtil = new UpdateNotificationUtil(context);
if (DownloadApkService.ACTION_START.equals(intent.getAction())) {
StaticUtil.isDownLoadApk = true;//单例下载,防止多任务进行
// 下载开始的时候启动通知栏
mNotificationUtil.showNotification(intent.getIntExtra("id", 0));
} else if (DownloadApkService.ACTION_UPDATE.equals(intent.getAction())) {
// 更新进度条
mNotificationUtil.updateNotification(intent.getIntExtra("id",0), intent.getIntExtra("finished", 0));
} else if (DownloadApkService.ACTION_FINISHED.equals(intent.getAction())) {
StaticUtil.isDownLoadApk = false;//变更未任务未下载
mNotificationUtil.cancelNotification(intent.getIntExtra("id", 0));// 下载结束后取消通知
UpdateManger.installApk(context, new File(DownloadApkService.path + StaticUtil.apkName));
} else if (DownloadApkService.ACTION_CANCEL.equals(intent.getAction())) {
StaticUtil.isDownLoadApk = false;//变更未任务未下载
mNotificationUtil.cancelNotification(intent.getIntExtra("id", 0));// 下载结束后取消通知
} else if (DownloadApkService.ACTION_ERROR.equals(intent.getAction())) {
MThoast.showShort(context, "读取文件失败,请前往官方网站扫码下载最新版本!");
}else if (DownloadApkService.ACTION_REDIRECT_ERROR.equals(intent.getAction())) {
MThoast.showShort(context, "下载地址重定向出现错误,请稍后再试!");
}
}
}
由于android 8.0添加了NotificationChannel的概念因此这个UpdateNotificationUtil工具类可以根据新的方式自己写,我贴一个仅供参考其中
StaticUtil.updateVerisonChannelId,StaticUtil.updateVerisonChannelName,两个静态变量最好不要重复
public static final String updateVerisonChannelId = "包名.updateVersionApp";
public static final String updateVerisonChannelName = "版本更新";
/**
* Created by CherishTang on 2017/10/13.
* 更新通知
*/
public class UpdateNotificationUtil {
private Context mContext;
private NotificationManager mManager;
private NotificationCompat.Builder mBuilder;
public UpdateNotificationUtil(Context context) {
this.mContext = context;
mManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationUtils.createNotificationChannel(
false,false,
StaticUtil.updateVerisonChannelId,
StaticUtil.updateVerisonChannelName,
NotificationManager.IMPORTANCE_DEFAULT);
}
mBuilder = new NotificationCompat.Builder(context, StaticUtil.updateVerisonChannelId);
}
/**
* 显示通知栏
*
* @param id
*/
public void showNotification(final int id) {
mBuilder.setTicker("正在下载");//Ticker是状态栏显示的提示
mBuilder.setOngoing(true);
mBuilder.setContentTitle("正在下载最新版本");
mBuilder.setProgress(100, 0, false);
mBuilder.setContentText(0 + "%");
mBuilder.setSmallIcon(R.mipmap.icon_launcher);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.icon_launcher));//通知栏的大图标
Intent msgIntent = new Intent();
msgIntent.setClass(mContext, MainActivity.class);
msgIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 100, msgIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pendingIntent);//点击跳转
mManager.notify(id, mBuilder.build());
}
/**
* 取消通知栏通知
*/
public void cancelNotification(int id) {
mManager.cancel(id);
}
/**
* 更新通知栏进度条
*
* @param id 获取Notification的id
* @param progress 获取的进度
*/
public void updateNotification(int id, int progress) {
if (mBuilder != null) {
mBuilder.setTicker("开始下载");//Ticker是状态栏显示的提示
mBuilder.setOngoing(true);
mBuilder.setContentTitle("app名称");
mBuilder.setSmallIcon(R.mipmap.icon_launcher);
mBuilder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.icon_launcher));//通知栏的大图标
mBuilder.setProgress(100, progress, false);
mBuilder.setContentText(progress + "%");
mManager.notify(id, mBuilder.build());
}
}
}
/**
* Created by CherishTang on 2018/7/19.
* 通知栏工具类
*/
public class NotificationUtils {
private static NotificationManager mManager;
private static NotificationCompat.Builder notification;
private String contentText, contentTitle, channelId;
private boolean autoCancel = true;
private static Application application;
public static void init(Application application) {
NotificationUtils.application = application;
mManager = (NotificationManager) application.getSystemService(NOTIFICATION_SERVICE);
}
public NotificationUtils Builder(String channelId) {
if (notification == null) {
notification = new NotificationCompat.Builder(application, channelId == null ? "chat" : channelId);
} else {
notification.setChannelId(channelId);
}
this.channelId = channelId;
return this;
}
public static NotificationManager getManager() {
return mManager;
}
public static NotificationCompat.Builder getNotification() {
return notification;
}
@TargetApi(Build.VERSION_CODES.O)
public static void createNotificationChannel(boolean isVibrate,
boolean hasSound,
String channelId,
String channelName,
int importance) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
NotificationManager notificationManager = (NotificationManager) application.getSystemService(
NOTIFICATION_SERVICE);
channel.enableVibration(isVibrate);
channel.enableLights(true);
if (!hasSound)
channel.setSound(null, null);
if (notificationManager != null)
notificationManager.createNotificationChannel(channel);
}
public NotificationUtils setChannelId(String channelId) {
notification.setChannelId(channelId);
this.channelId = channelId;
return this;
}
public NotificationUtils setContentText(String contentText) {
notification.setContentText(contentText);
this.contentText = contentText;
return this;
}
public NotificationUtils setContentTitle(String contentTitle) {
notification.setContentTitle(contentTitle);
this.contentTitle = contentTitle;
return this;
}
public NotificationUtils setAutoCancel(boolean autoCancel) {
notification.setAutoCancel(autoCancel);
this.autoCancel = autoCancel;
return this;
}
public NotificationUtils notifyMessage(int id) {
if (mManager != null) {
notification.setContentTitle(contentTitle)
.setContentText(contentText)
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.icon_launcher)
.setLargeIcon(BitmapFactory.decodeResource(application.getResources(), R.mipmap.icon_launcher))
.setAutoCancel(autoCancel)
.build();
mManager.notify(id, notification.build());
}
return this;
}
}
六、下载完成后启动自动安装
首先注意在Android O新增权限,必须添加,最后调用安装方法
<!--适配android 8.0-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
public static void installApk(Context mContext, File apkFile) {
try{
if (!apkFile.exists()) {
return;
}
Intent i = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= 24) { //适配安卓7.0
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK); //添加这一句表示对目标应用临时授权该Uri所代表的文件
Uri apkFileUri = FileProvider.getUriForFile(mContext.getApplicationContext(),
mContext.getPackageName() + ".FileProvider", apkFile);
i.setDataAndType(apkFileUri, "application/vnd.android.package-archive");
} else {
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setDataAndType(Uri.parse("file://" + apkFile.toString()),
"application/vnd.android.package-archive");// File.toString()会返
回路径信息
}
mContext.startActivity(i);
}catch (Exception e){
e.printStackTrace();
MThoast.showShort(mContext,"自动安装失败,请尝试手动安装!");
}
}
到这里版本更新就好了,大家有问题可以留言。
demo地址:https://download.csdn.net/download/fzkf9225/10679015
我做了两个下载进度条,通知栏和dialog,我觉得一般用通知栏就可以了,dialog最好不用如果关闭下载进度的dialog弹框的话,注销相关DownloadProgressDialog类的相关代码即可
---------------------
作者:青穗CherishTang
来源:CSDN
原文:https://blog.csdn.net/fzkf9225/article/details/80969439
版权声明:本文为博主原创文章,转载请附上博文链接!