记录下app版本更新的代码实现

1.检测版本是否最新,根据服务器返回结果,进行强制更新或推荐更新提示

2.判断apk是否已有SD卡读写权限

Manifest.permission.READ_EXTERNAL_STORAGE
Manifest.permission.WRITE_EXTERNAL_STORAGE

3.在异步线程中进行apk文件下载操作

public class DownloadRunnable implements Runnable {
    private DownApkListener downApkListener;
    private TaskInfo info;//下载信息JavaBean
    private boolean isStop;//是否暂停

    /**
     * 构造器
     * @param info 任务信息
     */
    public DownloadRunnable(TaskInfo info, DownApkListener downApkListener) {
        this.downApkListener = downApkListener;
        this.info = info;
    }

    /**
     * 停止下载
     */
    public void stop() {
        isStop = true;
    }

    /**
     * Runnable的run方法,进行文件下载
     */
    @Override
    public void run() {
        HttpURLConnection conn;//http连接对象
        BufferedInputStream bis;//缓冲输入流,从服务器获取
        RandomAccessFile raf;//随机读写器,用于写入文件,实现断点续传
        int len = 0;//每次读取的数组长度
        byte[] buffer = new byte[1024 * 8];//流读写的缓冲区
        try {
            File dirFile = new File(info.getPath());
            if (!dirFile.exists() || !dirFile.isDirectory()) {
                dirFile.mkdirs();
            }
            //通过文件路径和文件名实例化File
            File file = new File(info.getPath(),info.getName());
            //实例化RandomAccessFile,rwd模式
            raf = new RandomAccessFile(file, "rwd");
            conn = (HttpURLConnection) new URL(info.getUrl()).openConnection();
            conn.setConnectTimeout(120000);//连接超时时间
            conn.setReadTimeout(120000);//读取超时时间
            conn.setRequestMethod("GET");//请求类型为GET
            if (info.getContentLen() == 0) {//如果文件长度为0,说明是新任务需要从头下载
                //获取文件长度
                info.setContentLen(Long.parseLong(conn.getHeaderField("content-length")));
            } else {//否则设置请求属性,请求制定范围的文件流
                conn.setRequestProperty("Range", "bytes=" + info.getCompletedLen() + "-" + info.getContentLen());
            }
            raf.seek(info.getCompletedLen());//移动RandomAccessFile写入位置,从上次完成的位置开始
            conn.connect();//连接
            bis = new BufferedInputStream(conn.getInputStream());//获取输入流并且包装为缓冲流
            //从流读取字节数组到缓冲区
            while (!isStop && -1 != (len = bis.read(buffer))) {
                //把字节数组写入到文件
                raf.write(buffer, 0, len);
                //更新任务信息中的完成的文件长度属性
                info.setCompletedLen(info.getCompletedLen() + len);
                //更新进度
                downApkListener.inProgress((int) (info.getCompletedLen()* 100.0f/info.getContentLen()));
            }
            if (len == -1) {//如果读取到文件末尾则下载完成
                info.setComplete(true);
                SPOperation.setApkTaskInfo(App.getAppContext(), CommonUtils.toJSONString(info));
                downApkListener.onComplete(info.getPath()+File.separator+info.getName());
            } else {//否则下载系手动停止
                info.setComplete(false);
                SPOperation.setApkTaskInfo(App.getAppContext(), CommonUtils.toJSONString(info));
                LogUtils.i( "下载停止了");
            }
            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
            LogUtils.i(e);
        }
    }

    //下载的进度回调,如果更新UI,需要在主线程中进行
    public interface DownApkListener{
        void inProgress(int progress);
        void onComplete(String apkFullPath);
    }
}

4.保存下载信息、暂停位置的bean类

private String name;//文件名
    private String path;//文件路径
    private String url;//链接
    private long contentLen;//文件总长度
    private boolean isComplete = false;//文件是否完成下载完成了
    /**
     * 迄今为止java虚拟机都是以32位作为原子操作,而long与double为64位,当某线程
     * 将long/double类型变量读到寄存器时需要两次32位的操作,如果在第一次32位操作
     * 时变量值改变,其结果会发生错误,简而言之,long/double是非线程安全的,volatile
     * 关键字修饰的long/double的get/set方法具有原子性。
     */
    private volatile long completedLen;//已完成长度
    
    setXxx()
    getXxx()
    ...
}

5.在onComplete()回调方法中,调起apk安装页面进行安装操作

先请求安装未知来源apk的权限,适配8.0+

/**
     * 判断是否是8.0
     * 8.0需要处理未知应用来源权限问题,否则直接安装
     */
    public static void getInstallPermission(Activity activity,File apkFile) {
        if (Build.VERSION.SDK_INT >= 26) {
            boolean b = activity.getPackageManager().canRequestPackageInstalls();
            if (b) {
                install(activity,apkFile);
            } else {
                //请求安装未知应用来源的权限
                AndPermission.with(activity)
                        .requestCode(Constant.INSTALL_PERMISS_CODE)
                        .permission(Manifest.permission.REQUEST_INSTALL_PACKAGES)
                        .callback(new PermissionListener() {
                            @Override
                            public void onSucceed(int requestCode, @NonNull List<String> grantPermissions) {
                                install(activity,apkFile);
                            }

                            @Override
                            public void onFailed(int requestCode, @NonNull List<String> deniedPermissions) {
                                ToastUitl.showShort("请允许安装应用");
                                //跳转到授予安装未知来源应用开关界面
                                Uri packageURI = Uri.parse("package:"+activity.getPackageName());
                                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,packageURI);
                                activity.startActivityForResult(intent,Constant.INSTALL_PERMISS_CODE);
                            }
                        }).start();
            }
        } else {
            install(activity,apkFile);
        }
    }

获取权限后,调起系统的安装界面

private static final String MIME_TYPE_APK = "application/vnd.android.package-archive";
 private static final String APP_PROVIDE = "packagename.FileProvider";

/**
     * 调起系统的安装界面
     * @param activity
     * @param apkFile
     */
    public static void install(Activity activity,File apkFile) {
        if (CommonUtils.isEmpty(apkFile) || !apkFile.exists()) {
            return;
        }

        //提升读写权限,否则可能出现解析异常
        SystemManager.setPermission(apkFile.getParent());

        // 通过Intent安装APK文件
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (Build.VERSION.SDK_INT >  Build.VERSION_CODES.M) {
            Uri uriForFile = FileProvider.getUriForFile(activity, APP_PROVIDE, apkFile);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(uriForFile, MIME_TYPE_APK);
        } else {
            intent.setDataAndType(Uri.fromFile(apkFile), MIME_TYPE_APK);
        }
        activity.startActivity(intent);
    }

其中APP_PROVIDE常量是自己在清单文件中配置的

<!--下载访问文件内容提供者-->
<provider
   android:name="android.support.v4.content.FileProvider"
   android:authorities="packagename.FileProvider"
   android:exported="false"
   android:grantUriPermissions="true">
   <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/upgrade_file_paths"/>
</provider>

res\xml下upgrade_file_paths.xml文件是配置访问apk文件路径的适配7.0+,具体如何配置参考FileProvider 路径配置策略(7.0+)

 

本文参考允许安装未知来源权限(8.0+)