0

UrlImageViewHelper ( https://github.com/koush/UrlImageViewHelper ) に非常によく似たものを実装しようとしています。ここでは、単純な 1 行のコードを使用して簡単に URL から画像をロードし、画像が既にダウンロードされている場合代わりにキャッシュからロードされます。主な違いは、同じ効果が必要なことですが、URL からダウンロードする代わりに、独自のクライアント サーバー通信を使用して独自のサーバーから画像を受信したいということです。サーバー上のすべての画像は文字列で一意に識別でき、これを画像の ID として使用します。

私の主なアイデアはこれでした: LRU キャッシュを使用して画像を保持しますが、ビットマップ (非常に大きい) を保持する代わりに、生の画像データ バイナリを保持したいので、同じ画像を使用して異なるサイズのビットマップを構築できます。特定の状況に応じてオンデマンドの品質。

これはこれまでの私の実装です:

    public class ImageHandler {

    private static class BitmapCache extends LruCache<String, byte[]>
    {
        public WigoBitmapCache(int maxSize) {
            super(maxSize);
        }

        @Override
        protected int sizeOf(String key, byte[] value) {
            return value.length;
        }

    }

    private static class ImageHandlerThread extends Thread
    {

            /* THIS THREAD WILL DECODE THE IMAGE AND SET THE BITMAP TO THE IMAGEVIEW IN THE BACKGROUND */   
                Activity activity;
        ImageView imageView;
        byte[] imageBytes;

        public ImageHandlerThread(Activity activity, ImageView imageView, byte[] imageBytes)
        {
            this.activity=activity;
            this.imageView=imageView;
            this.imageBytes=imageBytes;
        }

        public void run() {

            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
            int factor1=o.outHeight/height;
            int factor2=o.outWidth/width;
                        /* height and width are for now constant */
            o = null;
            o = new BitmapFactory.Options();
            if (factor1>factor2)
                o.inSampleSize=factor1;
            else
                o.inSampleSize=factor2;
            Bitmap bit = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length,o);
            setBitmap(bit);
            bit = null;

        }


        private void setBitmap(final Bitmap bit) {
            activity.runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    imageView.setImageBitmap(bit);

                }
            });     
        }

    }

    private static class QueueItem
    { /*USED TO HOLD INFO ABOUT THE IMAGE REQUEST UNTIL THE IMAGE GETS FROM THE SERVER */
        String imageName;
        Activity activity;
        ImageView imageView;

        public QueueItem(String imageName, Activity activity, ImageView imageView) 
        {
            this.imageName=imageName;
            this.activity = activity;
            this.imageView = imageView;
        }

    }

    private BitmapCache cache; // this cache holds the image binaries 
    private ArrayList<QueueItem> queue; // this queue holds the info about the request, until the server sends the image

    public ImageHandler(int maxSize)
    {
        cache=new BitmapCache(maxSize);
        queue = new ArrayList<QueueItem>();

    }

    public synchronized void setBitmap(Activity activity, ImageView imageView, String imageName)
    {
        byte[] imageBytes = cache.get(imageName);
        if (imageBytes==null)
        {
            QueueItem item = new QueueItem(imageName, activity, imageView);
            queue.add(item);    

            /* HERE IS THE CODE TO RETRIEVE THE IMAGE BINARY FROM MY SERVER, THIS CODE WORKS FINE, SO THERE IS NO REASON TO BOHER YOU WITH IT */

        }
        else
        {
            ImageHandlerThread thread = new ImageHandlerThread(activity, imageView, imageBytes);
            thread.start();
        }

    }

    public synchronized void insert (String imageName, byte[] imageBytes)
    {

        /* THIS METHOD IS THE CALLBACK THAT IS CALLED WHEN THE IMAGE BINARY IS RECEIVED FROM THE SERVER */

        cache.put(imageName, imageBytes);

        for (QueueItem item: queue)
        {
            if (item.imageName.equals(imageName))
            {
                ImageHandlerThread thread = new ImageHandlerThread(item.activity, item.imageView, imageBytes);
                thread.start();
                queue.remove(item);
            }
        }
    }
}

基本的に、ここでの主なメソッドは setBitmap() で、アクティビティ、ビットマップが必要な imageView、および画像名の名前を取得します。画像が既にキャッシュにある場合は、新しいスレッドが開始され、バイトが適切なサイズのビットマップにデコードされ、ビットマップが imageView に設定されます。画像がキャッシュに存在しない場合、画像が受信されるまでリクエストはキューに入れられ、サーバーから画像が取得されてから、以前と同じスレッドが開始されます。

これはすべて正常に機能します。問題は、imageView が画像の別のビットマップに設定されている場合、またはアクティビティが破棄されている場合でも、ビットマップがメモリに常駐し、GC によって収集されないことです。

最初は、アクティビティへの参照を保持していたためだと思いました。その参照はアクティビティを存続させますが、そうではないようです。アクティビティへの参照は非常に短命であり、画像がサーバーはこの参照をクリアします。

この実装ではメモリがすぐに不足してしまい、それを修正するために何をすべきか、またはその理由がわかりません。私が作成したビットマップは収集されていませんが、それらへの参照は保持していません。これは、画像をデコードする方法のアーティファクトでしょうか? または、スレッドは適切に収集されていない参照を保持していますか? 誰にもアイデアはありますか?

4

1 に答える 1

0

わかりましたので、問題を誤解しました。実行モードではなくデバッグ モードでこれを実行していたため、GC が完成したスレッド オブジェクトを収集していなかったのです。これを実行モードで実行すると、アプリのメモリ使用量は 8 MB をまったく超えませんでしたが、デバッグ モードでは 25 MB 以上の領域になりました。

結論: デバッグ モードでの GC およびメモリ使用量の情報を信頼しないでください。特に、多数の短期スレッドを実行している場合は注意が必要です。

于 2013-09-28T22:12:40.623 に答える