关于实现android冷更新功能(附自己一些感悟和遇到的很多奇葩问题)

笔者最近接到个新需求 是使用RN框架(react-native) 来结合android 开发的APP。要实现用户点击检查更新,可以自动更新安装的功能。

 好了,我们先整理下逻辑,第一,先判断笔者自定义的下载目录下有没有包名跟当前APP包名一样的APK包,如果有的话获取该包的一些信息 (包括包名,版本号,版本名,图标,名称等一系列信息,这里我贴上我找了很久的一个大神写的代码,有需要可以自己copy)





如果需要versionCode  就写

这个方法的参数:只需要传入上下文和未安装的APK的位置 记住了 要标明未安装的APK的名称后缀 如下图

// apkPath=Environment.getExternalStorageDirectory()+"/"+"TestB.apk"

然后接上面所说,如果找到了 就判断版本号是否大于当前APP的版本号(如果大于就直接启动更新安装,否则就重新下载在进行更新安装),获取当前APP的版本号的方法如下(传入的参数 :上下文和包名 )

// 獲取版本號
public static int getVersionCode(Context context, String paname) {
    int version_code = 0;
    PackageManager pm = context.getPackageManager();
    try {
        version_code = pm.getPackageInfo(paname, 0).versionCode;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
        version_code = 0;
    }
    return version_code;
}

为什么这么做的原因是 这个一般是用户点击下载了 然后因为什么原因取消安装了或者放弃了安装,导致下载的APK已存在,如果用户在此点击检测更新 又重新下载 很影响用户体验和浪费用户流量(不过流量浪费不浪费也不管俺的事哈哈).现在讲讲 这里遇到的问题。

    private void getUninstallAPKInfo(Context ctx, String archiveFilePath, String url, String Pname, Uri uri) {
        // archiveFilePath=Environment.getExternalStorageDirectory()+"/"+"TestB.apk"

        String versionName = null;  //未安装APK 版本名
        String versionCode = null;  //未安装APK 版本号
        String appName = null;     //未安装APK 名字
        String pakName = null;     //未安装APK 包名
        if (archiveFilePath == null || archiveFilePath.equals("")) {

        } else {
            Log.i("APK包路径", archiveFilePath);
        }
        PackageManager pm = ctx.getPackageManager();
        PackageInfo pakinfo = pm.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES);
        if (pakinfo != null) {
            Log.i("获取APK信息", "文件夾内有包----第一层");
            ApplicationInfo appinfo = pakinfo.applicationInfo;
            versionName = pakinfo.versionName;
            versionCode = String.valueOf(pakinfo.versionCode);
            Drawable icon = pm.getApplicationIcon(appinfo);
            appName = (String) pm.getApplicationLabel(appinfo);
            pakName = appinfo.packageName;
            String text = "versionName = " + versionName + " , appName = " + appName + " , pakName = " + pakName + ",versionCode=" + versionCode;
            Log.i("APK信息", text);
            int i = Integer.parseInt(versionCode);
            int a = i / 10000;
            int c = i % 100;
            int b = i / 100 % 100;
            int realCode = Integer.parseInt("" + a + b + c);  // 未安裝
            int versioncode = Utils.getVersionCode(mContext, Pname);//該APP版本號

            if (realCode > versioncode) {
                Log.i("获取APK信息比較", "未安裝APK版本號大于当前APP版本号");
                if (Build.VERSION.SDK_INT >= 24) {
                    Log.i("android版本号", "7.0");
//                    MainActivity.getInstance().install(uri);
                    Intent intent2 = new Intent(Intent.ACTION_VIEW);
                    intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    intent2.setAction(Intent.ACTION_VIEW);
                    intent2.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
                    intent2.setDataAndType(Uri.parse(uri.toString()), "application/vnd.android.package-archive");
                    Log.i("安裝地址", uri.toString());
                    mContext.startActivity(intent2);
                } else {
                    Log.i("android版本号", "小于7.0");
//                    MainActivity.getInstance().install(uri);
                    Intent install = new Intent(Intent.ACTION_VIEW);
                    //表明不是未知来源
                    install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
                    install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    install.setDataAndType(Uri.parse("file://" + uri.toString()), "application/vnd.android.package-archive");
                    Log.i("安裝地址", "file://" + uri.toString());
                    mContext.startActivity(install);
                }
            } else {
                Log.i("获取APK信息比較", "未安裝APK版本號小于或等于当前APP版本号,需刪除未安裝APK");
                if (Build.VERSION.SDK_INT >= 24) {
                    File file1 = new File(archiveFilePath);
                    Boolean delete = file1.delete();
                    Log.i("7.0APK安装包刪除--", delete.toString());
                    if (delete) {
                        initData(url);
                    } else {

                    }
                } else {
                    File file = new File(archiveFilePath);
                    Boolean delete1 = file.delete();
                    Log.i("7.0以下APK安装包刪除--", delete1.toString());
                    if (delete1) {
                        initData(url);
                    } else {

                    }
                }

            }


        } else {
            Log.i("获取APK信息", "文件夾内沒包----第一层");
            initData(url);
        }
    }


第一,文件位置问题 想必大家肯定知道如何调用系统的安装页面了吧

代码如下

Intent install = new Intent(Intent.ACTION_VIEW);
//表明不是未知来源
install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.setDataAndType(Uri.parse("file://" + uri.toString()), "application/vnd.android.package-archive");
Log.i("安裝地址", "file://" + uri.toString());
mContext.startActivity(install);


这个有一个问题

  

也是笔者卡了挺久的地方,就是这个异常,度娘了半天,也没找到我需要的答案,后面在跟一个朋友探讨的时候,他跟我说了是不是文件路径有问题 你这个路径不对 ,我log日志答应出来后 果然发现是文件路径问题 

1.    如果出现no activity found handle intent异常,一般是因为intent跳转失败。在进行以下语句跳转时,忽略了在apk路径前添加"file://",导致跳转失败,注意添加即可。 

这就完美解决了我的第一个问题,后面发现进行android各系统测试 发现7.0报异常了,然后回想起,需要做适配。

利用fileProvider 来获取文件地址,代码如下


                if (Build.VERSION.SDK_INT >= 24) {
                    Log.i("android版本号", "7.0");
//                    MainActivity.getInstance().install(uri);
                    Intent intent2 = new Intent(Intent.ACTION_VIEW);
                    intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    intent2.setAction(Intent.ACTION_VIEW);
                    intent2.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
                    intent2.setDataAndType(Uri.parse(uri.toString()), "application/vnd.android.package-archive");
                    Log.i("安裝地址", uri.toString());
                    mContext.startActivity(intent2);
                }

注意:这里setDataAndType 这里的路径拿的是fileprovider获取到的URL 不用拼接 file://

7.0的URL 

  1. fileUri = FileProvider.getUriForFile(BaseApplication.getApplication(), BaseApplication.getApplication().getPackageName() + ".FileProvider",
  2. new File(filePath));

这句话就能拿到 至于怎么使用fileprovider获取文件地址这个我贴个链接 大家自行去学习观摩下,这个链接包含了解决android7.0 调用相机,播放视频,P图,安装APK详细的DEMO 和如何实现,大家自行观摩 感觉那位博友分享 

  1. https://blog.csdn.net/msn465780/article/details/59058088


2.如果出现下图的异常,就基本断定是7.0的路径问题。按照我上面给的方法就能解决


第二个问题,就是用户根本没有下载APK更新包的情况下,这时候需要我们给他下载,我这边调用的是系统的一个downloadManager 

代码如下 

private void initData(final String url1) {
    if (isPackageManagerDownloadEnabled()) {
        downloadPackage(url1);
    } else {
        openPackageManagerDownloadEnabledSettings();
    }
}

public void openPackageManagerDownloadEnabledSettings() {
    try {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.parse("package:com.android.providers.downloads"));
        mContext.startActivity(intent);
    } catch (ActivityNotFoundException e) {
        Log.e("RZMC", e.getMessage());
        mContext.startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
    } catch (Exception ex) {
        Log.e("RZMC", ex.getMessage());
    }
}

public boolean isPackageManagerDownloadEnabled() {
    boolean bOk = true;
    try {
        int state = mContext.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
        if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
                || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
                || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
            bOk = false;
        }
    } catch (Exception e) {
        Log.e("RZMC", e.getMessage());
        bOk = false;
    }
    return bOk;
}

/**
 * 下载前先移除前一个任务,防止重复下载
 *
 * @param downloadId
 */
public void clearCurrentTask(long downloadId) {
    DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
    try {
        dm.remove(downloadId);
    } catch (IllegalArgumentException ex) {
        ex.printStackTrace();
    }
}
public void downloadPackage(String strUri) {
    if (downloadID != 0) {
        clearCurrentTask(downloadID);
    }
    Uri uri = Uri.parse(strUri);
    DownloadManager downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);

    String strFileName = uri.getLastPathSegment();
    DownloadManager.Request request = new DownloadManager.Request(uri);

    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    request.setDestinationInExternalFilesDir(mContext, Environment.DIRECTORY_DOWNLOADS, strFileName);
    request.setTitle(strFileName);
    request.setVisibleInDownloadsUi(true);
    downloadID = downloadManager.enqueue(request);

    boolean bDownloading = false;
    try {
        DownloadManager.Query query = new DownloadManager.Query();
        query.setFilterByStatus(DownloadManager.STATUS_RUNNING);
        query.setFilterById(downloadID);
        Cursor cursor = downloadManager.query(query);
        while (cursor.moveToNext()) {
            if (cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI)).equalsIgnoreCase(strUri)) {
                bDownloading = true;
                break;
            }
        }
        if (cursor != null) {
            cursor.close();
        }
    } catch (Exception ex) {
        Log.e("RZMC", ex.getMessage());
    }
    if (bDownloading) {
        return;
    }

    File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
    if (!(dir.exists() && dir.isDirectory())) {
        dir.mkdirs();
    }


    try {

        DownloadReceiver.addDownloadID(downloadID);
    } catch (Exception ex) {
        Log.e("RZMC", ex.getMessage());
    }
}

这段代码我是封装好的 只要调接口接收一个下载的URL地址 即可下载

这里面还有一点小小的影响用户体验的问题,不知道大家有没有看出来,就是我这边做了个移除操作,

/**
 * 下载前先移除前一个任务,防止重复下载
 *
 * @param downloadId
 */
public void clearCurrentTask(long downloadId) {
    DownloadManager dm = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
    try {
        dm.remove(downloadId);
    } catch (IllegalArgumentException ex) {
        ex.printStackTrace();
    }
}

这个方法就是当正在下载APK的时候,如果用户再次点击下载APK的时候,如果没加这个方法的时候 是会同时下载2个一模一样的APK文件,这就导致了用户体验不佳。但是现在我这个方式是用户下载到一半,发现再次点击后 原先下载的没了 被我强制终止了,又重新开始下载 ,这个我希望大家能帮我提出点建议。


package com.rzdtapp.Receiver;

import android.Manifest;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.util.ArrayList;

public class DownloadReceiver extends BroadcastReceiver {
    private static ArrayList<Long> _downloadIDList = new ArrayList<Long>();


    public static void addDownloadID(long downloadID) {
        if (!_downloadIDList.contains(downloadID)) {
            Log.i("是否执行添加数据操作", "已添加数据");
            _downloadIDList.add(new Long(downloadID));
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
            long downloadID = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);

            Log.i("downloadID++++++++++", String.valueOf(downloadID));
            Log.i("_downloadIDList++++++++", String.valueOf(_downloadIDList));


            if (!_downloadIDList.contains(downloadID)) {
                return;
            }
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(downloadID);
            Cursor cursor = manager.query(query);
            if (cursor.moveToFirst()) {
                String strFileName = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                Log.i("RZJY", "下载成功:" + strFileName);
                Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show();


                Uri uri;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    uri = Uri.parse(strFileName);
                    strFileName = uri.getLastPathSegment();
                    Log.i("RZJY", "new path: " + uri.toString());
                    Intent intent2 = new Intent(Intent.ACTION_VIEW);
                    intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    intent2.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

//                    Uri uri1 = Uri.parse("content://com.rzdtapp.fileprovider/files/" + Environment.DIRECTORY_DOWNLOADS + "/" + strFileName);
                    String apkPath = Environment.getExternalStorageDirectory() + File.separator + "Android/data/你的APP/files/Download" + File.separator;
                    Log.i("RZJY", "7.0适配后的apkpath: " + apkPath);
                    File file = new File(apkPath + strFileName);
//                    File file = new File(String.valueOf(uri));
                    Uri apkUri = FileProvider.getUriForFile(context, "你的APP.fileprovider", file);
                    Log.i("RZJY", "7.0适配后的apkUrl: " + apkUri.toString());
                    //表明不是未知来源
                    intent2.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
                    intent2.setDataAndType(apkUri, "application/vnd.android.package-archive");
                    context.startActivity(intent2);
                } else {
                    uri = Uri.parse(strFileName);
                    Log.i("RZJY", "old path: " + uri.toString());
                    Intent install = new Intent(Intent.ACTION_VIEW);
                    //表明不是未知来源
                    install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
                    install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    install.setDataAndType(uri, "application/vnd.android.package-archive");

                    context.startActivity(install);
                }

            } else {
                Log.i("RZJY", "下载失败");
                Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
            }
            if (cursor != null) {
                cursor.close();
            }
            _downloadIDList.remove(downloadID);
        } else if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())) {
            Intent intent1 = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
            intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent1);
        }
    }
}

我写了个广播来接收并执行回调,这里来接收下载完后的回调,安装APK, 同样这里做了对android7.0的适配 这样到这就结束了,希望能帮到大家,关于冷更新的功能实现和异常修复之路。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值