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

Posted

技术标签:

【中文标题】Android:将位图从本地存储加载到应用小部件(RemoteViews)【英文标题】:Android: loading bitmap from local storage into app widget (RemoteViews) 【发布时间】:2016-05-10 09:35:20 【问题描述】:

到目前为止,我一直使用remoteViews.setImageViewBitmap() 直接将位图加载到我的RemoteViews 中。总体来说运行良好。

但是有几个用户遇到了问题,我认为是在加载非常大的位图时。无论如何,我已经将位图缓存到本地存储,所以我的想法是改用setImageViewUri(),正如其他地方所推荐的那样。

但我无法让它工作......我只是得到一个空白小部件。下面的 sn-p 说明了我在做什么(省略了一些上下文,但希望留下足够的内容)......

// 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 中没有错误。

【问题讨论】:

【参考方案1】:

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

请尝试使用using FileProvider 从内部存储中提供文件,并在您的setImageViewUri() 调用中使用FileProvider 提供的Uri

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

那么,您的选择是:

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

    把文件放到外部存储,而不是内部存储,然后希望主屏幕有READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限。

    确保您的图像始终相当小,以使您的 IPC 事务保持在 1MB 限制以下,并使用您原来的 setImageViewBitmap() 方法。

对于忘记FileProvider 不允许导出提供程序,我深表歉意。

【讨论】:

我在问题中添加了基于此方法的尝试。请你能解释一下我哪里出错了吗?谢谢。 @drmrbrewer:我会在File 构造函数中摆脱path=".",并摆脱"."。两者都不需要,也可能会干扰FileProvider。我还建议您始终使用getFilesDir(),替换openFileOutput()。但是,我怀疑您的大问题在于ContentProvider 权限——我编辑了我的答案。 参考您的 UPDATE,将本地位图添加到 RemoteViews 中似乎是一项艰巨的工作……为什么这么难?我遇到的问题不在于 one 位图的大小,而更多地在于 two 的大小。当大约同时刷新两个小部件时,似乎会出现此问题,因此我认为大小的总和会导致问题。当他们自己刷新“大”小部件时,这很好。会发生这种情况吗...合并后的大小是问题吗?我希望尝试使用setImageViewUri() 快速直接加载,以测试我的假设。 @drmrbrewer:“为什么这么难?” -- 标准 IPC 事务有 1MB 的限制,例如将您的 RemoteViews 从您的进程传递到操作系统进程,然后从那里传递到主屏幕进程。 “这会发生吗……合并后的大小是个问题?” - 我不知道,但我不能完全排除它。 “我希望尝试使用 setImageViewUri() 快速直接加载,以测试我的假设。” -- 然后将文件暂时放到外部存储上,看看会发生什么。 我试图绕过 1MB 的限制,方法是使用“链接”到本地存储上已有的位图,而不是在RemoteViews 中添加大量位图对象。我只是认为在 RemoteViews 中指定要使用的本地文件的位置应该更容易,而不必跳过箍或使用外部存储(需要用户不喜欢授予的额外权限)。【参考方案2】:

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

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

    从here下载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));
    

【讨论】:

非常感谢您的详细回答。我可以让它毫无问题地工作。【参考方案3】:

今天遇到了这个问题,但认为授予对任何启动器应用程序的访问权限是一个比导出整个提供程序更小的安全漏洞 :)

因此,在将 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);
    

【讨论】:

【参考方案4】:

方法remoteViews.setImageViewUri 还对ImageView 的位图大小有限制(大约1-2 Mb)。所以你会得到错误或小部件将是透明的。

所以remoteViews.setImageViewBitmap() 是一种很好的做法,但是您也必须使用BitmapFactory.Options inSampleSize 创建过程并尝试将位图限制到屏幕大小并捕获OutOfMemoryError

【讨论】:

以上是关于Android:将位图从本地存储加载到应用小部件(RemoteViews)的主要内容,如果未能解决你的问题,请参考以下文章

如何将图像从 url 加载到小部件的 android 远程视图中

Android 小部件位图大小

Android:如何将位图写入内部存储

Android Dev:从存储在 SD 卡上的临时文件创建位图,设置 ImageView,然后将其存储在本地

如何从我的应用程序将小部件添加到 Android 主屏幕?

将 URL 中的 ImageView 加载到主屏幕小部件的 RemoteView 中