无法在 RemoteViewsFactory 中加载多个图像

Posted

技术标签:

【中文标题】无法在 RemoteViewsFactory 中加载多个图像【英文标题】:Unable to load multiple images in a RemoteViewsFactory 【发布时间】:2013-12-02 00:51:50 【问题描述】:

我正在创建一个简单的 android 小部件,它可以从网站获取并显示一些电影封面。显示图像很简单GridView。该小部件工作正常,但当我开始滚动时,我得到了 OutOfMemoryError 和 Android 杀死了我的进程。

这是我的RemoteViewsFactory 的代码。我似乎无法理解如何解决这个问题。我使用的图像大小合适,所以我不会在 Android 中浪费内存。它们尺寸完美。正如你所看到的,我使用了一个非常小的 LRU 缓存,并且在我的清单中,我什至启用了android:largeHeap="true"。我已经在这个问题上绞尽脑汁整整两天了,我想知道我正在尝试做的事情是否可能?

public class SlideFactory implements RemoteViewsFactory 

    private JSONArray jsoMovies = new JSONArray();
    private Context ctxContext;
    private Integer intInstance;
    private RemoteViews remView;
    private LruCache<String, Bitmap> lruCache;
    private static String strCouch = "http://192.168.1.110:5984/movies/";

    public SlideFactory(Context ctxContext, Intent ittIntent) 
        this.ctxContext = ctxContext;
        this.intInstance = ittIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
        this.lruCache = new LruCache<String, Bitmap>(4 * 1024 * 1024);
        final Integer intMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final Integer intBuffer = intMemory / 8;

        lruCache = new LruCache<String, Bitmap>(intBuffer) 
            @Override
            protected int sizeOf(String strDigest, Bitmap bmpCover) 
                return bmpCover.getByteCount() / 1024;
            
        ;
    

    public RemoteViews getViewAt(int intPosition) 
        if (intPosition <= getCount()) 
            try 
                String strDocid = this.jsoMovies.getJSONObject(intPosition).getString("id");
                String strDigest = this.jsoMovies.getJSONObject(intPosition).getJSONObject("value").getJSONObject("_attachments").getJSONObject("thumb.jpg").getString("digest");
                String strTitle = this.jsoMovies.getJSONObject(intPosition).getJSONObject("value").getString("title");
                Bitmap bmpThumb = this.lruCache.get(strDigest);

                if (bmpThumb == null) 
                    String strUrl = strCouch + strDocid + "/thumb.jpg";
                    System.out.println("Fetching" + intPosition);
                    bmpThumb = new ImageFetcher().execute(strUrl).get();
                    this.lruCache.put(strDigest, bmpThumb);
                
                remView.setImageViewBitmap(R.id.movie_cover, bmpThumb);
                remView.setTextViewText(R.id.movie_title, strTitle);
             catch (Exception e) 
                e.printStackTrace();
            
            return remView;
        
        return null;
    

    public void onCreate() 
        return;
    

    public void onDestroy() 
        jsoMovies = null;
    

    public int getCount() 
        return 20;
    

    public RemoteViews getLoadingView() 
        return null;//new RemoteViews(this.ctxContext.getPackageName(), R.layout.loading);
    

    public int getViewTypeCount() 
        return 1;
    

    public long getItemId(int intPosition) 
        return intPosition;
    

    public boolean hasStableIds() 
        return true;
    

    public void onDataSetChanged() 
        this.remView = new RemoteViews(this.ctxContext.getPackageName(), R.layout.slide);
        try 
            DefaultHttpClient dhcNetwork = new DefaultHttpClient();
            String strUrl = strCouch + "_design/application/_view/language?" + URLEncoder.encode("descending=true&startkey=[\"hi\", ]&attachments=true");
            HttpGet getMovies = new HttpGet(strUrl);
            HttpResponse resMovies = dhcNetwork.execute(getMovies);
            Integer intMovies = resMovies.getStatusLine().getStatusCode();
            if (intMovies != HttpStatus.SC_OK) 
                throw new HttpResponseException(intMovies, "Server responded with an error");
            
            String strMovies = EntityUtils.toString(resMovies.getEntity(), "UTF-8");
            this.jsoMovies = new JSONObject(strMovies).getJSONArray("rows");
         catch (Exception e) 
            Log.e("SlideFactory", "Unknown error encountered", e);
         
    


