美文网首页
Android应用内更新(适配Android Q)

Android应用内更新(适配Android Q)

作者: 雨落川川 | 来源:发表于2020-04-06 20:47 被阅读0次

    下班,走路,春寒料峭
    想,你是人间的四月天
    霎时风起,一川烟草,满城风絮,梅子黄时雨
    可惜了,此时无雨
    喜欢下雨,要等梅子黄时。

    青玉案·凌波不过横塘路

    作者:[贺铸]

    凌波不过横塘路。但目送、芳尘去。锦瑟华年谁与度。月桥花院,琐窗朱户。只有春知处。
    飞云冉冉蘅皋暮。彩笔新题断肠句。若问闲情都几许。一川烟草,满城风絮。梅子黄时雨。

    书归正传,之前写的Android应用内更新组件,从6.0开始,为了适配7.0改动了一次,为了适配8.0又改了,前段时间又为了适配Android Q又改了,累积下来的冗余代码相当多,程序员都是有强迫症的,恰好最近任务不紧不慢,抽空整理起来。

    第一步:检查是否有新的版本

    应用内的app.gradle文件中已经配置了versionCode和VersionName,我们这里需要从服务端请求一个配置文件,用配置文件的版本信息和本地的进行对比,从而决定是否提示用户更新。

    a.使用PackageManager 获取本地versionCode
    PackageManager packageManager = mContext.getPackageManager();
            try {
                PackageInfo info = packageManager.getPackageInfo(mContext.getPackageName(), 0);
                if (info != null) {
                   int vLocalCode = info.versionCode;
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
    
    b.Retrofit2请求服务端配置文件

    我这里的配置文件是这样定义的

    {
        "data": {
            "downUrl": "http://www.xx.com/update/app-release.apk",
            "isForceUpdate": "1",
            "preBaselineCode": "1",
            "updateLog": "版本更新内容:\r\n1.更新了...。\r\n2.适配了...。",
            "versionCode": "3",
            "versionName": "1.1.3"
        },
        "message": "操作成功!",
        "status": 1
    }
    

    当然这个文件的定义随意,但必须包含了版本信息,更新提示,apk的下载地址等要素。

    第二步:提示用户有新版本,让用户选择是否更新

    这一步通过一个自定的Dialog实现,内容大概长成这样:



    这个并没有什么技术含量,代码就不贴了,这里吐槽一下,有的应用只放更新的Button,不更新就无法使用,这种做法很不友好,所以这里放了取消的Button,只是关闭当前弹窗,不更新可以继续使用。

    第三步:下载新的安装包
     private void downLoadApk() {
            Call<ResponseBody> download = apiService.download(downloadUrl);
            download.enqueue(new Callback<ResponseBody>() {
                @Override
                public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
    
    
                    String path = mContext.getExternalFilesDir(null).getPath() + "/download/myApk";
    
                    Thread mThread = new Thread() {
                        @Override
                        public void run() {
                            super.run();
                            writeResponseToDisk(path, response, downloadListener);
                        }
                    };
                    mThread.start();
                }
    
                @Override
                public void onFailure(Call<ResponseBody> call, Throwable t) {
                    t.printStackTrace();
                }
            });
        }
    

    Retrofit2请求,但是这里我们需要获取到下载文件的进度,所以在onResponse回调中,开启子线程,做一些处理

      private static void writeResponseToDisk(String path, Response<ResponseBody> response, DownloadListener downloadListener) {
            writeFileFromIS(new File(path), response.body().byteStream(), response.body().contentLength(), downloadListener);
        }
    

    同是,我们定义了一个DownLoadListener接口,开放方法,对不同的情况做不同的处理

    public interface DownloadListener {
        void onStart();
    
        void onProgress(int progress);
    
        void onFinish(File file);
    
        void onFail(String errorInfo);
    }
    

    继续处理我们的下载内容,其实也就是文件流的写入过程,很基础的东西,唯一不同是我们在不同的过程中调用了不同的监听方法,用来在通知中提示,还有一点需要注意一下,android Q 对于本地的文件读取写入机制做了改动,具体的很多文档都有说,这里不再赘述,这里只把代码贴出来,亲测可用。

     private static void writeFileFromIS(File file, InputStream inputStream, long totalLength, DownloadListener downloadListener) {
            downloadListener.onStart();
            if (!file.exists()) {
                if (!Objects.requireNonNull(file.getParentFile()).exists()) {
                    file.getParentFile().mkdir();
                } else {
                    try {
                        file.createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                        downloadListener.onFail("createNewFile IoException");
                    }
                }
            }
    
            OutputStream os = null;
            long currentLength = 0;
    
            try {
                os = new BufferedOutputStream(new FileOutputStream(file));
                byte data[] = new byte[sBufferSize];
                int len;
    
                while ((len = inputStream.read(data, 0, sBufferSize)) != -1) {
                    os.write(data, 0, len);
                    currentLength += len;
                    //计算当前下载进度
                    downloadListener.onProgress((int) (100 * currentLength / totalLength));
                }
                downloadListener.onFinish(file);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (os != null) {
                    try {
                        os.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    第四步:给用户通知下载进度

    其实这按照功能,也是第三步的内容,但是按照写代码的顺序,这是第四部分。
    通知进度不外乎几种,一种是Notification,通过系统的通知栏给下载进度,一种是在自己应用内,给个进度条或者什么的,可能还有,才疏学浅,慢慢见识,我这里是第一种方式,使用Notification进行通知。

       NotificationManager manager;
       NotificationCompat.Builder mBuilder;
       Notification notification;
    
      private void initNotification() {
            manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel(String.valueOf(channelId), "通知", NotificationManager.IMPORTANCE_DEFAULT);
                channel.enableLights(true);
                channel.setLightColor(ContextCompat.getColor(mContext, R.color.base_color));
                channel.setShowBadge(true);
                channel.setDescription("我的项目");
                manager.createNotificationChannel(channel);
            }
    
            mBuilder = new NotificationCompat.Builder(mContext, channelId);
            mBuilder.setContentTitle("正在下载...")
                    .setSmallIcon(R.mipmap.logo_fupin)
                    .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.logo_fupin))
                    .setDefaults(Notification.DEFAULT_LIGHTS)
                    .setPriority(NotificationCompat.PRIORITY_MAX)
                    .setAutoCancel(false)
                    .setContentText("下载进度:" + "0%")
                    .setProgress(100, 0, false);
            notification = mBuilder.build();
        }
    

    这里也需适配android Q,新版本加入了NotificationChannel ,必须设置channelId,否则这个通知栏不会显示的。
    这里初始化好后,就可以在监听方法中为所欲为的使用了,例如:

         downloadListener = new DownloadListener() {
           
                @Override
                public void onFinish(File file) {
                    mBuilder.setProgress(100, 100, false);
                    mBuilder.setContentText("下载完成" + 100 + "%");
                    notification = mBuilder.build();
                    mBuilder.setAutoCancel(true);
                    manager.notify(1, notification);
                    manager.cancel(1);
                    installApk2(file);
                }
            };
    

    篇幅有限,这里只贴出onFinish()方法,用于上线连贯

    第四步:安装apk
      /**
         * 安装apk  适配androidQ
         */
        private static void installApk2(File file) {
            try {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    Uri apkUri = FileProvider.getUriForFile(mContext, "我们的包名.provider", file);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
                } else {
                    intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
                }
                mContext.startActivity(intent);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    相信很多同学都遇到过下载完成,安装调用不起来的问题,放心,这都是源于Android一次一次的更新,心累,这里只贴代码,核心就是引入了一个FileProvider的东西,这个东西是需要在Manifest.xml文件里配置的

       <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="我们的包名.provider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
            </provider>
    

    @xml/file_paths是存放在xml文件夹下的path配置文件,需要自己去配置一个,位置在这:


    内容其实更简单:
    <?xml version="1.0" encoding="utf-8"?>
    <resources >
        <paths>
            <external-path path="" name="download"/>
        </paths>
    </resources>
    

    写到这里,大概一个应用内更新的组件就算完成了,交付测试组。

    相关文章

      网友评论

          本文标题:Android应用内更新(适配Android Q)

          本文链接:https://www.haomeiwen.com/subject/vqyjuhtx.html