Android 7.0: install .apk

最後是試出來了,中間有幾個問題,就是安裝是從內部 cache dir 會有問題,安裝程式會顯示:

there was a problem parsing the package

改成 external cache dir 就OK。所以 xml 檔一定要使用下面這一組:

<external-path name="external_files" path="." />

Android Manifest中加入權限

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

取得 SD Card 權限:

//Android 6.0以上需要判斷使用者是否願意開啟儲存(WRITE_EXTERNAL_STORAGE)的權限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    CheckStoragePermission();
}

private void CheckStoragePermission() {
    //Android 6.0檢查是否開啟儲存(WRITE_EXTERNAL_STORAGE)的權限,若否,出現詢問視窗
    if (ContextCompat.checkSelfPermission(this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this,
            Manifest.permission.READ_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {//Can add more as per requirement
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE},
                20);
    }
}

@Override
//Android 6.0以上 接收使用者是否允許使用儲存權限
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {
    switch (requestCode) {
        case 20: {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            } else {
                CheckStoragePermission();
            }
            return;
        }
    }
}

附註:我測試了多台Android 6.0 + Android 7.0 裝置,發現提升權限非必需,完全沒有提升在各裝置都可以正常執行。

附上我在使用中的 install apk function:

public void installApk(Context context, File apkFile) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    //Log.d(TAG, "filepath:" + apkFile.getAbsolutePath());

    //如果没有设置SDCard写权限,或者没有sdcard,apk文件保存在内存中,需要授予权限才能安装
    try {
        String[] command = {"chmod", "777", apkFile.toString()};
        ProcessBuilder builder = new ProcessBuilder(command);
        builder.start();
    } catch (IOException ignored) {
    }

    if(Build.VERSION.SDK_INT>=24) {
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri uri = FileProvider.getUriForFile(context, "tw.thinkingsoftware.geofence.provider", apkFile);
        //intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
        //intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        //Log.d(TAG, "uri:" + uri.getPath());
    } else {
        Uri uri = Uri.fromFile(apkFile);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        //Log.d(TAG, "uri:" + uri.getPath());
    }
    context.startActivity(intent);
}

 


1 manifest 中添加代码

<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>

2 res/xml/provider_paths.xml

  <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-path
            name="publicDir" path="/"/>
    </paths>

3 install

public static void installApk(Context context, File apkFile) {

    Intent intent = new Intent(Intent.ACTION_VIEW);
    Uri uri = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        uri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", apkFile);
        intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    } else {
        uri = Uri.fromFile(apkFile);
    }
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setDataAndType(uri, "application/vnd.android.package-archive");
    context.startActivity(intent);
}

you need this url

https://developer.android.com/reference/android/os/FileUriExposedException.html

https://developer.android.com/reference/android/support/v4/content/FileProvider.html

the code solution my problem. hope help you


 

使用Intent安装APK方法(兼容Android N)

Android N之前,通过Intent安装一个APK代码差多不是下面这个样子的:

Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
File apkFile = new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk";
install.setDataAndType(Uri.fromFile(apkFile)), "application/vnd.android.package-archive");
startActivity(install)

上面代码在Android N(API 26)之前是没有问题的,但是从Android 7.0开始,系统修改了安全机制: 限定应用在默认情况下只能访问自身应用数据。所以当我们想通过File对象访问其它package数据时,就需要借助于ContentProvider、FileProvider这些组件,否则会报FileUriExposedException异常。

相应的,显然上面的代码在Android N中会有FileUriExposedException的问题,所以我们需要借助FileProvider做出下面的修改。

使用FileProvider的步骤

添加Provider到AndroidManifest.xml

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="应用包名.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">

    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>

</provider>

创建@xml/file_paths对应的资源文件

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="download"
        path="" />
    <external-files-path
        name="Download"
        path="" />
</paths>

