Android 下載安裝應(yīng)用APK封裝(適配8.0)

image

一肆汹、簡介
二愚墓、效果預(yù)覽
?三、實(shí)現(xiàn)步驟
四昂勉、功能解析
五浪册、Demo地址
六、內(nèi)容推薦

一岗照、簡介

嘿嘿村象,這周沒缺席,繼續(xù)給大伙們提供一個(gè)工具類攒至。用于下載APK而线,安裝應(yīng)用旬渠。幾乎每個(gè)APP都帶有這個(gè)功能拨匆,通過鏈接下載APK后整袁,進(jìn)行安裝升級應(yīng)用。如果要自己重新寫的話 志膀,可能要花半個(gè)或一個(gè)多小時(shí)熙宇。不過寫過一遍后鳖擒,下次實(shí)現(xiàn)起來就簡單許多了。所以嘛烫止,作者就做了這個(gè)簡單封裝類蒋荚。你們只需CP,不說三分鐘搞定馆蠕,但也可以少走很多彎路期升。

image

二、效果預(yù)覽

話再多互躬,還不如先看看效果播赁。適不適合自己的胃口,好吃的話可以點(diǎn)個(gè)贊吼渡。

image

?三行拢、實(shí)現(xiàn)步驟

(1)檢查權(quán)限/申請權(quán)限

這里使用LinPermission個(gè)人封裝的權(quán)限工具類,這里就不解釋了诞吱,不是本章主要內(nèi)容。你們可以使用自己的權(quán)限進(jìn)行替換竭缝。

(2)提示更新

可以使用Dialog房维,也可以使用作者封裝好的LinCustomDialogFragment

(3)調(diào)用工具類進(jìn)行下載安裝

