android上的一个网络接口和图片缓存框架enif简析

来源:互联网 发布:座机电话录音软件 编辑:程序博客网 时间:2024/05/21 07:09

android上的一个网络接口和图片缓存框架enif详细介绍:底层网络接口采用apache的httpclient连接池框架、图片缓存采用基于LRU的算法等等,需要了解的朋友可以详细参考下

1.底层网络接口采用apache的httpclient连接池框架; 
2.图片缓存采用基于LRU的算法; 
3.网络接口采用监听者模式; 
4.包含图片的OOM处理(及时回收处理技术的应用); 

图片核心处理类:CacheView.java 

复制代码代码如下:

package xiaogang.enif.image; 
import java.io.FilterInputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.lang.ref.SoftReference; 
import java.util.HashMap; 
import java.util.concurrent.RejectedExecutionException; 
import org.apache.http.HttpEntity; 
import org.apache.http.HttpResponse; 
import org.apache.http.HttpStatus; 
import org.apache.http.client.methods.HttpGet; 
import xiaogang.enif.utils.HttpManager; 
import xiaogang.enif.utils.IOUtils; 
import xiaogang.enif.utils.LogUtils; 
import xiaogang.enif.utils.LruCache; 
import android.app.ActivityManager; 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.BitmapFactory.Options; 
import android.graphics.Canvas; 
import android.graphics.drawable.BitmapDrawable; 
import android.os.AsyncTask; 
import android.text.TextUtils; 
import android.util.AttributeSet; 
import android.widget.ImageView; 
public class CacheView extends ImageView { 
private static final int DEFAULT_RES_ID = 0; 
private int mDefaultImage = DEFAULT_RES_ID; 
private static LruCache<String, Bitmap> mLruCache; 
private static HashMap<Integer, SoftReference<Bitmap>> mResImage; 
private Context mContext; 
private LogUtils mLog = LogUtils.getLog(CacheView.class); 
public CacheView(Context context, AttributeSet attrs, int defStyle) { 
super(context, attrs, defStyle); 
init(context); 

public CacheView(Context context, AttributeSet attrs) { 
super(context, attrs); 
init(context); 

public CacheView(Context context) { 
super(context); 
init(context); 

private void init(Context context) { 
mContext = context; 
if (mLruCache == null) { 
final int cacheSize = getCacheSize(context); 
mLruCache = new LruCache<String, Bitmap>(cacheSize) { 
@Override 
protected int sizeOf(String key, Bitmap bitmap) { 
// The cache size will be measured in bytes rather than 
// number of items. 
return bitmap.getRowBytes() * bitmap.getHeight(); 

@Override 
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, 
Bitmap newValue) { 
if (evicted && oldValue != null && !oldValue.isRecycled()) { 
oldValue.recycle(); 
oldValue = null; 


}; 

if (mResImage == null) { 
mResImage = new HashMap<Integer, SoftReference<Bitmap>>(); 


@Override 
protected void onDraw(Canvas canvas) { 
BitmapDrawable drawable = (BitmapDrawable)getDrawable(); 
if (drawable == null) { 
setDefaultImage(); 
} else { 
if (drawable.getBitmap() == null || drawable.getBitmap().isRecycled()) { 
setDefaultImage(); 


try { 
super.onDraw(canvas); 
} catch(RuntimeException ex) { 


public void setImageUrl(String url, int resId) { 
setTag(url); 
Bitmap bitmap = getBitmapFromCache(url); 
if (bitmap == null || bitmap.isRecycled()) { 
mDefaultImage = resId; 
setDefaultImage(); 
try { 
new DownloadTask().execute(url); 
} catch (RejectedExecutionException e) { 
// do nothing, just keep not crash 

} else { 
setImageBitmap(bitmap); 


private void setDefaultImage() { 
if (mDefaultImage != DEFAULT_RES_ID) { 
setImageBitmap(getDefaultBitmap(mContext)); 


private Bitmap getDefaultBitmap(Context context) { 
SoftReference<Bitmap> loading = mResImage.get(mDefaultImage); 
if (loading == null || loading.get() == null || loading.get().isRecycled()) { 
loading = new SoftReference<Bitmap>(BitmapFactory.decodeResource( 
context.getResources(), mDefaultImage)); 
mResImage.put(mDefaultImage, loading); 

return loading.get(); 

private class DownloadTask extends AsyncTask<String, Void, Bitmap> { 
private String mParams; 
@Override 
public Bitmap doInBackground(String... params) { 
mParams = params[0]; 
final Bitmap bm = download(mParams); 
addBitmapToCache(mParams, bm); 
return bm; 

@Override 
public void onPostExecute(Bitmap bitmap) { 
String tag = (String)getTag(); 
if (!TextUtils.isEmpty(tag) && tag.equals(mParams)) { 
if (bitmap != null) { 
setImageBitmap(bitmap); 



}; 
/* 
* An InputStream that skips the exact number of bytes provided, unless it 
* reaches EOF. 
*/ 
static class FlushedInputStream extends FilterInputStream { 
public FlushedInputStream(InputStream inputStream) { 
super(inputStream); 

@Override 
public long skip(long n) throws IOException { 
long totalBytesSkipped = 0L; 
while (totalBytesSkipped < n) { 
long bytesSkipped = in.skip(n - totalBytesSkipped); 
if (bytesSkipped == 0L) { 
int b = read(); 
if (b < 0) { 
break; // we reached EOF 
} else { 
bytesSkipped = 1; // we read one byte 


totalBytesSkipped += bytesSkipped; 

return totalBytesSkipped; 


private Bitmap download(String url) { 
InputStream in = null; 
HttpEntity entity = null; 
Bitmap bmp = null; 
try { 
final HttpGet get = new HttpGet(url); 
final HttpResponse response = HttpManager.execute(mContext, get); 
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { 
entity = response.getEntity(); 
in = entity.getContent(); 
try { 
bmp = getDecodeBitmap(in, url); 
} catch (OutOfMemoryError err) { 
Runtime.getRuntime().gc(); 
bmp = getDecodeBitmap(in, url); 

} else { 
get.abort(); 
return bmp; 

addBitmapToCache(url, bmp); 
} catch (IOException e) { 
return bmp; 
} finally { 
IOUtils.closeStream(in); 

return bmp; 

private final Bitmap getDecodeBitmap(InputStream in, String url) { 
Options options = new Options(); 
options.inPurgeable = true; 
options.inInputShareable = true; 
return BitmapFactory.decodeStream(new FlushedInputStream(in), null, options); 

private final void addBitmapToCache(String url, Bitmap bitmap) { 
if (bitmap != null) { 
mLruCache.put(url, bitmap); 
Runtime.getRuntime().gc(); 


private final Bitmap getBitmapFromCache(String url) { 
return mLruCache.get(url); 

private int getCacheSize(Context context) { 
// According to the phone memory, set a proper cache size for LRU cache 
// dynamically. 
final ActivityManager am = (ActivityManager)context 
.getSystemService(Context.ACTIVITY_SERVICE); 
final int memClass = am.getMemoryClass(); 
int cacheSize; 
if (memClass <= 24) { 
cacheSize = (memClass << 20) / 24; 
} else if (memClass <= 36) { 
cacheSize = (memClass << 20) / 18; 
} else if (memClass <= 48) { 
cacheSize = (memClass << 20) / 12; 
} else { 
cacheSize = (memClass << 20) >> 3; 

mLog.debug("cacheSize == "+cacheSize); 
System.out.println("cacheSize == "+cacheSize); 
return cacheSize; 

public static void recycle() { 
if (mLruCache != null && !mLruCache.isEmpty()) { 
mLruCache.evictAll(); 
mLruCache = null; 

if (mResImage != null) { 
for (SoftReference<Bitmap> reference : mResImage.values()) { 
Bitmap bitmap = reference.get(); 
if (bitmap != null && !bitmap.isRecycled()) { 
bitmap.recycle(); 
bitmap = null; 


mResImage = null; 




说明: 
1)entryRemoved在做bitmap recycle的时候的3个条件缺一不可; 
2)onDraw里面判断图片是否被回收,如果回收,需要设置默认图片; 
3)add bitmap到cache的时候Runtime.getRuntime().gc();的调用; 
4)getCacheSize可以根据手机具体的内存来动态设置我们实际需要的缓存大小; 
5)退出时,记得调用recycle()方法; 
网络接口核心类:WSAPI.java, WSCfg.java, WSTask.java 
复制代码代码如下:

<STRONG>package xiaogang.enif.net; 
import java.util.ArrayList; 
import org.apache.http.message.BasicNameValuePair; 
/** 
* web service configuration file 
* */ 
public class WSCfg { 
public static final int USER_LOGIN = 0;//action 
public static final int USER_LOGOUT = 1;//action 
public static ArrayList<BasicNameValuePair> sValuePairs;//common vps 
static { 
sValuePairs = new ArrayList<BasicNameValuePair>(); 
sValuePairs.add(new BasicNameValuePair("v", "1.0")); 
sValuePairs.add(new BasicNameValuePair("format", "json")); 

}</STRONG> 

复制代码代码如下:

<STRONG>package xiaogang.enif.net; 
import java.util.ArrayList; 
import java.util.concurrent.RejectedExecutionException; 
import org.apache.http.message.BasicNameValuePair; 
import xiaogang.enif.net.WSTask.TaskListener; 
import android.content.Context; 
public class WSAPI { 
private WSAPI() { 

public static void execute(Context context, TaskListener listener, int action, 
ArrayList<BasicNameValuePair> vp) { 
try { 
new WSTask(context, listener, action, vp).execute(); 
} catch (RejectedExecutionException e) { 
// do nothing, just keep not crashing. 



</STRONG> 

复制代码代码如下:

<STRONG>package xiaogang.enif.net; 
import java.io.IOException; 
import java.io.InputStream; 
import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.Iterator; 
import org.apache.http.HttpEntity; 
import org.apache.http.HttpResponse; 
import org.apache.http.HttpStatus; 
import org.apache.http.client.entity.UrlEncodedFormEntity; 
import org.apache.http.client.methods.HttpPost; 
import org.apache.http.message.BasicNameValuePair; 
import org.json.JSONArray; 
import org.json.JSONException; 
import org.json.JSONObject; 
import xiaogang.enif.utils.HttpManager; 
import xiaogang.enif.utils.IOUtils; 
import xiaogang.enif.utils.LogUtils; 
import android.app.Activity; 
import android.content.Context; 
import android.os.AsyncTask; 
import android.text.TextUtils; 
public class WSTask extends AsyncTask<Void, Void, Object> { 
private int mAction; 
private String mErrorCode; 
private Object mParameter; 
private Context mContext; 
private TaskListener mTaskListener; 
private Exception mReason; 
private final LogUtils mLog = LogUtils.getLog(WSTask.class); 
public WSTask(Context context, TaskListener listener, int action, Object paramObject) { 
mContext = context; 
mTaskListener = listener; 
mParameter = paramObject; 
mAction = action; 

@Override 
public Object doInBackground(Void... arg0) { 
Object result = null; 
try { 
@SuppressWarnings("unchecked") 
ArrayList<BasicNameValuePair> vps = (ArrayList<BasicNameValuePair>)mParameter; 
final String jsonString = request(mContext, "your url", vps); 
mLog.debug(jsonString); 
result = parseJson(jsonString); 
if (result != null && result instanceof String 
&& TextUtils.isDigitsOnly((String)result)) { 
mErrorCode = (String)result; 
return null; 

} catch (Exception e) { 
mReason = e; 
mLog.error(e.getMessage()); 
return null; 

return result; 

@Override 
public void onPostExecute(Object result) { 
if (mContext== null) { 
clearTask(); 
return; 

if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) { 
clearTask(); 
return; 

if (result == null || mReason != null) { 
mTaskListener.onFailed(mAction, mErrorCode, mReason); 
} else { 
mTaskListener.onSuccess(mAction, result); 

clearTask(); 

private String request(Context context, String url, ArrayList<BasicNameValuePair> vp) 
throws IOException { 
final HttpPost post = new HttpPost(url); 
post.setEntity(new UrlEncodedFormEntity(vp, "UTF_8")); 
InputStream in = null; 
HttpEntity entity = null; 
try { 
final HttpResponse response = HttpManager.execute(context, post); 
final int statusCode = response.getStatusLine().getStatusCode(); 
if (statusCode == HttpStatus.SC_OK) { 
entity = response.getEntity(); 
if (entity != null) { 
in = entity.getContent(); 
return IOUtils.stream2String(in); 

} else { 
post.abort(); 
mLog.error("http code: " + response.getStatusLine().getStatusCode()); 

return null; 
} catch (IOException ex) { 
post.abort(); 
throw ex; 
} catch (RuntimeException ex) { 
post.abort(); 
throw ex; 
} finally { 
if(entity!=null) { 
entity.consumeContent(); 

IOUtils.closeStream(in); 


private Object parseJson(String jsonString) throws IOException { 
try { 
JSONObject jobj = new JSONObject(jsonString); 
if (jobj.has("errorcode")) { 
return jobj.optString("errorcode"); 

if (jobj.has("resultlist")) { 
ArrayList<HashMap<String, String>> arrList; 
arrList = new ArrayList<HashMap<String, String>>(); 
JSONArray jsonArray = jobj.optJSONArray("resultlist"); 
final int len = jsonArray.length(); 
for (int i = 0; i < len; i++) { 
final JSONObject obj = (JSONObject)jsonArray.opt(i); 
arrList.add(parse2Map(obj)); 

return arrList; 
} else { 
return parse2Map(jobj); 

} catch (JSONException e) { 
IOException ioe = new IOException("Invalid json String..."); 
ioe.initCause(e); 
throw ioe; 


private HashMap<String, String> parse2Map(JSONObject jsonObj) throws IOException { 
final HashMap<String, String> hashMap = new HashMap<String, String>(); 
@SuppressWarnings("unchecked") 
final Iterator<String> keyIter = jsonObj.keys(); 
String key, value; 
while (keyIter != null && keyIter.hasNext()) { 
key = keyIter.next(); 
value = jsonObj.optString(key); 
hashMap.put(key, value); 

return hashMap; 

private void clearTask() { 
mTaskListener = null; 
mParameter = null; 
mContext = null; 

public interface TaskListener { 
public void onSuccess(int action, Object result); 
public void onFailed(int action, String errcode, Exception ex); 


</STRONG> 

说明: 
1)根据你的服务器接口实际情况,去修改parseJson方法; 
2)WSCfg里面可以定义接口的action; 
sample: 
复制代码代码如下:

package xiaogang.enif.ui; 
import java.util.ArrayList; 
import org.apache.http.message.BasicNameValuePair; 
import xiaogang.enif.R; 
import xiaogang.enif.image.CacheView; 
import xiaogang.enif.net.WSAPI; 
import xiaogang.enif.net.WSCfg; 
import xiaogang.enif.net.WSTask.TaskListener; 
import xiaogang.enif.widget.ListsApdater; 
import android.app.Activity; 
import android.os.Bundle; 
import android.widget.ListView; 
public class MainActivity extends Activity implements TaskListener { 
ListView mList; 
ListsApdater mAdapter; 
@Override 
public void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.main); 
setupViews(); 

private void setupViews() { 
mList = (ListView)findViewById(R.id.list); 
mAdapter = new ListsApdater(this, mUrls); 
mList.setAdapter(mAdapter); 
final ArrayList<BasicNameValuePair> vp = new ArrayList<BasicNameValuePair>(); 
vp.addAll(WSCfg.sValuePairs); 
vp.add(new BasicNameValuePair("imei", "123")); 
vp.add(new BasicNameValuePair("imsi", "123")); 
WSAPI.execute(this, this, WSCfg.USER_LOGIN, vp); 

@Override 
protected void onDestroy() { 
super.onDestroy(); 
mAdapter.recycle(); 
CacheView.recycle(); 

private String[] mUrls = { 
"http://a3.twimg.com/profile_images/670625317/aam-logo-v3-twitter.png", 
"http://a3.twimg.com/profile_images/740897825/AndroidCast-350_normal.png", 
"http://a3.twimg.com/profile_images/121630227/Droid_normal.jpg", 
"http://a1.twimg.com/profile_images/957149154/twitterhalf_normal.jpg", 
"http://a1.twimg.com/profile_images/97470808/icon_normal.png", 
"http://a3.twimg.com/profile_images/511790713/AG.png", 
"http://a3.twimg.com/profile_images/956404323/androinica-avatar_normal.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man_normal.png", 
"http://a3.twimg.com/profile_images/72774055/AndroidHomme-LOGO_normal.jpg", 
"http://a1.twimg.com/profile_images/349012784/android_logo_small_normal.jpg", 
"http://a1.twimg.com/profile_images/841338368/ea-twitter-icon.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet_normal.png", 
"http://a1.twimg.com/profile_images/850960042/elandroidelibre-logo_300x300_normal.jpg", 
"http://a1.twimg.com/profile_images/655119538/andbook.png", 
"http://a3.twimg.com/profile_images/768060227/ap4u_normal.jpg", 
"http://a1.twimg.com/profile_images/74724754/android_logo_normal.png", 
"http://a3.twimg.com/profile_images/681537837/SmallAvatarx150_normal.png", 
"http://a1.twimg.com/profile_images/63737974/2008-11-06_1637_normal.png", 
"http://a3.twimg.com/profile_images/548410609/icon_8_73.png", 
"http://a1.twimg.com/profile_images/612232882/nexusoneavatar_normal.jpg", 
"http://a1.twimg.com/profile_images/213722080/Bugdroid-phone_normal.png", 
"http://a1.twimg.com/profile_images/645523828/OT_icon_090918_android_normal.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet.png", 
"http://a1.twimg.com/profile_images/850960042/elandroidelibre-logo_300x300_normal.jpg", 
"http://a1.twimg.com/profile_images/655119538/andbook_normal.png", 
"http://a3.twimg.com/profile_images/511790713/AG_normal.png", 
"http://a3.twimg.com/profile_images/956404323/androinica-avatar.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man_normal.png", 
"http://a3.twimg.com/profile_images/72774055/AndroidHomme-LOGO_normal.jpg", 
"http://a1.twimg.com/profile_images/349012784/android_logo_small_normal.jpg", 
"http://a1.twimg.com/profile_images/841338368/ea-twitter-icon_normal.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a1.twimg.com/profile_images/850960042/elandroidelibre-logo_300x300.jpg", 
"http://a1.twimg.com/profile_images/655119538/andbook_normal.png", 
"http://a3.twimg.com/profile_images/511790713/AG_normal.png", 
"http://a3.twimg.com/profile_images/956404323/androinica-avatar_normal.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man_normal.png", 
"http://a3.twimg.com/profile_images/121630227/Droid.jpg", 
"http://a1.twimg.com/profile_images/97470808/icon_normal.png", 
"http://a3.twimg.com/profile_images/511790713/AG_normal.png", 
"http://a3.twimg.com/profile_images/956404323/androinica-avatar_normal.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man.png", 
"http://a3.twimg.com/profile_images/72774055/AndroidHomme-LOGO_normal.jpg", 
"http://a1.twimg.com/profile_images/349012784/android_logo_small_normal.jpg", 
"http://a1.twimg.com/profile_images/841338368/ea-twitter-icon_normal.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet.png", 
"http://a3.twimg.com/profile_images/121630227/Droid_normal.jpg", 
"http://a1.twimg.com/profile_images/957149154/twitterhalf_normal.jpg", 
"http://a1.twimg.com/profile_images/97470808/icon.png", 
"http://a3.twimg.com/profile_images/511790713/AG_normal.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man_normal.png", 
"http://a3.twimg.com/profile_images/72774055/AndroidHomme-LOGO_normal.jpg", 
"http://a1.twimg.com/profile_images/349012784/android_logo_small_normal.jpg", 
"http://a1.twimg.com/profile_images/841338368/ea-twitter-icon.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet_normal.png", 
"http://a1.twimg.com/profile_images/850960042/elandroidelibre-logo_300x300_normal.jpg", 
"http://a1.twimg.com/profile_images/655119538/andbook_normal.png", 
"http://a3.twimg.com/profile_images/768060227/ap4u_normal.jpg", 
"http://a1.twimg.com/profile_images/74724754/android_logo.png", 
"http://a3.twimg.com/profile_images/681537837/SmallAvatarx150_normal.png", 
"http://a1.twimg.com/profile_images/63737974/2008-11-06_1637_normal.png", 
"http://a3.twimg.com/profile_images/548410609/icon_8_73_normal.png", 
"http://a1.twimg.com/profile_images/612232882/nexusoneavatar_normal.jpg", 
"http://a1.twimg.com/profile_images/213722080/Bugdroid-phone_normal.png", 
"http://a1.twimg.com/profile_images/645523828/OT_icon_090918_android.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet_normal.png", 
"http://a1.twimg.com/profile_images/850960042/elandroidelibre-logo_300x300_normal.jpg", 
"http://a1.twimg.com/profile_images/655119538/andbook.png", 
"http://a3.twimg.com/profile_images/956404323/androinica-avatar_normal.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man_normal.png", 
"http://a3.twimg.com/profile_images/72774055/AndroidHomme-LOGO_normal.jpg", 
"http://a1.twimg.com/profile_images/349012784/android_logo_small_normal.jpg", 
"http://a1.twimg.com/profile_images/841338368/ea-twitter-icon.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet_normal.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a1.twimg.com/profile_images/850960042/elandroidelibre-logo_300x300_normal.jpg", 
"http://a1.twimg.com/profile_images/655119538/andbook_normal.png", 
"http://a3.twimg.com/profile_images/511790713/AG_normal.png", 
"http://a3.twimg.com/profile_images/956404323/androinica-avatar_normal.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man_normal.png", 
"http://a3.twimg.com/profile_images/121630227/Droid_normal.jpg", 
"http://a1.twimg.com/profile_images/957149154/twitterhalf.jpg", 
"http://a1.twimg.com/profile_images/97470808/icon_normal.png", 
"http://a3.twimg.com/profile_images/511790713/AG_normal.png", 
"http://a1.twimg.com/profile_images/909231146/Android_Biz_Man_normal.png", 
"http://a3.twimg.com/profile_images/72774055/AndroidHomme-LOGO_normal.jpg", 
"http://a1.twimg.com/profile_images/841338368/ea-twitter-icon_normal.png", 
"http://a3.twimg.com/profile_images/64827025/android-wallpaper6_2560x160_normal.png", 
"http://a3.twimg.com/profile_images/77641093/AndroidPlanet_normal.png" 
}; 
@Override 
public void onSuccess(int action, Object result) { 
switch (action) { 
case WSCfg.USER_LOGIN: 
break; 
case WSCfg.USER_LOGOUT: 
break; 


@Override 
public void onFailed(int action, String errcode, Exception ex) { 
switch (action) { 
case WSCfg.USER_LOGIN: 
break; 
case WSCfg.USER_LOGOUT: 
break; 




复制代码代码如下:

package xiaogang.enif.widget; 
import xiaogang.enif.R; 
import xiaogang.enif.image.CacheView; 
import android.content.Context; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.ViewGroup; 
import android.widget.BaseAdapter; 
import android.widget.TextView; 
public class ListsApdater extends BaseAdapter { 
private String[] mUrls; 
private LayoutInflater mInflater; 
public ListsApdater(Context context, String[] urls) { 
mUrls = urls; 
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 

@Override 
public int getCount() { 
return mUrls.length; 

@Override 
public Object getItem(int position) { 
return position; 

@Override 
public long getItemId(int position) { 
return position; 

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
ViewHolder holder = null; 
if (null == convertView) { 
holder = new ViewHolder(); 
convertView = mInflater.inflate(R.layout.item, null); 
holder.view = (CacheView)convertView.findViewById(R.id.image); 
holder.text = (TextView)convertView.findViewById(R.id.text); 
convertView.setTag(holder); 
} else { 
holder = (ViewHolder)convertView.getTag(); 

holder.text.setText("item "+position); 
holder.view.setImageUrl(mUrls[position], R.drawable.stub); 
return convertView; 

public void recycle() { 
mUrls = null; 
mInflater = null; 

private class ViewHolder { 
CacheView view; 
TextView text; 



main.xml和item.xml 
复制代码代码如下:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="fill_parent" 
android:layout_height="fill_parent" 
android:orientation="vertical" > 
<ListView 
android:id="@+id/list" 
android:layout_width="fill_parent" 
android:layout_height="0dip" 
android:layout_weight="1" /> 
</LinearLayout> 

复制代码代码如下:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="fill_parent" 
android:layout_height="wrap_content" > 
<xiaogang.enif.image.CacheView 
android:id="@+id/image" 
android:layout_width="50dip" 
android:layout_height="50dip" 
android:scaleType="centerCrop" 
android:src="@drawable/stub" /> 
<TextView 
android:id="@+id/text" 
android:layout_width="0dip" 
android:layout_height="wrap_content" 
android:layout_gravity="left|center_vertical" 
android:layout_marginLeft="10dip" 
android:layout_weight="1" 
android:textSize="20dip" /> 
</LinearLayout> 

例子的效果图如下


android中图片的三级缓存cache策略(内存/文件/网络)


实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中

1.简介 
现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。

现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。 

2.图片缓存的原理 
实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。 

关于Java中对象的软引用(SoftReference),如果一个对象具有软引用,内存空间足够,垃 圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高 速缓存。使用软引用能防止内存泄露,增强程序的健壮性。 

从代码上来说,采用一个ImageManager来负责图片的管理和缓存,函数接口为public void loadBitmap(String url, Handler handler) ;其中url为要下载的图片地址,handler为图片下载成功后的回调,在handler中处理message,而message中包含了图片的信息以及bitmap对象。ImageManager中使用的ImageMemoryCache(内存缓存)、ImageFileCache(文件缓存)以及LruCache(最近最久未使用缓存)会在后续文章中介绍。 

3.代码ImageManager.java 

复制代码代码如下:

/* 
* 图片管理 
* 异步获取图片,直接调用loadImage()函数,该函数自己判断是从缓存还是网络加载 
* 同步获取图片,直接调用getBitmap()函数,该函数自己判断是从缓存还是网络加载 
* 仅从本地获取图片,调用getBitmapFromNative() 
* 仅从网络加载图片,调用getBitmapFromHttp() 

*/ 
public class ImageManager implements IManager 

private final static String TAG = "ImageManager"; 

private ImageMemoryCache imageMemoryCache; //内存缓存 

private ImageFileCache imageFileCache; //文件缓存 

//正在下载的image列表 
public static HashMap<String, Handler> ongoingTaskMap = new HashMap<String, Handler>(); 

//等待下载的image列表 
public static HashMap<String, Handler> waitingTaskMap = new HashMap<String, Handler>(); 

//同时下载图片的线程个数 
final static int MAX_DOWNLOAD_IMAGE_THREAD = 4; 

private final Handler downloadStatusHandler = new Handler(){ 
public void handleMessage(Message msg) 

startDownloadNext(); 

}; 

public ImageManager() 

imageMemoryCache = new ImageMemoryCache(); 
imageFileCache = new ImageFileCache(); 


/** 
* 获取图片,多线程的入口 
*/ 
public void loadBitmap(String url, Handler handler) 

//先从内存缓存中获取,取到直接加载 
Bitmap bitmap = getBitmapFromNative(url); 
if (bitmap != null) 

Logger.d(TAG, "loadBitmap:loaded from native"); 
Message msg = Message.obtain(); 
Bundle bundle = new Bundle(); 
bundle.putString("url", url); 
msg.obj = bitmap; 
msg.setData(bundle); 
handler.sendMessage(msg); 

else 

Logger.d(TAG, "loadBitmap:will load by network"); 
downloadBmpOnNewThread(url, handler); 


/** 
* 新起线程下载图片 
*/ 
private void downloadBmpOnNewThread(final String url, final Handler handler) 

Logger.d(TAG, "ongoingTaskMap'size=" + ongoingTaskMap.size()); 

if (ongoingTaskMap.size() >= MAX_DOWNLOAD_IMAGE_THREAD) 

synchronized (waitingTaskMap) 

waitingTaskMap.put(url, handler); 


else 

synchronized (ongoingTaskMap) 

ongoingTaskMap.put(url, handler); 

new Thread() 

public void run() 

Bitmap bmp = getBitmapFromHttp(url); 
// 不论下载是否成功,都从下载队列中移除,再由业务逻辑判断是否重新下载 
// 下载图片使用了httpClientRequest,本身已经带了重连机制 
synchronized (ongoingTaskMap) 

ongoingTaskMap.remove(url); 


if(downloadStatusHandler != null) 

downloadStatusHandler.sendEmptyMessage(0); 


Message msg = Message.obtain(); 
msg.obj = bmp; 
Bundle bundle = new Bundle(); 
bundle.putString("url", url); 
msg.setData(bundle); 

if(handler != null) 

handler.sendMessage(msg); 


}.start(); 


/** 
* 依次从内存,缓存文件,网络上加载单个bitmap,不考虑线程的问题 
*/ 
public Bitmap getBitmap(String url) 

// 从内存缓存中获取图片 
Bitmap bitmap = imageMemoryCache.getBitmapFromMemory(url); 
if (bitmap == null) 

// 文件缓存中获取 
bitmap = imageFileCache.getImageFromFile(url); 
if (bitmap != null) 

// 添加到内存缓存 
imageMemoryCache.addBitmapToMemory(url, bitmap); 

else 

// 从网络获取 
bitmap = getBitmapFromHttp(url); 


return bitmap; 


/** 
* 从内存或者缓存文件中获取bitmap 
*/ 
public Bitmap getBitmapFromNative(String url) 

Bitmap bitmap = null; 
bitmap = imageMemoryCache.getBitmapFromMemory(url); 

if(bitmap == null) 

bitmap = imageFileCache.getImageFromFile(url); 
if(bitmap != null) 

// 添加到内存缓存 
imageMemoryCache.addBitmapToMemory(url, bitmap); 


return bitmap; 


/** 
* 通过网络下载图片,与线程无关 
*/ 
public Bitmap getBitmapFromHttp(String url) 

Bitmap bmp = null; 

try 

byte[] tmpPicByte = getImageBytes(url); 

if (tmpPicByte != null) 

bmp = BitmapFactory.decodeByteArray(tmpPicByte, 0, 
tmpPicByte.length); 

tmpPicByte = null; 

catch(Exception e) 

e.printStackTrace(); 


if(bmp != null) 

// 添加到文件缓存 
imageFileCache.saveBitmapToFile(bmp, url); 
// 添加到内存缓存 
imageMemoryCache.addBitmapToMemory(url, bmp); 

return bmp; 


/** 
* 下载链接的图片资源 

* @param url 

* @return 图片 
*/ 
public byte[] getImageBytes(String url) 

byte[] pic = null; 
if (url != null && !"".equals(url)) 

Requester request = RequesterFactory.getRequester( 
Requester.REQUEST_REMOTE, RequesterFactory.IMPL_HC); 
// 执行请求 
MyResponse myResponse = null; 
MyRequest mMyRequest; 
mMyRequest = new MyRequest(); 
mMyRequest.setUrl(url); 
mMyRequest.addHeader(HttpHeader.REQ.ACCEPT_ENCODING, "identity"); 
InputStream is = null; 
ByteArrayOutputStream baos = null; 
try { 
myResponse = request.execute(mMyRequest); 
is = myResponse.getInputStream().getImpl(); 
baos = new ByteArrayOutputStream(); 
byte[] b = new byte[512]; 
int len = 0; 
while ((len = is.read(b)) != -1) 

baos.write(b, 0, len); 
baos.flush(); 

pic = baos.toByteArray(); 
Logger.d(TAG, "icon bytes.length=" + pic.length); 

catch (Exception e3) 

e3.printStackTrace(); 
try 

Logger.e(TAG, 
"download shortcut icon faild and responsecode=" 
+ myResponse.getStatusCode()); 

catch (Exception e4) 

e4.printStackTrace(); 


finally 

try 

if (is != null) 

is.close(); 
is = null; 


catch (Exception e2) 

e2.printStackTrace(); 

try 

if (baos != null) 

baos.close(); 
baos = null; 


catch (Exception e2) 

e2.printStackTrace(); 

try 

request.close(); 

catch (Exception e1) 

e1.printStackTrace(); 



return pic; 


/** 
* 取出等待队列第一个任务,开始下载 
*/ 
private void startDownloadNext() 

synchronized(waitingTaskMap) 

Logger.d(TAG, "begin start next"); 
Iterator iter = waitingTaskMap.entrySet().iterator(); 

while (iter.hasNext()) 


Map.Entry entry = (Map.Entry) iter.next(); 
Logger.d(TAG, "WaitingTaskMap isn't null,url=" + (String)entry.getKey()); 

if(entry != null) 

waitingTaskMap.remove(entry.getKey()); 
downloadBmpOnNewThread((String)entry.getKey(), (Handler)entry.getValue()); 

break; 




public String startDownloadNext_ForUnitTest() 

String urlString = null; 
synchronized(waitingTaskMap) 

Logger.d(TAG, "begin start next"); 
Iterator iter = waitingTaskMap.entrySet().iterator(); 

while (iter.hasNext()) 

Map.Entry entry = (Map.Entry) iter.next(); 
urlString = (String)entry.getKey(); 
waitingTaskMap.remove(entry.getKey()); 
break; 


return urlString; 


/** 
* 图片变为圆角 
* @param bitmap:传入的bitmap 
* @param pixels:圆角的度数,值越大,圆角越大 
* @return bitmap:加入圆角的bitmap 
*/ 
public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) 

if(bitmap == null) 
return null; 
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); 
Canvas canvas = new Canvas(output); 
final int color = 0xff424242; 
final Paint paint = new Paint(); 
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); 
final RectF rectF = new RectF(rect); 
final float roundPx = pixels; 
paint.setAntiAlias(true); 
canvas.drawARGB(0, 0, 0, 0); 
paint.setColor(color); 
canvas.drawRoundRect(rectF, roundPx, roundPx, paint); 
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); 
canvas.drawBitmap(bitmap, rect, rect, paint); 
return output; 


public byte managerId() 

return IMAGE_ID;