Android:将位图从本地存储加载到app小部件(RemoteViews)

时间:2016-02-01 19:42:06

标签: android android-appwidget remoteview android-remoteview

到目前为止,我一直使用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中没有错误。

3 个答案:

答案 0 :(得分:4)

主屏幕正在呈现您的应用小部件。主屏幕无法访问您应用的内部存储空间。

相反,请尝试using FileProvider从内部存储中提供文件,并在Uri来电中FileProvider提供setImageViewUri()

更新:我忘记了FileProvider的权限限制。我不知道通过Uri向主屏幕流程授予RemoteViews权限的可靠方法。理想情况下,您只允许导出提供程序,但FileProvider不支持该提供程序。

您的选择是:

  1. 编写您自己的流媒体ContentProvider,类似于this one,其中导出了提供商,因此任何人都可以使用Uri值。

  2. 将文件放在外部存储设备上,而不是内部存储设备上,然后希望主屏幕具有READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限。

  3. 确保您的图片总是相当小,因此您的IPC交易保持低于1MB的限制,并采用原始的setImageViewBitmap()方法。

  4. 我为忘记FileProvider不允许导出提供程序而道歉。

答案 1 :(得分:2)

这个答案可能会(相当)迟到。

通过使用允许导出提供程序的略微修改的FileProvider,可以实现此功能。以下是您需要做的事情:

  1. here
  2. 下载FileProvider的源代码
  3. 将文件复制到您的项目,例如致:com.xyz.fileprovider.FileProvider.java
  4. 如果您将提供商声明为已导出,则修改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);
    }
    
  5. 更新您的清单文件,以便它引用您修改后的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>
    
  6. 更新您的代码,以便您使用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);
    }
}