到目前为止,我一直使用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")));
修改
尝试使用下面CommonsWare建议的FileProvider
...
清单:
<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中没有错误。
答案 0 :(得分:4)
主屏幕正在呈现您的应用小部件。主屏幕无法访问您应用的内部存储空间。
相反,请尝试using FileProvider
从内部存储中提供文件,并在Uri
来电中FileProvider
提供setImageViewUri()
。
更新:我忘记了FileProvider
的权限限制。我不知道通过Uri
向主屏幕流程授予RemoteViews
权限的可靠方法。理想情况下,您只允许导出提供程序,但FileProvider
不支持该提供程序。
您的选择是:
编写您自己的流媒体ContentProvider
,类似于this one,其中导出了提供商,因此任何人都可以使用Uri
值。
将文件放在外部存储设备上,而不是内部存储设备上,然后希望主屏幕具有READ_EXTERNAL_STORAGE
或WRITE_EXTERNAL_STORAGE
权限。
确保您的图片总是相当小,因此您的IPC交易保持低于1MB的限制,并采用原始的setImageViewBitmap()
方法。
我为忘记FileProvider
不允许导出提供程序而道歉。
答案 1 :(得分:2)
这个答案可能会(相当)迟到。
通过使用允许导出提供程序的略微修改的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
设置为 true :
<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));
答案 2 :(得分:0)
今天遇到了这个问题,但发现授予对任何启动器应用程序访问权限的安全漏洞比导出整个提供程序要小:)
因此,在将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);
}
}