2013-03-15 58 views
13

我遇到過需要以非常快的速度切換圖像的幻燈片顯示圖像的情況。數量龐大的圖像讓我想要將JPEG數據存儲在內存中,並在需要顯示時解碼它們。爲了緩解垃圾收集器,我使用BitmapFactory.Options.inBitmap來重用位圖。BitmapFactory.Options.inBitmap在切換ImageView位圖時會導致撕裂

不幸的是,這造成了相當嚴重的撕裂,我試過不同的解決方案,如同步,信號量,2-3位圖之間交替,然而,似乎沒有解決問題。

我已經在GitHub上設置了一個演示此問題的示例項目; https://github.com/Berglund/android-tearing-example

我有,其解碼位圖,將其設置在UI線程上一個線程,休眠5毫秒:

Runnable runnable = new Runnable() { 
@Override 
public void run() { 
     while(true) { 
      BitmapFactory.Options options = new BitmapFactory.Options(); 
      options.inSampleSize = 1; 

      if(bitmap != null) { 
       options.inBitmap = bitmap; 
      } 

      bitmap = BitmapFactory.decodeResource(getResources(), images.get(position), options); 

      runOnUiThread(new Runnable() { 
       @Override 
       public void run() { 
        imageView.setImageBitmap(bitmap); 
       } 
      }); 

      try { 
       Thread.sleep(5); 
      } catch (InterruptedException e) {} 

      position++; 
      if(position >= images.size()) 
       position = 0; 
     } 
    } 
}; 
Thread t = new Thread(runnable); 
t.start(); 

我的想法是,ImageView.setImageBitmap(位圖)上繪製位圖下一個vsync,但是,當發生這種情況時,我們可能已經解碼了下一個位圖,因此我們已經開始修改位圖像素。我正在朝着正確的方向思考?

有沒有人有從哪裏去的任何提示?

+0

'ImageView'是必需的嗎?我建議'SurfaceView'或'TextureView'獲得較高的抽獎率。 – 2013-03-24 15:48:48

+0

我面臨着同樣的撕裂,但在不同的情況下。我無法提出問題,因爲這個屁股stackoverflow政策。請幫助我一個人。這對我來說是一個很大的幫助。 – therealprashant 2015-02-16 18:41:12

回答

4

作爲當前方法的替代方法,您可能會考慮像保留JPEG數據一樣進行操作,還要爲每幅圖像創建一個單獨的位圖,並使用inPurgeableinInputShareable標誌。這些標誌爲不在Java垃圾回收器直接管理的單獨堆上的位圖分配後備內存,並允許Android本身在沒有空間的情況下丟棄位圖數據,並在需要時按需重新解碼JPEG 。 Android擁有所有這些專用代碼來管理位圖數據,所以爲什麼不使用它?

+0

現在,這個。如果我早些時候看過這個答案,我可能會獎勵它的賞金。目前爲止,我的Nexus 4完美解決了這個問題(也非常簡單)。我只需要檢查低端設備。 – 2013-03-26 14:57:36

+0

很樂意幫忙=) – 2013-03-26 15:10:32

0

