Android: загрузка растрового изображения из локального хранилища в виджет приложения (RemoteViews)
До сих пор я загружал растровое изображение в свой RemoteViews
напрямую используя remoteViews.setImageViewBitmap()
, В целом работает нормально.
Но у пары пользователей есть проблемы, и я думаю, что это при загрузке в растровом изображении, которое является очень большим. Я все равно кэширую растровое изображение в локальное хранилище, поэтому моя идея состоит в том, чтобы использовать setImageViewUri()
вместо этого, как рекомендуется в другом месте.
Но я не могу заставить его работать... Я просто получаю пустой виджет. Следующий фрагмент иллюстрирует то, что я делаю (опуская некоторый контекст, но, надеюсь, оставляя достаточно)...
// Bitmap bmp is fetched from elsewhere... and then...
FileOutputStream fos = context.openFileOutput("img.png", Context.MODE_PRIVATE);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
// ... later ...
// this works...
remoteViews.setImageViewBitmap(R.id.imageView, bmp);
// but this doesn't...
remoteViews.setImageViewUri(R.id.imageView, Uri.fromFile(new File(context.getFilesDir().getPath(), "img.png")));
РЕДАКТИРОВАТЬ
Пытаясь использовать FileProvider
как предложено CommonsWare ниже...
Manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.xyz.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:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="cache" path="."/>
</paths>
код:
File imagePath = new File(context.getFilesDir(), ".");
File newFile = new File(imagePath, "img.png"); // img.png created as above, in top level folder
remoteViews.setImageViewUri(R.id.imageView, FileProvider.getUriForFile(context, "com.xyz.fileprovider", newFile));
Но все равно я остался с пустым виджетом, как и раньше, и без ошибки в logcat.
4 ответа
Виджет вашего приложения отображается на главном экране. Главный экран не может получить доступ к внутренней памяти вашего приложения.
Вместо этого попробуйте использоватьFileProvider
обслуживать файлы из внутреннего хранилища, с Uri
предоставлено FileProvider
в вашем setImageViewUri()
вызов.
ОБНОВЛЕНИЕ: я забыл о FileProvider
Ограничения разрешений. Я не знаю надежного способа предоставления разрешений Uri
к процессу домашнего экрана через RemoteViews
, В идеале вы бы просто разрешили экспортировать провайдера, но FileProvider
не поддерживает это.
Ваши варианты, то, являются:
Напишите свой собственный поток
ContentProvider
, родственный этому, где поставщик экспортируется, так что каждый может потреблятьUri
ценности.Поместите файл на внешнее хранилище, а не на внутреннее хранилище, затем надейтесь, что на главном экране есть
READ_EXTERNAL_STORAGE
или жеWRITE_EXTERNAL_STORAGE
разрешения.Убедитесь, что ваше изображение всегда достаточно маленькое, чтобы ваша транзакция IPC оставалась ниже предела в 1 МБ, и продолжайте использовать оригинал
setImageViewBitmap()
подход.
Я прошу прощения за то, что забыл о FileProvider
не позволяет провайдеру быть экспортированным.
Этот ответ может быть (довольно) немного поздно.
Это можно сделать, используя слегка модифицированный FileProvider
это позволяет провайдеру быть экспортированным. Вот что вам нужно сделать:
- Скачать исходный код
FileProvider
отсюда - Скопируйте файл в ваш проект, например:
com.xyz.fileprovider.FileProvider.java
изменять
attachInfo(Context context, ProviderInfo info)
так что не бросайSecurityException
, если вы объявите своего провайдера как экспортированного:@Override public void attachInfo(Context context, ProviderInfo info) { super.attachInfo(context, info); // Sanity check our security // Do not throw an exception if the provider is exported /* if (info.exported) { throw new SecurityException("Provider must not be exported"); } */ if (!info.grantUriPermissions) { throw new SecurityException("Provider must grant uri permissions"); } mStrategy = getPathStrategy(context, info.authority); }
Обновите файл манифеста, чтобы он ссылался на ваш измененный
FileProvider
и установитьandroid:exported
к истине:<provider> android:name="com.xyz.fileprovider.FileProvider" android:authorities="com.xyz.fileprovider" android:exported="true" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
Обновите свой код, чтобы вы использовали
com.xyz.fileprovider.FileProvider
вместоandroid.support.v4.content.FileProvider
:// Bitmap bmp is fetched from elsewhere... and then... FileOutputStream fos = context.openFileOutput("img.png", Context.MODE_PRIVATE); bmp.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); fos.close(); // ... later ... // this should work... File imagePath = new File(context.getFilesDir(), "."); File newFile = new File(imagePath, "img.png"); // img.png created as above, in top level folder remoteViews.setImageViewUri(R.id.imageView, Uri.parse("")); // this is necessary, without it the launcher is going to cache the Uri we set on the next line and effectively update the widget's image view just once!!! remoteViews.setImageViewUri(R.id.imageView, com.xyz.fileprovider.FileProvider.getUriForFile(context, "com.xyz.fileprovider", newFile));
Метод
remoteViews.setImageViewUri
дополнительно имеет ограничение размера растрового изображения для
ImageView
(примерно 1-2 Мб). Таким образом, вы получите ошибки или виджет будет прозрачным.
Так
remoteViews.setImageViewBitmap()
это хорошая практика, но вы слишком создаете процедуру с
BitmapFactory.Options inSampleSize
и попробуйте привязать растровое изображение к размеру экрана и поймать
OutOfMemoryError
.
Сегодня наткнулся на это, но решил, что предоставление доступа к любому приложению запуска - меньшая дыра в безопасности, чем экспорт всего провайдера:)
Итак, прямо перед установкой uri для удаленных представлений я предоставляю доступ к URI для каждого установленного домашнего экрана:
protected void bindTeaserImage(Context context, final RemoteViews remoteViews, final TeaserInfo teaserInfo) {
final Uri teaserImageUri = getTeaserImageUri(teaserInfo); // generates a content: URI for my FileProvider
grantUriAccessToWidget(context, teaserImageUri);
remoteViews.setImageViewUri(R.id.teaser_image, teaserImageUri);
}
protected void grantUriAccessToWidget(Context context, Uri uri) {
Intent intent= new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}