paths里面用来放你需要访问的非本应用路径,只有通过这里申明后,才能在程序中使用不报错。

  • files-path 对应 Context.getFilesDir()
  • cache-path 对应 getCacheDir()
  • external-path 对应 Environment.getExternalStorageDirectory()
  • external-files-path 对应 Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)
  • external-cache-path 对应 Context.getExternalCacheDir()

具体其中的path和name就需要你自己来根据需求确定了。

应用代码

Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
File apkFile = new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk";

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    Uri contentUri = FileProvider.getUriForFile(context, "应用报名.fileProvider", apkFile);
    install.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
    install.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
}

startActivity(install);

如何在Android7.0系统下通过Intent安装apk

Android系统升级到7.0之后,安全性提高了不少,过去我们通常是使用这样的代码进行apk的安装操作。

1
2
3
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
context.startActivity(intent);

但是在Android7.0的系统上,运行这段代码,会报如下错误。

Caused by: android.os.FileUriExposedException

原因是,安卓官方为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问 (0700)。此设置可防止私有文件的元数据泄漏,如它们的大小或存在性.

传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider

1.定义一个FileProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<manifest>
    ...
    <application>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.mydomain.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            ...
        </provider>
        ...
    </application>
</manifest>

2.添加可用权限的文件目录

在res目录下,增加xml文件夹,并新建一个名为 file_paths.xml 的文件。文件内容格式如下:

1
2
3
4
<paths xmlns:android="http://schemas.android.com/apk/res/android">
   		<files-path name="name1" path="test1" />
    	...
</paths>

标签下面必须包含至少包含以下标签中的一个或者多个。

files-path

1
<files-path name="name1" path="test1" />

表示Context.getFilesDir()目录或者其子目录。

示例 : /data/data/com.chen.gradle/files/test1

cache-path

1
<cache-path name="name2" path="test2" />

表示Context.getCacheDir()目录或者其子目录。

示例 : /data/data/com.chen.gradle/cache/test2

external-path

1
<external-path name="name3" path="test3" />

表示Environment.getExternalStorageDirectory()目录或者其子目录。

示例 : /storage/emulated/0/test3

external-files-path

1
<external-files-path name="name4" path="test4" />

表示Context.getExternalFilesDir(null)目录或者其子目录。

示例 : /storage/emulated/0/Android/data/com.chen.gradle/files/test4

external-cache-path

1
<external-cache-path name="name5" path="test5" />

表示Context.getExternalCacheDir()目录或者其子目录。

示例 : /storage/emulated/0/Android/data/com.chen.gradle/cache/test5

3.增加到provider

通过<meta-data>标签将上面的filepath添加到provider当中。

1
2
3
4
5
6
7
8
9
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

4.通过provider生成Uri

1
2
3
4
File imagePath = new File(Context.getFilesDir(), "test1");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

5.赋予临时权限给Uri

1
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

最终安装apk的代码变成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void installApk(Context context, String apkPath) {
    if (context == null || TextUtils.isEmpty(apkPath)) {
        return;
    }

    
    File file = new File(apkPath);
    Intent intent = new Intent(Intent.ACTION_VIEW);

    //判读版本是否在7.0以上
    if (Build.VERSION.SDK_INT >= 24) {
        //provider authorities
        Uri apkUri = FileProvider.getUriForFile(context, "com.mydomain.fileprovider", file);
        //Granting Temporary Permissions to a URI
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
    } else {
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
    }

    context.startActivity(intent);
}

参考文献

Android 7.0 行为变更


相關文章:

下载安装APK(兼容Android7.0)
https://www.jianshu.com/p/577816c3ce93

如何在Android7.0系统下通过Intent安装apk
http://www.czhzero.com/2016/12/21/how-to-install-apk-on-Android7-0/

[ANDROID] DOWNLOADMANAGER下載APK並自動開啟安裝畫面(ANDROID 6.0 , 7.0皆可運作)
https://solinariwu.blogspot.tw/2017/03/android-downloadmanagerapkandroid-60-70.html

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *