android 版本更新适配8.0,解决8.0手机无法更新自动安装apk

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/fzkf9225/article/details/80969439

 

       随着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类的相关代码即可

 

展开阅读全文

没有更多推荐了,返回首页