美文网首页AndroidAndroid收藏集
Android 下载安装应用APK封装(适配8.0)

Android 下载安装应用APK封装(适配8.0)

作者: 斌林诚上 | 来源:发表于2019-04-14 17:28 被阅读9次
    image

    一、简介
    二、效果预览
    ​三、实现步骤
    四、功能解析
    五、Demo地址
    六、内容推荐

    一、简介

    嘿嘿,这周没缺席,继续给大伙们提供一个工具类。用于下载APK,安装应用。几乎每个APP都带有这个功能,通过链接下载APK后,进行安装升级应用。如果要自己重新写的话 ,可能要花半个或一个多小时。不过写过一遍后,下次实现起来就简单许多了。所以嘛,作者就做了这个简单封装类。你们只需CP,不说三分钟搞定,但也可以少走很多弯路。

    image

    二、效果预览

    话再多,还不如先看看效果。适不适合自己的胃口,好吃的话可以点个赞。

    image

    ​三、实现步骤

    (1)检查权限/申请权限

    这里使用LinPermission个人封装的权限工具类,这里就不解释了,不是本章主要内容。你们可以使用自己的权限进行替换。

    (2)提示更新

    可以使用Dialog,也可以使用作者封装好的LinCustomDialogFragment

    (3)调用工具类进行下载安装

    //1.检查存储权限  
    if (LinPermission.checkPermission(activity, 7)) {
        //2.弹窗提示 
        LinCustomDialogFragment.init().setTitle("发现新版本")
            .setContent("是否更新?")
            .setType(LinCustomDialogFragment.TYPE_CANCLE)
            .setOnClickListener(new LinCustomDialogFragment.OnSureCancleListener() {
                @Override
                public void clickSure(String EdiText) {
                    //3.下载安装
                    LinDownloadAPk.downApk(activity, Constants.InstallApk);
                }
    
                @Override
                public void clickCancle() {
                }
             }).show(getFragmentManager());
    } else {
        //申请存储权限
        LinPermission.requestPermission(activity, 7);
    }
    

    LinDownloadAPk.class

    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.net.Uri;
    import android.os.AsyncTask;
    import android.os.Build;
    import android.os.Environment;
    import android.provider.Settings;
    import android.support.annotation.RequiresApi;
    import android.support.v4.content.FileProvider;
    import android.view.View;
    import android.widget.RemoteViews;
    import android.widget.Toast;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.MalformedURLException;
    import java.net.SocketTimeoutException;
    import java.net.URL;
    import blcs.lwb.lwbtool.R;
    
    /**
     * 下载工具类(开发中一般用于APK应用升级)
     */
    public class LinDownloadAPk
    {
        private static int FILE_LEN = 0;
        private static RemoteViews mNotifiviews;
        public static String APK_UPGRADE = Environment
                .getExternalStorageDirectory() + "/DownLoad/apk/BLCS.apk";
        private static PendingIntent nullIntent;
        private static Context mContext;
    
        /**
         * 判断8.0 安装权限
         */
        public static void downApk(Context context, String url) {
            mContext = context;
            if (Build.VERSION.SDK_INT >= 26) {
                boolean b = context.getPackageManager().canRequestPackageInstalls();
                if (b) {
                    downloadAPK( url, null);
                } else {
                    //请求安装未知应用来源的权限
                    startInstallPermissionSettingActivity();
                }
            } else {
                downloadAPK( url, null);
            }
        }
    
        /**
         * 开启安装APK权限(适配8.0)
         */
        @RequiresApi(api = Build.VERSION_CODES.O)
        public static void startInstallPermissionSettingActivity() {
            Uri packageURI = Uri.parse("package:" + mContext.getPackageName());
            Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
            mContext.startActivity(intent);
        }
    
        /**
         * 下载APK文件
         */
        private static void downloadAPK( String url,String localAddress)
        {
            // 下载
            if (localAddress != null)
            {
                APK_UPGRADE = localAddress;
            }
    
            new UpgradeTask().execute(url);
        }
    
        static class UpgradeTask extends AsyncTask<String, Integer, Void>
        {
            @Override
            protected void onPreExecute()
            {
                // 发送通知显示升级进度
                sendNotify();
            }
            @Override
            protected Void doInBackground(String... params)
            {
    
                String apkUrl = params[0];
                InputStream is = null;
                FileOutputStream fos = null;
                try {
                    URL url = new URL(apkUrl);
                    HttpURLConnection conn = (HttpURLConnection) url
                            .openConnection();
                    // 设置连接超时时间
                    conn.setConnectTimeout(25000);
                    // 设置下载数据超时时间
                    conn.setReadTimeout(25000);
                    if (conn.getResponseCode() != HttpURLConnection.HTTP_OK)
                    {
                        return null;// 服务端错误响应
                    }
                    is = conn.getInputStream();
                    FILE_LEN = conn.getContentLength();
                    File apkFile = new File(APK_UPGRADE);
                    // 如果文件夹不存在则创建
                    if (!apkFile.getParentFile().exists())
                    {
                        apkFile.getParentFile().mkdirs();
                    }
                    fos = new FileOutputStream(apkFile);
                    byte[] buffer = new byte[8024];
                    int len = 0;
                    int loadedLen = 0;// 当前已下载文件大小
                    // 更新10次
                    int updateSize = FILE_LEN / 10;
                    int num = 0;
                    while (-1 != (len = is.read(buffer)))
                    {
                        loadedLen += len;
                        fos.write(buffer, 0, len);
                        if (loadedLen > updateSize * num)
                        {
                            num++;
                            publishProgress(loadedLen);
                        }
                    }
                    fos.flush();
                } catch (MalformedURLException e)
                {
                    e.printStackTrace();
                } catch (SocketTimeoutException e)
                {
                    // 处理超时异常,提示用户在网络良好情况下重试
                } catch (IOException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } finally
                {
                    if (is != null)
                    {
                        try
                        {
                            is.close();
                        } catch (IOException e)
                        {
                            e.printStackTrace();
                        }
                    }
                    if (fos != null)
                    {
                        try
                        {
                            fos.close();
                        } catch (IOException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
                return null;
            }
    
            @Override
            protected void onProgressUpdate(Integer... values)
            {
                // 更新通知
                updateNotify(values[0]);
            }
    
            @Override
            protected void onPostExecute(Void result)
            {
                Toast.makeText(mContext, "下载完成,请点击通知栏完成升级", Toast.LENGTH_LONG)
                        .show();
                finishNotify();
            }
        }
    
        private static void sendNotify()
        {
            Intent intent = new Intent();
            nullIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
            mNotifiviews = new RemoteViews(mContext.getPackageName(),
                    R.layout.custom_notify);
            mNotifiviews.setViewVisibility(R.id.tv_custom_notify_number, View.VISIBLE);
            mNotifiviews.setViewVisibility(R.id.pb_custom_notify, View.VISIBLE);
            LinNotify.show(mContext,"","",mNotifiviews,LinNotify.NEW_MESSAGE,null);
        }
    
        private static void updateNotify(int loadedLen)
        {
            int progress = loadedLen * 100 / FILE_LEN;
            mNotifiviews.setTextViewText(R.id.tv_custom_notify_number, progress + "%");
            mNotifiviews.setProgressBar(R.id.pb_custom_notify, FILE_LEN, loadedLen,
                    false);
            LinNotify.show(mContext,"","",mNotifiviews,LinNotify.NEW_MESSAGE,null);
        }
    
        private static void finishNotify()
        {
            mNotifiviews.setTextViewText(R.id.tv_custom_notify_number,  "100%");
            Intent installAppIntent = getInstallAppIntent(APK_UPGRADE);
            PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0,installAppIntent, 0);
            mNotifiviews.setTextViewText(R.id.tv_custom_notify_finish, "下载完成,请点击进行安装");
            mNotifiviews.setViewVisibility(R.id.tv_custom_notify_number, View.INVISIBLE);
            mNotifiviews.setViewVisibility(R.id.pb_custom_notify, View.GONE);
            mNotifiviews.setViewVisibility(R.id.tv_custom_notify_finish, View.VISIBLE);
            LinNotify.show(mContext,"","",mNotifiviews,LinNotify.NEW_MESSAGE,contentIntent);
        }
    
        /**
         * 调往系统APK安装界面(适配7.0)
         * @return
         */
        public static Intent getInstallAppIntent( String filePath) {
            //apk文件的本地路径
            File apkfile = new File(filePath);
            if (!apkfile.exists()) {
                return null;
            }
            Intent intent = new Intent(Intent.ACTION_VIEW);
            Uri contentUri = getUriForFile(apkfile);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            }
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            return intent;
        }
    
        /**
         * 将文件转换成uri
         * @return
         */
        public static Uri getUriForFile(File file) {
            LogUtils.e(mContext.getPackageName());
            Uri fileUri = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                fileUri = FileProvider.getUriForFile(mContext, mContext.getPackageName()+".fileprovider", file);
            } else {
                fileUri = Uri.fromFile(file);
            }
            return fileUri;
        }
    }
    

    (1)这里面还使用了LinNotify通知工具类,因为不是主要内容,所以使用工具类方便继续。你们也可自己写一个通知进行替代。

    (2)Android7.0 FileProvider适配

    Android 7.0 做了一些权限更改,为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问。对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。(这里就简单介绍实现步骤)

    1.声明 FileProvider

    <application>
        ......
        ......
            <provider
                android:name="android.support.v4.content.FileProvider"
                android:authorities="${applicationId}.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
            </provider>
    </application>
    

    2.xml配置(res/xml/file_paths.xml)

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <!--代表设备的根目录 new File("/")-->
        <root-path name="root" path="" />
        <!--代表 context.getFileDir()-->
        <files-path name="files" path="." />
        <!--代表 context.getCacheDir()-->
        <cache-path name="cache" path="." />
        <!--代表 Environment.getExternalStorageDirectory()-->
        <external-path name="external" path="." />
        <!--代表 context.getExternalFilesDirs()-->
        <external-files-path name="external-files" path="." />
        <!--代表 getExternalCacheDirs()-->
        <external-cache-path name="external-cache" path="." />
    </paths>
    

    3.代码实现

        public static Uri getUriForFile(File file) {
            Uri fileUri = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                fileUri = FileProvider.getUriForFile(mContext, mContext.getPackageName()+".fileprovider", file);
            } else {
                fileUri = Uri.fromFile(file);
            }
            return fileUri;
        }
    

    (3)Android8.0 增加未知来源应用权限适配

    Android8.0的诸多新特性中有一个非常重要的特性:未知来源应用权限

    1.在清单文件中增加请求安装权限

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

    2.判断是否开启权限

    boolean b = context.getPackageManager().canRequestPackageInstalls();
    

    3.开启安装APK权限

        /**
         * 开启安装APK权限(适配8.0)
         */
        @RequiresApi(api = Build.VERSION_CODES.O)
        public static void startInstallPermissionSettingActivity() {
            Uri packageURI = Uri.parse("package:" + mContext.getPackageName());
            Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
            mContext.startActivity(intent);
        }
    

    到这里代码已经全部提供好了,你们只需学会CP大法。1分钟就差不多可以搞定这个功能了

    当然清单中还需要添加基础的网络,存储权限。

    image

    四、功能解析

    (1)android 8.0 适配

    1.判断是否是8.0以上 是的话进行8.0适配

    2.android8.0以上判断是否已经申请安装权限 没有这进行权限申请

    3.满足以上条件则调用 downLoadAPK()方法

        public static void downApk(Context context, String url) {
            mContext = context;
            //8.0需要申请安装权限
            if (Build.VERSION.SDK_INT >= 26) {
                boolean b = context.getPackageManager().canRequestPackageInstalls();
                if (b) {
                    downloadAPK( url, null);
                } else {
                    //请求安装未知应用来源的权限
                    startInstallPermissionSettingActivity();
                }
            } else {
                downloadAPK( url, null);
            }
        }
    

    (2)下载APK链接

    new UpgradeTask().execute(url);
    

    这里使用AsyncTask进行异步下载

    1.开始下载前,进行下载通知

    @Override
    protected void onPreExecute()
    {
        // 发送通知显示升级进度
        sendNotify();
    }
    

    2.更新通知栏进度条方法

    publishProgress(loadedLen);
    
    @Override
    protected void onProgressUpdate(Integer... values)
    {
        // 更新通知
        updateNotify(values[0]);
    }
    

    3.下载完成后通知

    @Override
    protected void onPostExecute(Void result)
    {
        Toast.makeText(mContext, "下载完成,请点击通知栏完成升级", Toast.LENGTH_LONG).show();
        finishNotify();
    }
    

    4.完成后,点击进行安装。

    private static void finishNotify()
        {
            mNotifiviews.setTextViewText(R.id.tv_custom_notify_number,  "100%");
            Intent installAppIntent = getInstallAppIntent(APK_UPGRADE);
            PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0,installAppIntent, 0);
            mNotifiviews.setTextViewText(R.id.tv_custom_notify_finish, "下载完成,请点击进行安装");
            mNotifiviews.setViewVisibility(R.id.tv_custom_notify_number, View.INVISIBLE);
            mNotifiviews.setViewVisibility(R.id.pb_custom_notify, View.GONE);
            mNotifiviews.setViewVisibility(R.id.tv_custom_notify_finish, View.VISIBLE);
            LinNotify.show(mContext,"","",mNotifiviews,LinNotify.NEW_MESSAGE,contentIntent);
        }
    

    5.安装APK

        /**
         * 调往系统APK安装界面(适配7.0)
         * @return
         */
        public static Intent getInstallAppIntent( String filePath) {
            //apk文件的本地路径
            File apkfile = new File(filePath);
            if (!apkfile.exists()) {
                return null;
            }
            Intent intent = new Intent(Intent.ACTION_VIEW);
            Uri contentUri = getUriForFile(apkfile);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            }
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            return intent;
        }
    

    6.适配7.0

    /**
         * 将文件转换成uri
         * @return
         */
        public static Uri getUriForFile(File file) {
            Uri fileUri = null;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                fileUri = FileProvider.getUriForFile(mContext, mContext.getPackageName()+".fileprovider", file);
            } else {
                fileUri = Uri.fromFile(file);
            }
            return fileUri;
        }
    

    五、Demo地址

    源码地址:https://github.com/DayorNight/BLCS

    apk下载体验地址:https://www.pgyer.com/BLCS

    image

    六、内容推荐

    CSDN:《Android 下载安装应用APK封装(适配8.0)》

    《Android Notification通知简单封装(适配8.0)​​​​​​​》​​​​​​​

    《Android 仿RxDialog自定义DialogFragment》

    《Android 获取App应用、缓存、数据等大小适配8.0(仿微信存储空间)》

    《Android Rxjava+Retrofit网络请求框架封装(一)》

    如果你觉得写的不错或者对您有所帮助的话

    不妨顶一个【微笑】,别忘了点赞、收藏、加关注哈

    看在花了这么多时间整理写成文章分享给大家的份上,记得手下留情哈

    您的每个举动都是对我莫大的支持

    image

    相关文章

      网友评论

        本文标题:Android 下载安装应用APK封装(适配8.0)

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