本示例演示如何在Android中使用加載器(Loader)來實現獲取本機中的所有圖片,並進行查看圖片的效果。
在這個示例中,我使用android-support-v4.jar中的加載器(Loader)來實現獲取本機中所有圖片,關於這個包在以前的文章中也提到,是一個非常有用的包,關於這個包的詳細信息請大家查看官方文檔:http://developer.android.com/sdk/compatibility-library.html。
關於加載器(Loader)是在Android3.0中才開始引進並使用的,Android3.0以前的版本中要想使用加載器則需要用android-support-v4.jar來實現,我這個示例是基於Android2.2的。加載器(Loader)有什么作用呢?官方文檔介紹是:它能夠使用得在activity或fragment中異步加載數據變得更加容易,它具有以下的特點:
1.它們對每一個Activity和Fragment都是有效的。
2.它們提供了一種異步加載數據的能力。
3.它們監視數據源並且數據內容改變時將會傳遞新的結果。
4.當配置改變而被重新創建時,它們自動的會重連到上一個加載器的游標,然而,它們不需要重新查詢數據。
我會結合下面的代碼逐步介紹加載器(Loader)相關類及使用方法。下面先讓我們看下本示例實現的效果圖:

左邊的圖是顯示本機中所有的圖片列表,右邊的圖則是點擊一項時查看圖片。
項目結構圖如下所示:

