应用程序小部件setImageViewUri无法更新图像

12

我有一个应用小部件,只包含一个ImageView。我重新绘制了这个图片并将其存储为PNG格式在应用的私有内存中,然后我使用URI设置RemoteViews的图像(widget.setImageViewUri(R.id.widget_icon, uri))而不是发送位图本身,因为极少情况下会出现“FAILED BINDER TRANSACTION”错误。

问题是,随着时间的推移,图片发生变化但小部件的图片没有改变。我用Root Explorer检查了保存的PNG文件,它被正确更新了。但是正确的图像没有显示出来。每个小部件都显示添加到主屏幕时的图像。如果我使用setImageViewBitmap(...),它能正常工作,除了极少出现的“FAILED BINDER TRANSACTION”错误。

我从服务中使用下面的方法更新小部件。它在Android 2.3.7上和运行2.2的模拟器上都不起作用。问题可能是什么?它是否被缓存了?

public void updateWidget(Context context) {
  // redraws the widget's image
  updateIcon();

  OutputStream stream;
  try {
    stream = context.openFileOutput("icon.png", Context.MODE_WORLD_READABLE);
  }
  catch (FileNotFoundException ex) {
    ex.printStackTrace();
    return;
  }

  m_Icon.compress(CompressFormat.PNG, 100, stream);

  try {
    stream.close();
  }
  catch (IOException ex) {
    ex.printStackTrace();
  }

  AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
  int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, WidgetProvider.class));

  Intent intent = new Intent(context, MyDialog.class);
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

  PendingIntent clickIntent = PendingIntent.getActivity(context, 0, intent, 0);

  File file = new File(context.getFilesDir().getAbsolutePath() + File.separator + "icon.png");
//      Uri uri = Uri.fromFile(file);

  // update all widgets on home screen
  for (int wid : appWidgetIds) {
    RemoteViews widget = new RemoteViews(context.getPackageName(), R.layout.widget);
    widget.setOnClickPendingIntent(R.id.widget_layout, clickIntent);

    // === EDIT ===
    // Uri.fromFile(file); does not work correctly on android 2.1, only 2.2 and up
    // widget's image will disappear
//        widget.setImageViewUri(R.id.widget_icon, uri);
    widget.setImageViewUri(R.id.widget_icon, Uri.parse(file.getPath()));

    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, wid);

    appWidgetManager.updateAppWidget(wid, widget);
  }
}

编辑: 我还尝试了使用自定义ContentProvider并设置 "content://..." uri,但结果相同。小部件显示从添加到主屏幕的时间开始的图像,并且不会随时间更新。

logcat没有显示任何错误。


您正在为所有图标使用相同的URI(相同的文件名),这就是问题所在。URI必须按文件、资源等唯一。系统不会更新可绘制对象并使用第一个加载的对象,这是预期的行为。我认为最好使用FileProvider,并提供多个文件。您可以删除过时的文件,并使用新的URI更新小部件。 - Anis BEN NSIR
@AnisBENNSIR 这个问题在五年前被提出并解决了... - shelll
抱歉,但赏金仍处于开放状态...也许我的评论对新用户有用,因为被接受的答案并不是最佳解决方案。 - Anis BEN NSIR
3个回答

9
如果Uri保持不变,似乎Android不会更新图像。不过我找到了一个小技巧来解决这个问题。只需在widget.setImageViewUri(R.id.widget_icon, uri);之前调用widget.setImageViewUri(R.id.widget_icon, Uri.parse(""));
虽然它会调用两次setImageViewUri(),但似乎可以正常工作。不过如果有更好的解决方法,我很乐意听取建议,因为我自己也遇到了这个问题。

