Как открыть файл APK для всех версий Android
До сих пор был простой способ установить APK-файл, используя это намерение:
final Intent intent=new Intent(Intent.ACTION_VIEW)
.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
Но если ваше приложение предназначено для Android API 24 и выше (Nougat - 7.0), и вы запускаете этот код на нем или новее, вы получите исключение, как показаноВот , например:
android.os.FileUriExposedException: file:///storage/emulated/0/sample.apk exposed beyond app through Intent.getData()
Эта проблемаПоэтому я сделал то, что мне сказали: используйте класс FileProvider библиотеки поддержки как таковой:
final Intent intent = new Intent(Intent.ACTION_VIEW)//
.setDataAndType(android.support.v4.content.FileProvider.getUriForFile(context,
context.getPackageName() + ".provider", apkFile),
"application/vnd.android.package-archive").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
манифест:
<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>
Рез / XML / provider_paths.xml :
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--<external-path name="external_files" path="."/>-->
<external-path
name="files_root"
path="Android/data/${applicationId}"/>
<external-path
name="external_storage_root"
path="."/>
</paths>
Но теперь он работает только на Android Nougat. На Android 5.0 выдает исключение: ActivityNotFoundException.
Что я пробовалЯ могу просто добавить проверку версии ОС Android и использовать любой из этих методов, но, как я уже прочитал, должен использоваться один метод: FileProvider.
Итак, я попытался использовать свой собственный ContentProvider, который действует как FileProvider, но я получил то же исключение, что и FileProvider библиотеки поддержки.
Вот мой код для этого:
final Intent intent = new Intent(Intent.ACTION_VIEW)
.setDataAndType(OpenFileProvider.prepareSingleFileProviderFile(apkFilePath),
"application/vnd.android.package-archive")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
OpenFileProvider.java
public class OpenFileProvider extends ContentProvider {
private static final String FILE_PROVIDER_AUTHORITY = "open_file_provider";
private static final String[] DEFAULT_PROJECTION = new String[]{MediaColumns.DATA, MediaColumns.DISPLAY_NAME, MediaColumns.SIZE};
public static Uri prepareSingleFileProviderFile(String filePath) {
final String encodedFilePath = new String(Base64.encode(filePath.getBytes(), Base64.URL_SAFE));
final Uri uri = Uri.parse("content://" + FILE_PROVIDER_AUTHORITY + "/" + encodedFilePath);
return uri;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public String getType(@NonNull Uri uri) {
String fileName = getFileName(uri);
if (fileName == null)
return null;
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileName);
}
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
final String fileName = getFileName(uri);
if (fileName == null)
return null;
final File file = new File(fileName);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final String filePath = getFileName(uri);
if (filePath == null)
return null;
final String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
final MatrixCursor ret = new MatrixCursor(columnNames);
final Object[] values = new Object[columnNames.length];
for (int i = 0, count = columnNames.length; i < count; ++i) {
String column = columnNames[i];
switch (column) {
case MediaColumns.DATA:
values[i] = uri.toString();
break;
case MediaColumns.DISPLAY_NAME:
values[i] = extractFileName(uri);
break;
case MediaColumns.SIZE:
File file = new File(filePath);
values[i] = file.length();
break;
}
}
ret.addRow(values);
return ret;
}
private static String getFileName(Uri uri) {
String path = uri.getLastPathSegment();
return path != null ? new String(Base64.decode(path, Base64.URL_SAFE)) : null;
}
private static String extractFileName(Uri uri) {
String path = getFileName(uri);
return path;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0; // not supported
}
@Override
public int delete(@NonNull Uri uri, String arg1, String[] arg2) {
return 0; // not supported
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
return null; // not supported
}
}
манифест
<provider
android:name=".utils.apps_utils.OpenFileProvider"
android:authorities="open_file_provider"
android:exported="true"
android:grantUriPermissions="true"
android:multiprocess="true"/>
ВопросыПочему это происходит?
Что-то не так с созданным пользователем провайдером? Нужен ли флаг? В порядке ли создание URI? Должен ли я добавить имя пакета текущего приложения к нему?
Должен ли я просто добавить проверку, если это Android API 24 и выше, и если это так, использовать провайдер, а если нет, использовать нормальныйUri.fromFile вызов ? Если я использую это, библиотека поддержки фактически теряет свое назначение, потому что она будет использоваться для более новых версий Android ...
Будет ли библиотеки поддержки FileProvider достаточно для всех случаев использования (конечно, если у меня есть разрешение на внешнее хранилище)?