App 在线更新适配(Android 6.0、Android7.0,Android8.0)

前言

本篇仅介绍关于代码更新App所需要考虑的适配问题,不介绍如何下载App文件,也不介绍关于Android6.0动态权限申请的流程(apk文件存放于外部存储上,需要Manifest.permission.WRITE_EXTERNAL_STORAGE 权限)。 Android8.0添加了“未知来源的应用权限”的权限(Manifest.permission.REQUEST_INSTALL_PACKAGES)。本篇暂不做详细说明。

也就是说,本文前提:

  1. 该App已获取WRITE_EXTERNAL_STORAGE 和 REQUEST_INSTALL_PACKAGES 权限。 Android6.0之后的动态权限申请,小编推荐使用AndPermission,AndPermission中文文档
  2. apk文件已下载完成, 路径为 String mSavePath = Environment.getExternalStorageDirectory().getPath() + File.separator + “apk” + File.separator; 文件名称为 String mFileName = “test.apk”;

代码

调用apk安装工具类AppUtils.installApp(Context context, File file)

1
2
3
4
5
6
7
/**
* 安装APK文件
*/
private void installApk() {
File apkfile = new File(mSavePath, mFileName);
AppUtils.installApp(context, apkfile);
}

AppUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    public final class AppUtils {

private AppUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}



/**
* 安装App(支持6.0)
*
* @param context 上下文
* @param filePath 文件路径
*/
public static void installApp(Context context, String filePath) {
installApp(context, FileUtils.getFileByPath(filePath));
}

/**
* 安装App(支持6.0)
*
* @param context 上下文
* @param file 文件
*/
public static void installApp(Context context, File file) {
if (!FileUtils.isFileExists(file)) return;
context.startActivity(IntentUtils.getInstallAppIntent(context, file));
}

/**
* 安装App(支持6.0)
*
* @param activity activity
* @param filePath 文件路径
* @param requestCode 请求值
*/
public static void installApp(Activity activity, String filePath, int requestCode) {
installApp(activity, FileUtils.getFileByPath(filePath), requestCode);
}

/**
* 安装App(支持6.0)
*
* @param activity activity
* @param file 文件
* @param requestCode 请求值
*/
public static void installApp(Activity activity, File file, int requestCode) {
if (!FileUtils.isFileExists(file)) return;
activity.startActivityForResult(IntentUtils.getInstallAppIntent(activity , file), requestCode);
}

}

IntentUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* <pre>
* author : Haitao
* blog : http://blog.nhtzj.com
* time : 2018/2/7
* desc : 意图相关工具类
* version: 2.0
* </pre>
*/
public class IntentUtils {

private IntentUtils() {
throw new UnsupportedOperationException("u can't fuck me...");
}

/**
* 获取安装App(支持6.0)的意图
*
* @param filePath 文件路径
* @return intent
*/
public static Intent getInstallAppIntent(Context context , String filePath) {
return getInstallAppIntent(context , FileUtils.getFileByPath(filePath));
}

/**
* 获取安装App(支持6.0)的意图
*
* @param file 文件
* @return intent
*/
public static Intent getInstallAppIntent(Context context , File file) {
if (file == null) return null;
Intent intent = new Intent(Intent.ACTION_VIEW);
String type;

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 小于Android 6.0
type = "application/vnd.android.package-archive";
} else {
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FileUtils.getFileExtension(file));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //大于等于Android 7.0
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(context , BuildConfig.APPLICATION_ID + ".provider", file);
intent.setDataAndType(contentUri, type);
} else {
intent.setDataAndType(Uri.fromFile(file), type);
}
return intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}

}

FileUtils.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public final class FileUtils {

/**
* 根据文件路径获取文件
*
* @param filePath 文件路径
* @return 文件
*/
public static File getFileByPath(String filePath) {
return isSpace(filePath) ? null : new File(filePath);
}

private static boolean isSpace(String s) {
if (s == null) return true;
for (int i = 0, len = s.length(); i < len; ++i) {
if (!Character.isWhitespace(s.charAt(i))) {
return false;
}
}
return true;
}

/**
* 获取全路径中的文件拓展名
*
* @param file 文件
* @return 文件拓展名
*/
public static String getFileExtension(File file) {
if (file == null) return null;
return getFileExtension(file.getPath());
}

/**
* 获取全路径中的文件拓展名
*
* @param filePath 文件路径
* @return 文件拓展名
*/
public static String getFileExtension(String filePath) {
if (isSpace(filePath)) return filePath;
int lastPoi = filePath.lastIndexOf('.');
int lastSep = filePath.lastIndexOf(File.separator);
if (lastPoi == -1 || lastSep >= lastPoi) return "";
return filePath.substring(lastPoi + 1);
}

}

适配Android 7.0

在Android 7.0上,对文件的访问权限作出了修改,不能在使用file://格式的Uri 访问文件 ,Android 7.0提供 FileProvider,应该使用这个来获取apk地址

在res 目录下,新建一个xml 文件夹,在xml 下面创建一个文件provider_paths文件

provider_paths.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>

之后在清单文件中添加

1
2
3
4
5
6
7
8
9
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>

Android 8.0适配

Android 8.0适配请参考Android app 在线更新那点事儿(适配Android6.0、7.0、8.0)的第5段:“五、适配Android 8.0:未知来源的应用权限”。

该篇介绍了使用SDK自带的下载类DownloadManager来下载apk。
而小编这篇是借助三方框架okhttp直接下载到指定位置,故有前言中提到的第二个条件。

坚持原创技术分享,您的支持是对我最大的鼓励!