創建項目時我們需要引入android-support-v4.jar包。
MyDevicePhotoActivity.java文件中代碼如下:
package com.device.photo; import android.app.Dialog; import android.content.ContentResolver; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.SimpleCursorAdapter; import android.support.v4.widget.SimpleCursorAdapter.ViewBinder; /** * Android實現獲取本機中所有圖片 * @Description: Android實現獲取本機中所有圖片 * @FileName: MyDevicePhotoActivity.java * @Package com.device.photo * @Author Hanyonglu * @Date 2012-5-10 下午04:43:55 * @Version V1.0 */ public class MyDevicePhotoActivity extends FragmentActivity implements LoaderCallbacks<Cursor>{ private Bitmap bitmap = null; private byte[] mContent = null; private ListView listView = null; private SimpleCursorAdapter simpleCursorAdapter = null; private static final String[] STORE_IMAGES = { MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.LATITUDE, MediaStore.Images.Media.LONGITUDE, MediaStore.Images.Media._ID }; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); listView = (ListView)findViewById(android.R.id.list); simpleCursorAdapter = new SimpleCursorAdapter( this, R.layout.simple_list_item, null, STORE_IMAGES, new int[] { R.id.item_title, R.id.item_value}, 0 ); simpleCursorAdapter.setViewBinder(new ImageLocationBinder()); listView.setAdapter(simpleCursorAdapter); // 注意此處是getSupportLoaderManager(),而不是getLoaderManager()方法。 getSupportLoaderManager().initLoader(0, null, this); // 單擊顯示圖片 listView.setOnItemClickListener(new ShowItemImageOnClickListener()); } @Override public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { // TODO Auto-generated method stub // 為了查看信息,需要用到CursorLoader。 CursorLoader cursorLoader = new CursorLoader( this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, STORE_IMAGES, null, null, null); return cursorLoader; } @Override public void onLoaderReset(Loader<Cursor> arg0) { // TODO Auto-generated method stub simpleCursorAdapter.swapCursor(null); } @Override public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) { // TODO Auto-generated method stub // 使用swapCursor()方法,以使舊的游標不被關閉. simpleCursorAdapter.swapCursor(cursor); } // 將圖片的位置綁定到視圖 private class ImageLocationBinder implements ViewBinder{ @Override public boolean setViewValue(View view, Cursor cursor, int arg2) { // TODO Auto-generated method stub if (arg2 == 1) { // 圖片經度和緯度 double latitude = cursor.getDouble(arg2); double longitude = cursor.getDouble(arg2 + 1); if (latitude == 0.0 && longitude == 0.0) { ((TextView)view).setText("位置:未知"); } else { ((TextView)view).setText("位置:" + latitude + ", " + longitude); } // 需要注意:在使用ViewBinder綁定數據時,必須返回真;否則,SimpleCursorAdapter將會用自己的方式綁定數據。 return true; } else { return false; } } } // 單擊項顯示圖片事件監聽器 private class ShowItemImageOnClickListener implements OnItemClickListener{ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub final Dialog dialog = new Dialog(MyDevicePhotoActivity.this); // 以對話框形式顯示圖片 dialog.setContentView(R.layout.image_show); dialog.setTitle("圖片顯示"); ImageView ivImageShow = (ImageView) dialog.findViewById(R.id.ivImageShow); Button btnClose = (Button) dialog.findViewById(R.id.btnClose); btnClose.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { dialog.dismiss(); // 釋放資源 if(bitmap != null){ bitmap.recycle(); } } }); Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon(). appendPath(Long.toString(id)).build(); FileUtil file = new FileUtil(); ContentResolver resolver = getContentResolver(); // 從Uri中讀取圖片資源 try { mContent = file.readInputStream(resolver.openInputStream(Uri.parse(uri.toString()))); bitmap = file.getBitmapFromBytes(mContent, null); ivImageShow.setImageBitmap(bitmap); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } dialog.show(); } } @Override protected void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if(bitmap != null){ bitmap.recycle(); } } }
我們在上面的代碼中可以使用回調方法onCreateLoader()來創建一個新的加載器。
LoaderManager.LoaderCallbacks:是一個用於客戶端與LoaderManager交互的回調接口。
LoaderManager:是一個抽像類,並關聯到一個Activity或Fragment,管理一個或多個裝載器的實例。這能夠幫助一個應用管理那些與Activity或Fragment的生命周期相關的運行長時間的的操作。最常見的方式是它與一個CursorLoader一起使用,然而應用是可以自由的寫它們自己的裝載器以加載其它類型的數據。
Loader:是一個執行異步數據加載的抽象類。它是加載器的基類。我們可以使用典型的CursorLoader,但是我們也可以實現自己的子類。如果加載器被激活,它們將監視它們的數據源並且當數據改變時發送新的結果。
AsyncTaskLoader:是一個抽象類,能夠提供一個AsyncTask來執行異步工作。
CursorLoader:它是AsyncTaskLoader的子類,它可以查詢ContentResolver然后返回一個Cursor。該類實現了加載器的協議並能夠以標准的方式查詢cursors.以AsyncTaskLoader為基礎,在后台線程中執行cursor查詢以便不阻塞程序的UI。使用這個加載器是從ContentProvider中異步查詢數據的最好的方式,而不是使用Activity或Fragment的API去執行一個被管理的查詢。
一個使用加載器的應用一般的會包括以下:
1. An Activity or Fragment.
2. An instance of the LoaderManager.
3. 一個被ContentProvider所支持的加載器,該加載器用以加載數據。當然了,我們也可以繼承Loader或AsyncTaskLoader以實現自己的子類,去加載其他數據源的數據。
4. 實現LoaderManager.LoaderCallbacks接口,我們可以創建新的裝載器以及管理已有的加載器的引用。
5. 一種展現加載器數據的方式 ,比如可以使用SimpleCursorAdapter。
6. 一個數據源,比如ContentProvider。
LoaderManager用以管理一個Activiry或Fragment中的一個或多個裝載器.但每個Activity或Fragment只能一個LoaderManager.
那么如何初始化一個加載器呢?需要在Activity的onCreate()方法或是Fragment中的onActivityCreate()方法中實現。
在本示例的代碼中,通過如下代碼初始化加載器。
getSupportLoaderManager().initLoader(0, null, this);
如果是在Android3.0中則需要通過如下代碼進行初始化。
getLoaderManager().initLoader(0, null, this);
initLoader()方法有如下的參數:
1. 一個標識加載器的唯一ID。在本示例中,加載器的ID是0。
2. 一個可選的參數以支持加載器的構建。在本示例中,使用null。
3. 一個LoaderManager.LoaderCallbacks的實現。被LoaderManager調用以報告裝載器的事件,在本示例中實現了LoaderManager.LoaderCallbacks這個接口,因此它傳遞this這個參數。
initLoader()方法能夠確保加載器被初始化並激活。它有兩種可能的結果:
1. 如果被ID標識的加載器已經存在,那么該加載器將被重新使用。
2. 如果被ID標識的加載器不存在,initLoader()將激發LoaderManager.LoaderCallbacks的onCreateLoader()方法.這能夠實現並返回一個新加載器。
一般情況下,對LoaderManager.LoaderCallbacks的實現都與加載器緊密聯系着一起,並且當加載器的狀態變化時該方法將被調用。如果調用者在這個時候處於開始狀態,並且那請求的加載器已經存在且已經產生了它的數據,然后系統將立即會調用onLoadFinished()方法(當initLoader()方法執行過程中),所以我們必須為這個事情的發生做好准備。
我們需要注意iniLoader()方法返回被創建的加載器,我們不必捕獲對它的一個引用。因為LoaderManager會自動的管理加載器的生命。LoaderManager會在必要的時候開始和停止加載工作,並且會保持加載器的狀態和加載器相關聯的內容。這就意味着,我們很少直接與加載器們交互。我們大多數情況下當特殊的事件發生時去使用LoaderManager.LoaderCallbacks的回調方法去介入加載過程。
如果我們想放棄舊的數據,則應使用restartLoader()方法。
LoaderManager.LoaderCallbacks包含以下的方法:
1. onCreateLoader():根據所給出的ID,初始化並返回一個新的加載器。
2. onLoadFinished():當一個先前被創建的加載器完成了它的加載過程時被調用。
3.onLoaderReset():當一個先前被創建的加載器被重置時被調用,然后使加載器的數據無效。
在onCreateLoader()方法中,能夠使我們創建一個新的加載器,或者是實現我們自己的加載器。
在本示例中創建加載器的代碼如下:
// 創建新的加載器 @Override public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { // TODO Auto-generated method stub // 為了查看信息,需要用到CursorLoader,也可以實現我們自己的加載器。 CursorLoader cursorLoader = new CursorLoader( this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, STORE_IMAGES, null, null, null); return cursorLoader; }
在onLoadFinished()方法中,加載器一旦知道應用不再使用數據時,加載器將會釋放數據。如果數據是一個從CursorLoader來的cursor,我們不應該調用它自己的close()方法,如果cursor被放置在CursorAdapter或是SimpleCursorAdapter中,我們應該使用它自己的swapCursor()方法以使舊的Cursor不被關閉。
在本示例中代碼如下:
@Override public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) { // TODO Auto-generated method stub // 使用swapCursor()方法,以使舊的游標不被關閉. simpleCursorAdapter.swapCursor(cursor); }
在onLoaderReset()方法中,當一個先前被創建的加載器被重置時該方法會被調用,然后使加載器的數據無效。該回調方法讓我們發現數據在什么時候將被釋放以便我們刪除對它的引用。通常我們實現swapCursor()方法,並給方法傳遞一個null參數。
在本示例中代碼如下:
@Override public void onLoaderReset(Loader<Cursor> arg0) { // TODO Auto-generated method stub simpleCursorAdapter.swapCursor(null); }
我通過Uri的形式查詢到圖片,代碼如下:
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon(). appendPath(Long.toString(id)).build(); FileUtil file = new FileUtil(); ContentResolver resolver = getContentResolver(); // 從Uri中讀取圖片資源 try { mContent = file.readInputStream(resolver.openInputStream(Uri.parse(uri.toString()))); bitmap = file.getBitmapFromBytes(mContent, null); ivImageShow.setImageBitmap(bitmap); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); }
FileUtil.java文件主要是對圖片Uri的處理過程。代碼如下:
package com.device.photo; import java.io.ByteArrayOutputStream; import java.io.InputStream; import android.graphics.Bitmap; import android.graphics.BitmapFactory; /** * 文件操作類 * @Description: 文件操作類 * @FileName: FileUtil.java * @Package com.device.photo * @Author Hanyonglu * @Date 2012-5-10 下午01:37:49 * @Version V1.0 */ public class FileUtil { public FileUtil() { // TODO Auto-generated constructor stub } /** * InputStream to byte * @param inStream * @return * @throws Exception */ public byte[] readInputStream(InputStream inStream) throws Exception { byte[] buffer = new byte[1024]; int len = -1; ByteArrayOutputStream outStream = new ByteArrayOutputStream(); while ((len = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, len); } byte[] data = outStream.toByteArray(); outStream.close(); inStream.close(); return data; } /** * Byte to bitmap * @param bytes * @param opts * @return */ public Bitmap getBitmapFromBytes(byte[] bytes, BitmapFactory.Options opts) { if (bytes != null){ if (opts != null){ return BitmapFactory.decodeByteArray(bytes, 0, bytes.length,opts); } else{ return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } } return null; } }
在本示例中,我們不需要配置任何的權限就可以實現。
關於Android中使用加載器(Loader)來實現獲取本機中的所有圖片的實現過程及加載器(Loader)的使用大致上就介紹到這里了。
關於加載器更多的信息大家可以查閱官方幫助文檔:http://developer.android.com/guide/topics/fundamentals/loaders.html
最后,希望轉載的朋友能夠尊重作者的勞動成果,加上轉載地址:http://www.cnblogs.com/hanyonglu/archive/2012/05/10/2494908.html,謝謝。
Github:https://github.com/hanyonglu/CursorLoader
示例下載:點擊下載。
完畢。^_^
