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

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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容