这是获取图像的AsyncTask 的来源:

public class ImageFetcher extends AsyncTask<String, Void, Bitmap> 
    @Override
    protected Bitmap doInBackground(String... strUrl) 
        Bitmap bmpThumb = null;
        try 
            URL urlThumb = new URL(strUrl[0]);
            HttpURLConnection hucConnection = (HttpURLConnection) urlThumb.openConnection();
            InputStream istThumb = hucConnection.getInputStream();
            bmpThumb = BitmapFactory.decodeStream(istThumb);
            istThumb.close();
            hucConnection.disconnect();
         catch (Exception e) 
            e.printStackTrace();
        
        return bmpThumb;
    

【问题讨论】:

@commonsware,您能对此有所了解吗?你一直都有答案。 :) 谢谢。 你能发布一些 logcat 输出吗? @MridangAgarwalla 你能发布ImageFetcher 的代码吗?您还可以提及您正在加载的图像的大小(或者可能是其中最大的大小)..?? @AmulyaKhare,我已经发布了ImageFetcher 的来源。我可以使用样本大小,但它会大大降低质量。我的图像都是 250x375 像素。我将println 放入方法getViewAt 方法中,并注意到Android 尝试一次加载所有图像,即使GridView 中只有少数图像可见。我认为ListViewGridViewStackVIew 的这些适配器的工作方式是它们只预加载一些项目,并且当用户滚动并且它们失去焦点时,Android 会丢弃旧的布局。我不明白为什么它会尝试加载所有项目。 Have you read it? 而这条this.lruCache = new LruCache&lt;String, Bitmap&gt;(4 * 1024 * 1024); 行没用。 【参考方案1】:

我有类似的痛苦经历,经过大量挖掘后,我发现setImageViewBitmap 将所有位图复制到新实例中,因此占用了双倍内存。 考虑将以下行更改为静态资源或其他内容!

remView.setImageViewBitmap(R.id.movie_cover, bmpThumb);

这需要大量内存并 ping 垃圾收集器来清理内存,但速度太慢,以至于您的应用无法及时使用释放的内存。

【讨论】:

由于我是从远程服务器下载图像,我将如何创建静态引用。通过静态引用,您是指可绘制文件夹中的资产吗?如果您可以链接到使用 setImageViewBitmap 方法发现问题的 Android 资源,那也很棒。谢谢@AVEbrahimi 谢谢!这真的挽救了一天! @MridangAgarwalla 你能发布你的解决方案吗? 我认为问题尚未解决。如果您找到了解决方案,请发布!【参考方案2】:

我的解决方法是使用 LRUCache 来存储小部件的位图:

首先,照常初始化:

protected void initializeCache()
    
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        // Use 1/10th of the available memory for this memory cache.
        final int cacheSize = maxMemory / 10;

        bitmapLruCache = new LruCache<String, Bitmap>(cacheSize)
        
            @Override
            protected int sizeOf(String key, Bitmap bitmap) 
                // The cache size will be measured in kilobytes rather than
                // number of items.
                return bitmap.getByteCount() / 1024;
            
        ;
    

然后重用位图:

void updateImageView(RemoteViews views, int resourceId, final String imageUrl)
    
        Bitmap bitmap = bitmapLruCache.get(imageUrl);

        if (bitmap == null)
        
            bitmap = // get bitmap from web with your loader
            bitmapLruCache.put(imageUrl, bitmap);
        

        views.setImageViewBitmap(resourceId, bitmap);
    

使用此代码小部件现在不会使我的应用崩溃。

更多信息here

【讨论】:

以上是关于无法在 RemoteViewsFactory 中加载多个图像的主要内容,如果未能解决你的问题,请参考以下文章

调用 notifyAppWidgetViewDataChanged 后,在 RemoteViewsFactory 中不调用 onDataSetChanged

用于内容的 Widget RemoteViewsFactory 中的 Android 权限拒绝

空数据集时 RemoteViewsFactory 调用 getViewAt

RemoteViewsFactory grantUriPermission 来获取数据

RemoteViewsFactory 如何处理 Android 中的 ViewTypes?

用于 Android 小部件的 RemoteViewsFactory 内的通用图像加载器