2013/07/15

Implement ImageLoader with picasso

之前發表了一篇使用 volley 來當作 ImageLoader, 但是實際上我自己都是使用 Picasso,
至少作為基本的 Loader 而言, 我認為 Picasso 好用多了.

環境:
SDK: android-17
IDE: android-studio
Device: Moto Atrix with 4.2.2
External Libraries: picasso

本篇跟之前一樣, 使用簡單的 gridview 當作 main layout.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<GridView
android:id="@+id/gridview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
android:alwaysDrawnWithCache="false"
android:fadeScrollbars="true"
android:cacheColorHint="@android:color/transparent"
android:numColumns="3"
android:columnWidth="160dp"/>

</RelativeLayout>

gridview row 的 layout 跟之前稍微有點不一樣.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/imageview"
android:layout_width="160dp"
android:layout_height="240dp"
android:layout_centerInParent="true"
android:scaleType="centerInside"
android:contentDescription="@null"/>

</RelativeLayout>

這邊我並不使用 Picasso 內建的 loader, 而是根據自己的需求實作.
此範例中我只是很簡單的實作 Disk Cache 的機制.
MyURLConnectionLoader.java
package tw.clotai.picassoexample;

import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;

import com.squareup.picasso.Loader;

import java.io.*;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* A {@link com.squareup.picasso.Loader} which uses {@link java.net.HttpURLConnection} to chapter_main images. A disk cache of 10MB
* will automatically be installed in the application's cache directory, when available.
*/
public class MyURLConnectionLoader implements Loader {

private static final String HASH_ALGORITHM = "MD5";
private static final int RADIX = 10 + 26;

private final Context context;

public MyURLConnectionLoader(Context context) {
this.context = context.getApplicationContext();
}

protected HttpURLConnection openConnection(String path) throws IOException {
return (HttpURLConnection) new URL(path).openConnection();
}

@Override
public Response load(Uri uri, boolean b) throws IOException {
Response res = null;

String cacahePath = PicassoHelper.getCachePath(context);
if (cacahePath == null) {
return res;
}

String url = uri.toString();

if (url == null) {
return res;
}

String key = generateUniqueID(url);
if (key == null) {
return res;
}

if (!url.startsWith("http")) {
File f = new File(url);
if (f.exists()) {
FileInputStream fin = new FileInputStream(f);
res = new Response(fin, true);
}
return res;
}

File f = new File(cacahePath, key);
if (f.exists()) {
FileInputStream fin = new FileInputStream(f);
res = new Response(fin, true);

} else {
InputStream in = null;
try {
HttpURLConnection connection = openConnection(url);
connection.setRequestProperty("User-Agent",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.91 Safari/537.11");
connection.setRequestProperty("Accept-Language", "en-US,en;q=0.8");
connection.setUseCaches(true);
connection.setDoInput(true);
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
connection.setRequestMethod("GET");
connection.connect();

in = connection.getInputStream();
copyToFile(in, f);
} finally {
if (in != null) {
in.close();
}
}

if (f.exists()) {
FileInputStream fin = new FileInputStream(f);
res = new Response(fin, false);
}
}
return res;
}



private String generateUniqueID(String url) {

StringBuilder sb = new StringBuilder();

byte[] md5;
BigInteger bi = null;

try {
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
digest.update(url.getBytes());
md5 = digest.digest();

bi = new BigInteger(md5).abs();

} catch (NoSuchAlgorithmException e) {
}

if (bi == null) {
return null;
}

sb.append(bi.toString(RADIX));
return sb.toString();
}

private void copyToFile(InputStream in, File dest) throws IOException {
FileOutputStream fos = null;
BufferedInputStream bin = null;
BufferedOutputStream bout = null;

try {
fos = new FileOutputStream(dest);
bin = new BufferedInputStream(in, 8192);
bout = new BufferedOutputStream(fos, 8192);

byte[] rdata = new byte[8192];
int count = 0;
while ((count = bin.read(rdata)) != -1) {
bout.write(rdata, 0, count);
}
bout.flush();

} finally {
if (bin != null) {
bin.close();
}
if (fos != null) {
if (fos.getFD() != null) {
fos.getFD().sync();
}
}

if (bout != null) {
bout.close();
}
if (fos != null) {
fos.close();
}
}
}


}

另外為了方便使用, 建立一個 singleton PicassoHelper.
以下為部份內容
public static PicassoHelper getInstance(Context c) {
if (helper == null) {
if (c == null) {
return null;
}
synchronized (PicassoHelper.class) {
if (helper == null) {
helper = new PicassoHelper(c);
}
}
}
return helper;
}

private PicassoHelper(Context c) {
mContext = c.getApplicationContext();

Picasso.Builder builder = new Picasso.Builder(c);
builder.loader(new MyURLConnectionLoader(c));

mPicasso = builder.build();
mPicasso.setDebugging(false);
PicassoHelper.getCachePath(mContext);
}

public void load(String url, ImageView v) {
v.setImageResource(R.drawable.ic_launcher);

if (url == null) {
return;
}

mPicasso.load(url)
.resize(160, 240)
.error(R.drawable.ic_launcher)
.centerCrop()
.into(v);
}

這樣大致上完成了.
同樣使用上次的範例, 執行結果如下:



原始碼下載:
PicassoExampleProject.zip

參考資料:
#1 Picasso v1.1.1