在BM.decode(資源... 是網絡參與?

如果是,那麼u需要優化前瞻連接和數據傳輸的跨網連接,以及您的工作優化的位圖和memory.That可能意味着熟練使用您的連接協議(HTTP我猜)低延遲或異步傳輸。確保您不運輸更多的數據比您需要?位圖解碼通常可以丟棄80%的像素創建優化對象來填充本地視圖

如果用於位圖的數據已經是本地的,並且沒有關於網絡延遲的問題,那麼只關注於rese創建一個集合類型DStructure(listArray)來保存UI將在頁面轉發,頁面返回事件上交換的片段。

如果你的jpegs(pngs是無損的,位圖操作IMO)大約每個100K,你可以使用std適配器將它們加載到碎片。如果它們比較大,那麼你將不得不找出位圖'compress'選項來與解碼一起使用,以免浪費你的片段數據結構中的大量內存。

如果您需要一個theadpool以優化位圖創建,那麼請執行此操作以消除該步驟中涉及的任何延遲。

林不知道它的工作原理,但如果你想變得更加複雜,你可以看看把一個循環緩衝區或一些東西放在與適配器協作的listArray之下?

國際海事組織 - 一旦你有結構,頁面之間的事務在片段之間切換應該是非常快的。我有直接的經驗,每個內存大約有6張圖片,大小約爲200k,頁面快進,頁面返回。

我用this app作爲框架,側重於'頁面查看器'的例子。

+0

這裏有一些好點。但是,我並不完全明白你的意思是什麼。你是否建議我在片段之間切換,每個片段預先加載單個圖像?我認爲這會造成太多開銷,造成碎片並在它們之間切換。 – 2013-03-26 15:05:19

+0

http://developer.android.com/training/displaying-bitmaps/display-bitmap.html#gridview。您可以將位圖存儲在pageview的集合中,並使用適配器來處理分頁。這個例子只是在集合中存儲imgRefId。 – 2013-03-26 15:11:58

4

您需要做以下事情才能擺脫這個問題。

  1. 添加一個額外的位圖,以防止UI線程在另一個線程修改它時繪製位圖的情況。
  2. 實現線程同步以防止後臺線程嘗試解碼新位圖時的情況,但前一個未由ui線程顯示。

我修改了你的代碼,現在它對我來說工作正常。

package com.example.TearingExample; 

import android.app.Activity; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.os.Bundle; 
import android.view.View; 
import android.widget.Button; 
import android.widget.ImageView; 

import java.util.ArrayList; 

public class MainActivity extends Activity { 
    ArrayList<Integer> images = new ArrayList<Integer>(); 

    private Bitmap[] buffers = new Bitmap[2]; 
    private volatile Bitmap current; 

    private final Object lock = new Object(); 
    private volatile boolean ready = true; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     images.add(R.drawable.black); 
     images.add(R.drawable.blue); 
     images.add(R.drawable.green); 
     images.add(R.drawable.grey); 
     images.add(R.drawable.orange); 
     images.add(R.drawable.pink); 
     images.add(R.drawable.red); 
     images.add(R.drawable.white); 
     images.add(R.drawable.yellow); 

     final ImageView imageView = (ImageView) findViewById(R.id.image); 
     final Button goButton = (Button) findViewById(R.id.button); 

     goButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       Runnable runnable = new Runnable() { 
        @Override 
        public void run() { 
         int position = 0; 
         int index = 0; 

         while (true) { 
          try { 
           synchronized (lock) { 
            while (!ready) { 
             lock.wait(); 
            } 
            ready = false; 
           } 

           BitmapFactory.Options options = new BitmapFactory.Options(); 

           options.inSampleSize = 1; 
           options.inBitmap = buffers[index]; 

           buffers[index] = BitmapFactory.decodeResource(getResources(), images.get(position), options); 
           current = buffers[index]; 

           runOnUiThread(new Runnable() { 
            @Override 
            public void run() { 
             imageView.setImageBitmap(current); 
             synchronized (lock) { 
              ready = true; 
              lock.notifyAll(); 
             } 
            } 
           }); 

           position = (position + 1) % images.size(); 
           index = (index + 1) % buffers.length; 

           Thread.sleep(5); 
          } catch (InterruptedException ignore) { 
          } 
         } 
        } 
       }; 
       Thread t = new Thread(runnable); 
       t.start(); 
      } 
     }); 
    } 
} 
+0

實際上,setImageBitmap()方法只將位圖對象傳遞給ImageView,並觸發視圖無效,此時沒有繪圖已經開始。因此,如果解碼運行速度快於顯示,則可能會繞過一些幀。 – 2013-03-22 14:56:33

+0

非常好的嘗試,我確實看到了改進 - 但不幸的是,由於平Tran給出的原因,它不能解決問題100%。 – 2013-03-26 15:02:08

7

您應該使用,因爲該方法的ImageView的的onDraw()方法時,認爲需要借鑑其在屏幕上的內容被調用。

我創建了一個名爲MyImageView新的類,它擴展了ImageView的,並覆蓋將觸發回調,讓聽衆知道,這一觀點已經完成了它的繪圖

public class MyImageView extends ImageView { 

    private OnDrawFinishedListener mDrawFinishedListener; 

    public MyImageView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
    } 

    @Override 
    protected void onDraw(Canvas canvas) { 
     super.onDraw(canvas); 
     if (mDrawFinishedListener != null) { 
      mDrawFinishedListener.onOnDrawFinish(); 
     } 
    } 

    public void setOnDrawFinishedListener(OnDrawFinishedListener listener) { 
     mDrawFinishedListener = listener; 
    } 

    public interface OnDrawFinishedListener { 
     public void onOnDrawFinish(); 
    } 

} 

在MainActivity中的onDraw()方法,定義3位圖:對ImageView使用的位圖的一個引用,一個用於解碼,一個引用位圖,以供下一次解碼循環使用。我重用vminorov的回答synchronized塊,而是把在不同的地方有解釋在代碼註釋