我试图做这件事,但是我发送了一个空的Uri,导致程序崩溃。使用空字符串解决了这个问题,谢谢!还有一种缓存机制,如果需要的话应该可以关闭 :( - shelll
首先将uri设置为空,然后再设置正确的uri可以解决问题,但这会导致logcat在每次小部件更新时产生大量警告,因为空uri无效...所以这只是一个部分解决方案,但它有效。 - shelll
是的,这就是使用这种解决方案的问题。就像我说的那样,这是一种hack方法,我很想找到更好的方法,但我只是不知道有更好的方法来做到这一点。 - Michell Bak
这种方法还存在另一个问题。在某些更新时,小部件会闪烁。我认为我必须保存图像两次,并每次分配第二个图像的URI。 - shelll
我必须说,我没有经历过那种情况。 - Michell Bak
1
我们距离这个答案已经接近6年了,这个方法一直对我很有效,但最近我发现在某些情况下(Android O + Pixel launcher),小部件更新过程似乎永远无法到达第二个(真正的)URI加载...它在第一个(虚假的)URI之后失败,但没有加载真正的URI,从而留下一个空白的小部件。使用相同的代码、相同的设备和相同的操作系统,但不同的启动器(Nova),它仍然可以正常工作。所以这可能是启动器特定的,但可能是由于使用类似于使用无效URI的笨拙解决方案导致的。一定有更好的方法吧? - drmrbrewer

2

Well it looks like setImageViewUri caches images and does not refresh them if the have the same name.

Not the most elegant solution but it works, every second time it will use a different file name.

String filename = new String("bitmap-1.png");
// rotate files, due to not being refreshed if same name 
if ( context.deleteFile(filename) ) {
   filename = new String("bitmap-0.png");
}
FileOutputStream out = context.openFileOutput(filename, Context.MODE_WORLD_READABLE);
bitmap.compress(Bitmap.CompressFormat.PNG, 80, out);
file = context.getFileStreamPath(filename);
out.close();
updateViews.setImageViewUri(R.id.image1,Uri.parse(file.toString())); 


这将增加存储使用量,请考虑代码是否被许多小部件使用,这不是一个好的实践。相反,使用接受的答案并忘记日志记录。 - YEH
请问 @shelll,您能否详细说明一下您正在使用的解决方案?您只是将两个相同的位图副本存储在不同的名称下,然后类似于已接受的答案,连续两次执行 widget.setImageViewUri(),分别指向这两个位图吗?也就是说,与已接受的答案相同,只是您使用指向相同位图副本的 URI,因此实际上是快速连续加载了相同的图像两次? - drmrbrewer
@drmrbrewer 是的,我正在做这件事。我用不同的名称两次存储位图。这些图像很小,因此没有存储问题。没有像“空URL”技巧那样的闪烁。而且在logcat中也没有警告。 - shelll
好的,谢谢@shelll。我打算尝试实现这个方法。正如我在已接受的解决方案的评论中提到的那样,它对我来说一直很好用(没有闪烁),直到我遇到了特定的设备/操作系统/启动器组合,所以我需要尝试一些不同的东西。你为什么不只在两个文件名之间旋转,每次只保留一个位图(删除另一个),并且只使用一个 setImageViewUri() 而不是两个呢?除了增加复杂性(如果图像本身很小,则收益很小)之外还有其他原因吗? - drmrbrewer
1
@drmrbrewer,我不想为这样一个小问题制作复杂的逻辑。一直保存两张图片并始终设置URI两次是非常可靠的。我的应用程序已经安装在超过200,000个设备上,没有任何用户抱怨存储问题 :) - shelll
显示剩余2条评论

0

我希望这能帮助其他人。我尝试在一个移除列表视图中使用那个方法,但它从未起作用。但使用位图对我有用。我能够更新任何记录中的图片,并且它们在更新时没有缓存。这是我使用的静态方法。

public static Bitmap getBitmap( String imageLocation ) {

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.ARGB_8888;

    return BitmapFactory.decodeFile(imageLocation, options);
}

那么

bitmap = BitmapUtils.getBitmap( address.getPhotoLocation() );
row.setImageViewBitmap( R.id.row_image, bitmap );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接