//1.檢查存儲(chǔ)權(quán)限  
if (LinPermission.checkPermission(activity, 7)) {
    //2.彈窗提示 
    LinCustomDialogFragment.init().setTitle("發(fā)現(xiàn)新版本")
        .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 {
    //申請存儲(chǔ)權(quán)限
    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;

/**
 * 下載工具類(開發(fā)中一般用于APK應(yīng)用升級)
 */
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 安裝權(quán)限
     */
    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 {
                //請求安裝未知應(yīng)用來源的權(quán)限
                startInstallPermissionSettingActivity();
            }
        } else {
            downloadAPK( url, null);
        }
    }

    /**
     * 開啟安裝APK權(quán)限(適配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()
        {
            // 發(fā)送通知顯示升級進(jìn)度
            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();
                // 設(shè)置連接超時(shí)時(shí)間
                conn.setConnectTimeout(25000);
                // 設(shè)置下載數(shù)據(jù)超時(shí)時(shí)間
                conn.setReadTimeout(25000);
                if (conn.getResponseCode() != HttpURLConnection.HTTP_OK)
                {
                    return null;// 服務(wù)端錯(cuò)誤響應(yīng)
                }
                is = conn.getInputStream();
                FILE_LEN = conn.getContentLength();
                File apkFile = new File(APK_UPGRADE);
                // 如果文件夾不存在則創(chuàng)建
                if (!apkFile.getParentFile().exists())
                {
                    apkFile.getParentFile().mkdirs();
                }
                fos = new FileOutputStream(apkFile);
                byte[] buffer = new byte[8024];
                int len = 0;
                int loadedLen = 0;// 當(dāng)前已下載文件大小
                // 更新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)
            {
                // 處理超時(shí)異常,提示用戶在網(wǎng)絡(luò)良好情況下重試
            } 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, "下載完成抬纸,請點(diǎn)擊通知欄完成升級", 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, "下載完成咙俩,請點(diǎn)擊進(jìn)行安裝");
        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);
    }

    /**
     * 調(diào)往系統(tǒng)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;
    }

    /**
     * 將文件轉(zhuǎn)換成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通知工具類,因?yàn)椴皇侵饕獌?nèi)容湿故,所以使用工具類方便繼續(xù)阿趁。你們也可自己寫一個(gè)通知進(jìn)行替代。

(2)Android7.0 FileProvider適配

Android 7.0 做了一些權(quán)限更改坛猪,為了提高私有文件的安全性脖阵,面向 Android 7.0 或更高版本的應(yīng)用私有目錄被限制訪問。對于面向 Android 7.0 的應(yīng)用墅茉,Android 框架執(zhí)行的 StrictMode API 政策禁止在您的應(yīng)用外部公開 file:// URI命黔。如果一項(xiàng)包含文件 URI 的 intent 離開您的應(yīng)用,則應(yīng)用出現(xiàn)故障就斤,并出現(xiàn) FileUriExposedException 異常悍募。(這里就簡單介紹實(shí)現(xiàn)步驟)

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>
    <!--代表設(shè)備的根目錄 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.代碼實(shí)現(xiàn)

    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 增加未知來源應(yīng)用權(quán)限適配

Android8.0的諸多新特性中有一個(gè)非常重要的特性:未知來源應(yīng)用權(quán)限

1.在清單文件中增加請求安裝權(quán)限

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

2.判斷是否開啟權(quán)限

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

3.開啟安裝APK權(quán)限

    /**
     * 開啟安裝APK權(quán)限(適配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);
    }

到這里代碼已經(jīng)全部提供好了,你們只需學(xué)會(huì)CP大法洋机。1分鐘就差不多可以搞定這個(gè)功能了

當(dāng)然清單中還需要添加基礎(chǔ)的網(wǎng)絡(luò)坠宴,存儲(chǔ)權(quán)限。

image

四绷旗、功能解析

(1)android 8.0 適配

1.判斷是否是8.0以上 是的話進(jìn)行8.0適配

2.android8.0以上判斷是否已經(jīng)申請安裝權(quán)限 沒有這進(jìn)行權(quán)限申請

3.滿足以上條件則調(diào)用 downLoadAPK()方法

    public static void downApk(Context context, String url) {
        mContext = context;
        //8.0需要申請安裝權(quán)限
        if (Build.VERSION.SDK_INT >= 26) {
            boolean b = context.getPackageManager().canRequestPackageInstalls();
            if (b) {
                downloadAPK( url, null);
            } else {
                //請求安裝未知應(yīng)用來源的權(quán)限
                startInstallPermissionSettingActivity();
            }
        } else {
            downloadAPK( url, null);
        }
    }

(2)下載APK鏈接

new UpgradeTask().execute(url);

這里使用AsyncTask進(jìn)行異步下載

1.開始下載前喜鼓,進(jìn)行下載通知

@Override
protected void onPreExecute()
{
    // 發(fā)送通知顯示升級進(jìn)度
    sendNotify();
}

2.更新通知欄進(jìn)度條方法

publishProgress(loadedLen);

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

3.下載完成后通知

@Override
protected void onPostExecute(Void result)
{
    Toast.makeText(mContext, "下載完成副砍,請點(diǎn)擊通知欄完成升級", Toast.LENGTH_LONG).show();
    finishNotify();
}

4.完成后,點(diǎn)擊進(jìn)行安裝颠通。

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, "下載完成址晕,請點(diǎn)擊進(jìn)行安裝");
        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

    /**
     * 調(diào)往系統(tǒng)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

/**
     * 將文件轉(zhuǎn)換成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下載體驗(yàn)地址:https://www.pgyer.com/BLCS

image

六顿锰、內(nèi)容推薦

CSDN:《Android 下載安裝應(yīng)用APK封裝(適配8.0)》

《Android Notification通知簡單封裝(適配8.0)???????》???????

《Android 仿RxDialog自定義DialogFragment》

《Android 獲取App應(yīng)用谨垃、緩存、數(shù)據(jù)等大小適配8.0(仿微信存儲(chǔ)空間)》

《Android Rxjava+Retrofit網(wǎng)絡(luò)請求框架封裝(一)》

如果你覺得寫的不錯(cuò)或者對您有所幫助的話

不妨頂一個(gè)【微笑】硼控,別忘了點(diǎn)贊刘陶、收藏、加關(guān)注哈

看在花了這么多時(shí)間整理寫成文章分享給大家的份上牢撼,記得手下留情哈

您的每個(gè)舉動(dòng)都是對我莫大的支持

?
image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匙隔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子熏版,更是在濱河造成了極大的恐慌纷责,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撼短,死亡現(xiàn)場離奇詭異再膳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)曲横,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門喂柒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人禾嫉,你說我怎么就攤上這事灾杰。” “怎么了熙参?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵艳吠,是天一觀的道長。 經(jīng)常有香客問我孽椰,道長讲竿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任弄屡,我火速辦了婚禮题禀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘膀捷。我一直安慰自己迈嘹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秀仲,像睡著了一般融痛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上神僵,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天雁刷,我揣著相機(jī)與錄音,去河邊找鬼保礼。 笑死沛励,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炮障。 我是一名探鬼主播目派,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼胁赢!你這毒婦竟也來了企蹭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤智末,失蹤者是張志新(化名)和其女友劉穎谅摄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體系馆,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡螟凭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了它呀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棒厘,死狀恐怖纵穿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奢人,我是刑警寧澤谓媒,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站何乎,受9級特大地震影響句惯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜支救,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一抢野、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧各墨,春花似錦指孤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽结洼。三九已至,卻和暖如春叉跛,著一層夾襖步出監(jiān)牢的瞬間松忍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工筷厘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸣峭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓敞掘,卻偏偏與公主長得像叽掘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子玖雁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容