android grideview 图片png透明,Android完美解决GridView异步加载图片和加载大量图片时出现Out Of Memory问题...
眾所周知,我們在使用GridView或者ListView時,通常會遇到兩個棘手的問題:
1.每個Item獲取的數據所用的時間太長會導致程序長時間黑屏,更甚會導致程序ANR,也就是Application No Responding
2.當每個Item中有圖片存在時,少量圖片不會出現問題,當有大量圖片存在時,就會出現Out Of Memory的錯誤,導致這個錯誤的原因是Android系統中讀取位圖Bitmap時.默認分給虛擬機中圖片的堆棧大小只有8M。
好了,話不多說,下面我們來一起解決這兩個棘手的問題。
一、解決第一個問題,這里我們采用異步加載圖片的方法,也就是先讓每個Item加載一張默認的drawable,在后臺處理獲取圖片的任務,等后臺處理完以后,提示前臺更新圖片。這里我們會遇到一個問題,就是在gridview或則listview等容器中,當用戶隨意滑動的時候,將會產生N個線程去加載圖片,這個是我們不想看到的。我們希望的是一個圖片只有一個線程去下載就行了。為了解決這個問題,我們應該做的是讓這個Item中p_w_picpathview記住它是否正在加載(或者說是下載)圖片資源。如果正在加載,或者加載完成,那么我就不應該再建立一個任務去加載圖片了。
二、第二個問題我們采用圖片緩存的方式,將已經加載完成的圖片保存到緩存中,然后通過監控gridview的滑動事件去釋放圖片,即調用bitmap.recycle()方法,從而保證不會出現Out Of Memory錯誤 。
好了,話不多說,我們一起到代碼中去尋找答案。
這個是MainActivity類:
public class MainActivity extends Activity implements OnScrollListener{
private static final String TAG = "MainActivity";
private TextView textview_show_prompt = null;
private GridView gridview_test = null;
private List mList = null;
//圖片緩存用來保存GridView中每個Item的圖片,以便釋放
public static Map gridviewBitmapCaches = new HashMap();
private MyGridViewAdapter adapter = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
initData();
setAdapter();
}
private void findViews(){
textview_show_prompt = (TextView)findViewById(R.id.textview_show_prompt);
gridview_test = (GridView)findViewById(R.id.gridview_test);
}
private void initData(){
mList = new ArrayList();
String url = "/mnt/sdcard/testGridView/jay.png";//為sd卡下面創建testGridView文件夾,將圖片放入其中
//為了方便測試,我們這里只存入一張圖片,將其路徑后面添加數字進行區分,到后面要獲取圖片時候再處理該路徑。
for(int i=0;i<1000;i++){
mList.add(url+"/"+i);//區分路徑
}
}
private void setAdapter(){
adapter = new MyGridViewAdapter(this, mList);
gridview_test.setAdapter(adapter);
gridview_test.setOnScrollListener(this);
}
//釋放圖片的函數
private void recycleBitmapCaches(int fromPosition,int toPosition){
Bitmap delBitmap = null;
for(int del=fromPosition;del
delBitmap = gridviewBitmapCaches.get(mList.get(del));
if(delBitmap != null){
//如果非空則表示有緩存的bitmap,需要清理
Log.d(TAG, "release position:"+ del);
//從緩存中移除該del->bitmap的映射
gridviewBitmapCaches.remove(mList.get(del));
delBitmap.recycle();
delBitmap = null;
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
//注釋:firstVisibleItem為第一個可見的Item的position,從0開始,隨著拖動會改變
//visibleItemCount為當前頁面總共可見的Item的項數
//totalItemCount為當前總共已經出現的Item的項數
recycleBitmapCaches(0,firstVisibleItem);
recycleBitmapCaches(firstVisibleItem+visibleItemCount, totalItemCount);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
}
這個是MyGridViewAdapter類
public class MyGridViewAdapter extends BaseAdapter{
private Context mContext = null;
private LayoutInflater mLayoutInflater = null;
private List mList = null;
private int width = 120;//每個Item的寬度,可以根據實際情況修改
private int height = 150;//每個Item的高度,可以根據實際情況修改
public static class MyGridViewHolder{
public ImageView p_w_picpathview_thumbnail;
public TextView textview_test;
}
public MyGridViewAdapter(Context context,List list) {
// TODO Auto-generated constructor stub
this.mContext = context;
this.mList = list;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mList.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
MyGridViewHolder viewHolder = null;
if(convertView == null){
viewHolder = new MyGridViewHolder();
convertView = mLayoutInflater.inflate(R.layout.layout_my_gridview_item, null);
viewHolder.p_w_picpathview_thumbnail = (ImageView)convertView.findViewById(R.id.p_w_picpathview_thumbnail);
viewHolder.textview_test = (TextView)convertView.findViewById(R.id.textview_test);
convertView.setTag(viewHolder);
}else{
viewHolder = (MyGridViewHolder)convertView.getTag();
}
String url = mList.get(position);
//首先我們先通過cancelPotentialLoad方法去判斷p_w_picpathview是否有線程正在為它加載圖片資源,
//如果有現在正在加載,那么判斷加載的這個圖片資源(url)是否和現在的圖片資源一樣,不一樣則取消之前的線程(之前的下載線程作廢)。
//見下面cancelPotentialLoad方法代碼
if (cancelPotentialLoad(url, viewHolder.p_w_picpathview_thumbnail)) {
AsyncLoadImageTask task = new AsyncLoadImageTask(viewHolder.p_w_picpathview_thumbnail);
LoadedDrawable loadedDrawable = new LoadedDrawable(task);
viewHolder.p_w_picpathview_thumbnail.setImageDrawable(loadedDrawable);
task.execute(position);
}
viewHolder.textview_test.setText((position+1)+"");
return convertView;
}
private Bitmap getBitmapFromUrl(String url){
Bitmap bitmap = null;
bitmap = MainActivity.gridviewBitmapCaches.get(url);
if(bitmap != null){
System.out.println(url);
return bitmap;
}
url = url.substring(0, url.lastIndexOf("/"));//處理之前的路徑區分,否則路徑不對,獲取不到圖片
//bitmap = BitmapFactory.decodeFile(url);
//這里我們不用BitmapFactory.decodeFile(url)這個方法
//用decodeFileDescriptor()方法來生成bitmap會節省內存
//查看源碼對比一下我們會發現decodeFile()方法最終是以流的方式生成bitmap
//而decodeFileDescriptor()方法是通過Native方法decodeFileDescriptor生成bitmap的
try {
FileInputStream is = new FileInputStream(url);
bitmap = BitmapFactory.decodeFileDescriptor(is.getFD());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
bitmap = BitmapUtilities.getBitmapThumbnail(bitmap,width,height);
return bitmap;
}
//加載圖片的異步任務
private class AsyncLoadImageTask extends AsyncTask{
private String url = null;
private final WeakReference p_w_picpathViewReference;
public AsyncLoadImageTask(ImageView p_w_picpathview) {
super();
// TODO Auto-generated constructor stub
p_w_picpathViewReference = new WeakReference(p_w_picpathview);
}
@Override
protected Bitmap doInBackground(Integer... params) {
// TODO Auto-generated method stub
Bitmap bitmap = null;
this.url = mList.get(params[0]);
bitmap = getBitmapFromUrl(url);
MainActivity.gridviewBitmapCaches.put(mList.get(params[0]), bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap resultBitmap) {
// TODO Auto-generated method stub
if(isCancelled()){
resultBitmap = null;
}
if(p_w_picpathViewReference != null){
ImageView p_w_picpathview = p_w_picpathViewReference.get();
AsyncLoadImageTask loadImageTask = getAsyncLoadImageTask(p_w_picpathview);
if (this == loadImageTask) {
p_w_picpathview.setImageBitmap(resultBitmap);
p_w_picpathview.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
}
}
super.onPostExecute(resultBitmap);
}
}
private boolean cancelPotentialLoad(String url,ImageView p_w_picpathview){
AsyncLoadImageTask loadImageTask = getAsyncLoadImageTask(p_w_picpathview);
if (loadImageTask != null) {
String bitmapUrl = loadImageTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
loadImageTask.cancel(true);
} else {
// 相同的url已經在加載中.
return false;
}
}
return true;
}
//當 loadImageTask.cancel(true)被執行的時候,則AsyncLoadImageTask 就會被取消,
//當AsyncLoadImageTask 任務執行到onPostExecute的時候,如果這個任務加載到了圖片,
//它也會把這個bitmap設為null了。
//getAsyncLoadImageTask代碼如下:
private AsyncLoadImageTask getAsyncLoadImageTask(ImageView p_w_picpathview){
if (p_w_picpathview != null) {
Drawable drawable = p_w_picpathview.getDrawable();
if (drawable instanceof LoadedDrawable) {
LoadedDrawable loadedDrawable = (LoadedDrawable)drawable;
return loadedDrawable.getLoadImageTask();
}
}
return null;
}
//該類功能為:記錄p_w_picpathview加載任務并且為p_w_picpathview設置默認的drawable
public static class LoadedDrawable extends ColorDrawable{
private final WeakReference loadImageTaskReference;
public LoadedDrawable(AsyncLoadImageTask loadImageTask) {
super(Color.TRANSPARENT);
loadImageTaskReference =
new WeakReference(loadImageTask);
}
public AsyncLoadImageTask getLoadImageTask() {
return loadImageTaskReference.get();
}
}
}
還定義了一個BitmapUtilities類來處理圖片,這里我們在p_w_picpathview顯示圖片之前就將圖片縮小,更好的節省內存
public class BitmapUtilities {
public BitmapUtilities() {
// TODO Auto-generated constructor stub
}
public static Bitmap getBitmapThumbnail(String path,int width,int height){
Bitmap bitmap = null;
//這里可以按比例縮小圖片:
/*BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4;//寬和高都是原來的1/4
bitmap = BitmapFactory.decodeFile(path, opts); */
/*進一步的,
如何設置恰當的inSampleSize是解決該問題的關鍵之一。BitmapFactory.Options提供了另一個成員inJustDecodeBounds。
設置inJustDecodeBounds為true后,decodeFile并不分配空間,但可計算出原始圖片的長度和寬度,即opts.width和opts.height。
有了這兩個參數,再通過一定的算法,即可得到一個恰當的inSampleSize。*/
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, opts);
opts.inSampleSize = Math.max((int)(opts.outHeight / (float) height), (int)(opts.outWidth / (float) width));
opts.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(path, opts);
return bitmap;
}
public static Bitmap getBitmapThumbnail(Bitmap bmp,int width,int height){
Bitmap bitmap = null;
if(bmp != null ){
int bmpWidth = bmp.getWidth();
int bmpHeight = bmp.getHeight();
if(width != 0 && height !=0){
Matrix matrix = new Matrix();
float scaleWidth = ((float) width / bmpWidth);
float scaleHeight = ((float) height / bmpHeight);
matrix.postScale(scaleWidth, scaleHeight);
bitmap = Bitmap.createBitmap(bmp, 0, 0, bmpWidth, bmpHeight, matrix, true);
}else{
bitmap = bmp;
}
}
return bitmap;
}
}
這里我順便把用到的兩個XML文件代碼貼出來下:
這個是activity_main.xml文件:
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
android:id="@+id/textview_show_prompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/textview_show_prompt"
android:textSize="24dp"
tools:context=".MainActivity" />
android:layout_below="@id/textview_show_prompt"
android:id="@+id/gridview_test"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:horizontalSpacing="10dp"
android:verticalSpacing="20dp"
android:numColumns="4"
android:gravity="center"
android:padding="10dp"
android:background="#FFFFFFFF">
這個是layout_my_gridview_item.xml文件
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#88000000"
android:gravity="center"
>
android:id="@+id/p_w_picpathview_thumbnail"
android:layout_width="120dp"
android:layout_height="150dp"
android:padding="5dp"
android:scaleType="centerInside"
/>
android:id="@+id/textview_test"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24dp"
android:textColor="#FFFF0000"
/>
好了,到此為止我們已經完成了任務,在這里我想說的一些心得體會:
本來三天前就打算寫這篇文章了,準備先寫好demo,然后再來發表,結果在寫demo的過程中發現了很多問題,比如AsyncTask這個類的用法,Bitmap的recycle方法的調用,以及面對嵌入式開發,怎么樣去節省內存空間的使用,從而確保不會出現Out Of Memory。大家可以去查看資料看看AsyncTask這個類的用法,至于怎么樣去節省內存使用,仁者見仁,智者見智吧。我在這里就單獨說下Bitmap的recycle方法的調用的注意事項吧。
首先,Bitmap是否有調用recycle方法的必要性,這個怎么說呢,每個人都有不同的答案。嵌入式系統總是格外注重空間的問題,不小心的話就會有OOM。但是應用層使用java的android平臺有其天然的優勢(java語言有自己的垃圾回收,android平臺上各個application有自己的process自己的空間)。
無需調用bitmap的理由有:
1. 垃圾回收會處理的;
2.當application關閉,process被殺掉,所有這個process占用的空間自然回歸系統;
bitmap的recycle函數的調用還是有必要的,理由有:
1.垃圾回收雖然好使,但是有可能的話,我們還是讓它少干點活吧。垃圾回收有很大的未來不確定性,會加重未來未知時間點的loading,若有大量bitmap需要垃圾回收處理,那必然垃圾回收需要做的次數就更多也發生地更頻繁,小心會造成ANR。但是,若是自己recycle,就可以可控制地分散處理了這些回收任務了。
2. 若是launcher那樣一直運行的application,它的process一直存在,memory問題還是多多注意下比較好
再來說下假如要調用bitmap的recycle方法,何時調用是個很重要的問題,早了就大事不好了,會有這樣的Exception:? ? java.lang.RuntimeException,Canvas: trying to use a recycled bitmap
所以,我們 怎樣才可以保證不會早了呢?
關于圖片顯示,重要的時間點:
step1: 設置進去的時間點;
Step2: 畫面畫出來的時間點;
最保險最笨的做法,在新的圖片設置進去以后再recycle掉老的圖片,這樣做的壞處在于,在某個時間段,你需要的空間是double的(新舊兩套都在);
如果你不偏向于那么做,可以考慮后面一個時間點,除了setImageBitmap以及其它代碼中顯示調用那個bitmap的時候我們會檢查bitmap,在acticvity變為visible的時候系統還是會去找之前設置進去的bitmap。所以,在UI線程里面,在一個不可能被打斷的方法里面,是先設置新的bitmap還是先recycle舊的圖片是沒有影響的。
譬如說? ???mBitmap.recycle();
mBitmap = .....? ?//設置
mImageView.setImageBitmap(mBitmap);
這樣的代碼是完全可以的。
后面這樣的做法,最重要的就是確保:在UI線程(因為設置UI顯示只能在UI主線程里)里面一個不可能被打斷的方法里面。這個是為了確保在兩者之間UI主線程不可能被打斷,不可能剛好從invisible變成visible。
所以,特別小心兩種東西:
1. 多線程(最好不要在多線程里面調用);
2. 非即時發生的方法:譬如,發intent,發notify去通知UI主線程去做UI重新刷新并不能替代mImageView.setImageBitmap(mBitmap)。完全有可能出現這種情況:你確實發了intent出去了,但是目標activity之一還沒有做UI重新設置,這個時候這個acitivity變成visible了,系統仍然試圖找舊的圖片,找不到了就會報exception了。
就說這么多吧,這里也參照一些網絡上的代碼,另外大家下載demo代碼運行的時候需要把壓縮包里面的jay.png圖片放到sd中testGridView文件夾 ,有些機器sd卡路徑可能會不同,大家只要到代碼中修改String url = "/mnt/sdcard/testGridView/jay.png"這行代碼就行。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的android grideview 图片png透明,Android完美解决GridView异步加载图片和加载大量图片时出现Out Of Memory问题...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 进lanucher的广播
- 下一篇: baidu+app+per+androi