public class MainActivity extends Activity { 

    private Bitmap mDecodingBitmap; 
    private Bitmap mShowingBitmap; 
    private Bitmap mRecycledBitmap; 

    private final Object lock = new Object(); 

    private volatile boolean ready = true; 

    ArrayList<Integer> images = new ArrayList<Integer>(); 
    int position = 0; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     images.add(R.drawable.black); 
     images.add(R.drawable.blue); 
     images.add(R.drawable.green); 
     images.add(R.drawable.grey); 
     images.add(R.drawable.orange); 
     images.add(R.drawable.pink); 
     images.add(R.drawable.red); 
     images.add(R.drawable.white); 
     images.add(R.drawable.yellow); 

     final MyImageView imageView = (MyImageView) findViewById(R.id.image); 
     imageView.setOnDrawFinishedListener(new OnDrawFinishedListener() { 

      @Override 
      public void onOnDrawFinish() { 
       /* 
       * The ImageView has finished its drawing, now we can recycle 
       * the bitmap and use the new one for the next drawing 
       */ 
       mRecycledBitmap = mShowingBitmap; 
       mShowingBitmap = null; 
       synchronized (lock) { 
        ready = true; 
        lock.notifyAll(); 
       } 
      } 
     }); 

     final Button goButton = (Button) findViewById(R.id.button); 

     goButton.setOnClickListener(new View.OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       Runnable runnable = new Runnable() { 
        @Override 
        public void run() { 
         while (true) { 
          BitmapFactory.Options options = new BitmapFactory.Options(); 
          options.inSampleSize = 1; 

          if (mDecodingBitmap != null) { 
           options.inBitmap = mDecodingBitmap; 
          } 

          mDecodingBitmap = BitmapFactory.decodeResource(
            getResources(), images.get(position), 
            options); 

          /* 
          * If you want the images display in order and none 
          * of them is bypassed then you should stay here and 
          * wait until the ImageView finishes displaying the 
          * last bitmap, if not, remove synchronized block. 
          * 
          * It's better if we put the lock here (after the 
          * decoding is done) so that the image is ready to 
          * pass to the ImageView when this thread resume. 
          */ 
          synchronized (lock) { 
           while (!ready) { 
            try { 
             lock.wait(); 
            } catch (InterruptedException e) { 
             e.printStackTrace(); 
            } 
           } 
           ready = false; 
          } 

          if (mShowingBitmap == null) { 
           mShowingBitmap = mDecodingBitmap; 
           mDecodingBitmap = mRecycledBitmap; 
          } 

          runOnUiThread(new Runnable() { 
           @Override 
           public void run() { 
            if (mShowingBitmap != null) { 
             imageView 
               .setImageBitmap(mShowingBitmap); 
             /* 
             * At this point, nothing has been drawn 
             * yet, only passing the data to the 
             * ImageView and trigger the view to 
             * invalidate 
             */ 
            } 
           } 
          }); 

          try { 
           Thread.sleep(5); 
          } catch (InterruptedException e) { 
          } 

          position++; 
          if (position >= images.size()) 
           position = 0; 
         } 
        } 
       }; 
       Thread t = new Thread(runnable); 
       t.start(); 
      } 
     }); 

    } 
} 
+0

這似乎確實解決了這個問題,但j__m的答案更容易解決問題,所以我會授予它正確的答案。然而,隨着賞金即將結束,我將它授予您,因爲您的解決方案似乎也行得通。如果我在賞金出去之前看到了答案,我可能會把它交給j__m。 – 2013-03-26 15:01:24

+2

在這種情況下,j__應得到賞金。我剛剛提出了200點聲望的賞金,並在24小時後將其分配給j__m。 – 2013-03-28 01:13:27

+0

@Binh Tran,你值得我爲此+1! – 2013-03-28 05:20:39

0

它涉及到圖像緩存,asycTask處理,從網絡等 後臺下載請閱讀此頁: http://developer.android.com/training/displaying-bitmaps/index.html

如果您下載並查看該頁面上的示例項目bitmapfun,我相信它會解決您的所有問題。這是一個完美的例子。

+0

您可以在答案中發佈該鏈接文章的相關部分嗎?隨着時間的推移,鏈接往往會移動/消失,使得答案無用。 – 2013-03-22 02:16:48

+0

對不起,但bitmapfun項目與快速顯示位圖和保持性能很少有關。 – 2013-03-26 15:02:47