前面将源码过了一遍,不难看出,分为以下几个模块:
a) 对于Picasso的创建,利用Builder可对Picasso的线程池,下载器等进行动态配置,即使用用户
自定义的是完全可以的。
b )RequestHandler和Action:前者是指明了图片的来源,后者是指明了使用者,并且后者提供了
complete和error抽象方法供用户使用
c)Picasso自己定义了一套下载器,线程池,缓存;还定义了状态器
d)Request请求包含了图片所需的各种参数
e)BitmapHunter下载图片的具体执行者,如利用下载器等,包含一个Request和响应集合,
之所以有响应集合因为key值一样,key值中包含了各种图片变换的参数如旋转,centerCrop等,
即大家需求一样,那么就一起等待吧
f)Dispatcher分发命令,调用BitmapHunter去执行,利用mainHander去通知Picasso
g) Picasso供用户使用,包括添加删除回调等等,一些细节如添加Imageview时,会将之前的Action删除等等,adaper。
最后,里面有很多取消,添加,删除,下载成功回调操作,这些操作不会冲突吗?比如,下载成功一个后,
回调时又执行了Action暂停的操作等?—因为在同一个线程操作的,所以没问题
流程——
Picasso.with(mContext)
.load(imageFile)
.tag(ZApp.getInstance()) // 没在Request,在RequestCreator
.placeholder(R.color.img_not) // 没在Request,在RequestCreator
.resize(mItemSize, mItemSize)
.centerCrop()
.into(image);
1)into(image)
2)Request request = createRequest(started)
3)createKey(request)
4)内存有则返回,否则从RequestCreator下获取holder设置
5)创建ImageViewAction,同时设置了tag参数。
6)提交ImageViewAction:如果之前有相同的ImageView的请求,且Action不同则
删除之前的即if (target != null && targetToAction.get(target) != action);cancelExistingRequest(target);
并添加当前的ImageViewAction,即targetToAction.put(target, action);
7)提交成功dispatcher.dispatchSubmit(action)
8)performSubmit(Action action, boolean dismissFailed),
如果BitmapHunter hunter = hunterMap.get(action.getKey());存在则说明当前已经在获取这个相同的图片了,
直接hunter.attach(action)添加到这个hunter中即可,等待获取成功回调
9)否则创建一个hunter即hunter = forRequest(action.getPicasso(), this, cache, stats, action);
并交给线程池去处理service.submit(hunter)
10)线程池调用,内存中有则直接取出;
否则通过RequestHandler去加载即requestHandler.load(data, networkPolicy),加载成功后,
如果需要needsTransformation()则BitmapHunter类进行加锁执行transform的转换。
11)成功时dispatcher.dispatchComplete(this),会存入缓存,之后回到主线程
mainThreadHandler.sendMessage(mainThreadHandler.obtainMessage(HUNTER_BATCH_COMPLETE, copy));
12) 接着hunter.picasso.complete(hunter); 将hunter中所以的action回调action.complete(result, from);
即PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
接下来,看一下Picasso的例子吧
1 主界面
首先看一下listview的adapter
final class PicassoSampleAdapter extends BaseAdapter {
private static final int NOTIFICATION_ID = 666;
enum Sample { // 构造函数有2个参数,并且包括一个launch方法
GRID_VIEW("Image Grid View", SampleGridViewActivity.class),
GALLERY("Load from Gallery", SampleGalleryActivity.class),
CONTACTS("Contact Photos", SampleContactsActivity.class),
LIST_DETAIL("List / Detail View", SampleListDetailActivity.class),
SHOW_NOTIFICATION("Sample Notification", null) {
@Override public void launch(Activity activity) {//覆盖了父类的launch方法
// 具体代码见后分析
}
};
private final Class<? extends Activity> activityClass;//类的成员1
private final String name;//类的成员2
Sample(String name, Class<? extends Activity> activityClass) {
this.activityClass = activityClass;
this.name = name;
}
public void launch(Activity activity) {// 类的方法
activity.startActivity(new Intent(activity, activityClass));
activity.finish();
}
} // enum Sample end
private final LayoutInflater inflater;
public PicassoSampleAdapter(Context context) { inflater = LayoutInflater.from(context); }
@Override public int getCount() { return Sample.values().length; }
@Override public Sample getItem(int position) { return Sample.values()[position]; }
@Override public long getItemId(int position) { return position; }
@Override public View getView(int position, View convertView, ViewGroup parent) {
TextView view = (TextView) convertView;
if (view == null)
view = (TextView) inflater.inflate(R.layout.picasso_sample_activity_item, parent, false);
view.setText(getItem(position).name);
return view;
}
}
主界面简析
import android.widget.ToggleButton;//android:textOff="@string/more",android:textOn="@string/hide"
activityList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
adapter.getItem(position).launch(PicassoSampleActivity.this);//启动配置的Activity
}
});
@Override
protected void onDestroy() {
super.onDestroy();
Picasso.with(this).cancelTag(this);//取消加载图片
}
2 SampleGridViewActivity
从网络获取图片
GridView的滑动监听
public class SampleScrollListener implements AbsListView.OnScrollListener {
private final Context context;
public SampleScrollListener(Context context) {
this.context = context;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
final Picasso picasso = Picasso.with(context);
if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_TOUCH_SCROLL) {
picasso.resumeTag(context);// 将Pause队列中的Action重新submit提交执行
} else {
picasso.pauseTag(context); //将Action从hunter中暂时去除,加入Pause队列中
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
}
}
adapter
final class SquaredImageView extends ImageView {
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
}
}
final class SampleGridViewAdapter extends BaseAdapter {
private final Context context;
private final List<String> urls = new ArrayList<String>();
public SampleGridViewAdapter(Context context) {
this.context = context;
// Ensure we get a different ordering of images on each run.
Collections.addAll(urls, Data.URLS);
//Moves every element of the list to a random new position in the list.
Collections.shuffle(urls);
// Triple up the list. 3倍
ArrayList<String> copy = new ArrayList<String>(urls);
urls.addAll(copy);
urls.addAll(copy);
}
@Override public View getView(int position, View convertView, ViewGroup parent) {
SquaredImageView view = (SquaredImageView) convertView;
if (view == null) {
view = new SquaredImageView(context);
view.setScaleType(CENTER_CROP);
}
// Get the image URL for the current position.
String url = getItem(position);
// Trigger the download of the URL asynchronously into the image view.
Picasso.with(context) //Picasso
.load(url) // RequestCreator
.placeholder(R.drawable.placeholder) //RequestCreator
.error(R.drawable.error) //RequestCreator
.fit() //RequestCreator
.tag(context) //RequestCreator
.into(view);
return view;
/**流程简述:配置了fit(),defered说明需要延时设定view的宽高,比如第一次new的时候,宽高不确定,
利用picasso.defer(target, new DeferredRequestCreator(this, target, callback));添加;
之后DeferredRequestCreator检测到onPreDraw时宽高已确定了,调用
creator.unfit().resize(width, height).into(target, callback);注意unfit();
先看内存中是否有,没有的话就提交一个Action给Dispatch,Dispatch会判断是否已经存在对应key的Hunter了,
有的话就将此Action添加到此Hunter中,等待之前的Run获取图片后回调;
否则就新建一个Hunter,新建过程中会检测出适合的RequestHandler,新建之后service.submit(hunter);
获取成功后,将保留Action的列表中删除这个Action
*/
}
}
3 SampleGalleryActivity
从文件中选择图片
<ViewAnimator
android:id="@+id/animator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffc0c0c0"
android:animateFirstView="false"
>
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ProgressBar
android:id="@+id/progress" />
</ViewAnimator>
public class SampleGalleryActivity extends PicassoSampleActivity {
private static final int GALLERY_REQUEST = 9391;
private static final String KEY_IMAGE = "com.example.picasso:image";
private ImageView imageView;
private ViewAnimator animator;//控制子view的显示与隐藏,一次显示一个view
private String image;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_gallery_activity);
animator = (ViewAnimator) findViewById(R.id.animator);
imageView = (ImageView) findViewById(R.id.image);
findViewById(R.id.go).setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {// "android.intent.action.PICK", content://
Intent gallery = new Intent(ACTION_PICK, EXTERNAL_CONTENT_URI);
startActivityForResult(gallery, GALLERY_REQUEST);
}
});
if (savedInstanceState != null) {
image = savedInstanceState.getString(KEY_IMAGE);
if (image != null) {
loadImage();
}
}
}
@Override protected void onPause() {
super.onPause();
if (isFinishing()) {//Check to see whether this activity is in the process of finishing
// Always cancel the request here, this is safe to call even if the image has been loaded.
// This ensures that the anonymous callback we have does not prevent the activity from
// being garbage collected. It also prevents our callback from getting invoked even after
// the activity has finished. !!!!
Picasso.with(this).cancelRequest(imageView);
}
}
@Override protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_IMAGE, image);
}
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == GALLERY_REQUEST && resultCode == RESULT_OK && data != null) {
image = data.getData().toString();
loadImage();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void loadImage() {
// Index 1 is the progress bar. Show it while we're loading the image.
animator.setDisplayedChild(1);
Picasso.with(this).load(image).into(imageView, new EmptyCallback() {
@Override public void onSuccess() {
// Index 0 is the image view.
animator.setDisplayedChild(0);
}
});
}
}
4 SampleContactsActivity
Contacts 联系人
class SampleContactsAdapter extends CursorAdapter {
private final LayoutInflater inflater;
public SampleContactsAdapter(Context context) {
super(context, null, 0);
inflater = LayoutInflater.from(context);
}
@Override public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View itemLayout = inflater.inflate(R.layout.sample_contacts_activity_item, viewGroup, false);
ViewHolder holder = new ViewHolder();
holder.text1 = (TextView) itemLayout.findViewById(android.R.id.text1);
holder.icon = (QuickContactBadge) itemLayout.findViewById(android.R.id.icon);
itemLayout.setTag(holder);
return itemLayout;
}
@Override public void bindView(View view, Context context, Cursor cursor) {
Uri contactUri = Contacts.getLookupUri(cursor.getLong(ContactsQuery.ID),
cursor.getString(ContactsQuery.LOOKUP_KEY));
// content://com.android.contacts/contacts/lookup/3176r1-5A6158E16D57/1
ViewHolder holder = (ViewHolder) view.getTag();
holder.text1.setText(cursor.getString(ContactsQuery.DISPLAY_NAME));
holder.icon.assignContactUri(contactUri);//点击后弹出联系人详情
Picasso.with(context)
.load(contactUri)
.placeholder(R.drawable.contact_picture_placeholder)
.tag(context)
.into(holder.icon);
}
@Override public int getCount() {
return getCursor() == null ? 0 : super.getCount();
}
private static class ViewHolder {
TextView text1;
QuickContactBadge icon;
}
}
public class SampleContactsActivity extends PicassoSampleActivity
implements LoaderManager.LoaderCallbacks<Cursor> {
private static final boolean IS_HONEYCOMB =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
private SampleContactsAdapter adapter;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_contacts_activity);
adapter = new SampleContactsAdapter(this);
ListView lv = (ListView) findViewById(android.R.id.list);
lv.setAdapter(adapter);
lv.setOnScrollListener(new SampleScrollListener(this));
getSupportLoaderManager().initLoader(ContactsQuery.QUERY_ID, null, this);
}
@Override public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == ContactsQuery.QUERY_ID) {
return new CursorLoader(this, //
ContactsQuery.CONTENT_URI, //
ContactsQuery.PROJECTION, //
ContactsQuery.SELECTION, //
null, //
ContactsQuery.SORT_ORDER);
}
return null;
}
@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);//Swap in a new Cursor, returning the old Cursor.
}
@Override public void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
interface ContactsQuery {
int QUERY_ID = 1;
Uri CONTENT_URI = Contacts.CONTENT_URI;
String SELECTION = (IS_HONEYCOMB ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME)
+ "<>''"
+ " AND "
+ Contacts.IN_VISIBLE_GROUP
+ "=1";
String SORT_ORDER = IS_HONEYCOMB ? Contacts.SORT_KEY_PRIMARY : Contacts.DISPLAY_NAME;
String[] PROJECTION = {
Contacts._ID, //
Contacts.LOOKUP_KEY, //
IS_HONEYCOMB ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME, //
IS_HONEYCOMB ? Contacts.PHOTO_THUMBNAIL_URI : Contacts._ID, //
SORT_ORDER
};
int ID = 0;
int LOOKUP_KEY = 1;
int DISPLAY_NAME = 2;
}
}
5 SampleListDetailActivity
final class SampleListDetailAdapter extends BaseAdapter {
public SampleListDetailAdapter(Context context) {
this.context = context;
Collections.addAll(urls, Data.URLS);
}
@Override public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
// Get the image URL for the current position.
String url = getItem(position);
holder.text.setText(url);
// Trigger the download of the URL asynchronously into the image view.
Picasso.with(context)
.load(url)
.placeholder(R.drawable.placeholder)
.error(R.drawable.error)
.resizeDimen(R.dimen.list_detail_image_size, R.dimen.list_detail_image_size)//设置大小
.centerInside()
.tag(context)
.into(holder.image);
return view;
}
}
public class SampleListDetailActivity extends PicassoSampleActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.sample_content, ListFragment.newInstance())
.commit();
}
}
void showDetails(String url) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.sample_content, DetailFragment.newInstance(url))
.addToBackStack(null) //保留,返回键可恢复
.commit();
}
public static class ListFragment extends Fragment {
public static ListFragment newInstance() {
return new ListFragment();
}
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final SampleListDetailActivity activity = (SampleListDetailActivity) getActivity();
final SampleListDetailAdapter adapter = new SampleListDetailAdapter(activity);
ListView listView = (ListView) LayoutInflater.from(activity)
.inflate(R.layout.sample_list_detail_list, container, false);
listView.setAdapter(adapter);
listView.setOnScrollListener(new SampleScrollListener(activity));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String url = adapter.getItem(position);
activity.showDetails(url);
}
});
return listView;
}
}
public static class DetailFragment extends Fragment {
private static final String KEY_URL = "picasso:url";
public static DetailFragment newInstance(String url) {
Bundle arguments = new Bundle();
arguments.putString(KEY_URL, url);
DetailFragment fragment = new DetailFragment();
fragment.setArguments(arguments);
return fragment;
}
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Activity activity = getActivity();
View view = LayoutInflater.from(activity)
.inflate(R.layout.sample_list_detail_detail, container, false);
TextView urlView = (TextView) view.findViewById(R.id.url);
ImageView imageView = (ImageView) view.findViewById(R.id.photo);
Bundle arguments = getArguments();
String url = arguments.getString(KEY_URL);
urlView.setText(url);
Picasso.with(activity)
.load(url)
.fit() // 参数defered延期
.tag(activity)
.into(imageView);
return view;
}
}
}
6 NOTIFICATION
SHOW_NOTIFICATION("Sample Notification", null) {//开头那个
@Override public void launch(Activity activity) {
RemoteViews remoteViews =
new RemoteViews(activity.getPackageName(), R.layout.notification_view);
Intent intent = new Intent(activity, SampleGridViewActivity.class);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(activity).setSmallIcon(R.drawable.icon)
.setContentIntent(PendingIntent.getActivity(activity, -1, intent, 0))
.setContent(remoteViews);
Notification notification = builder.getNotification();
// Bug in NotificationCompat that does not set the content. !!!
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
notification.contentView = remoteViews;
}
NotificationManager notificationManager =
(NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notification);
// Now load an image for this notification.
Picasso.with(activity) //
.load(Data.URLS[new Random().nextInt(Data.URLS.length)]) //
.resizeDimen(R.dimen.notification_icon_width_height,
R.dimen.notification_icon_width_height) //
.into(remoteViews, R.id.photo, NOTIFICATION_ID, notification);
}
};
// 获取图片成功后
remoteViews.setImageViewBitmap(viewId, result);
NotificationManager manager = getService(picasso.context, NOTIFICATION_SERVICE);
manager.notify(notificationTag, notificationId, notification);//notificationTag=null
7 其它
自定义的图片转换
public class GrayscaleTransformation implements Transformation {
private final Picasso picasso;
public GrayscaleTransformation(Picasso picasso) {
this.picasso = picasso;
}
@Override public Bitmap transform(Bitmap source) {
Bitmap result = createBitmap(source.getWidth(), source.getHeight(), source.getConfig());
Bitmap noise;
try {
noise = picasso.load(R.drawable.noise).get();
} catch (IOException e) {
throw new RuntimeException("Failed to apply transformation! Missing resource.");
}
BitmapShader shader = new BitmapShader(noise, REPEAT, REPEAT);
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix);
Paint paint = new Paint(ANTI_ALIAS_FLAG);
paint.setColorFilter(filter);
Canvas canvas = new Canvas(result);
canvas.drawBitmap(source, 0, 0, paint);
paint.setColorFilter(null);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
source.recycle();
noise.recycle();
return result;
}
@Override public String key() { return "grayscaleTransformation()"; }
}
Manifinest.xml
<receiver android:name="SampleWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/sample_widget_info"/>
</receiver>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="@dimen/widget_min_width"
android:minHeight="@dimen/widget_min_height"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/sample_widget"---ImageView
android:widgetCategory="home_screen"
/>
// extends BroadcastReceiver
public class SampleWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(final Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.sample_widget);
// Load image for all appWidgetIds.
Picasso picasso = Picasso.with(context);
picasso.load(Data.URLS[new Random().nextInt(Data.URLS.length)]) //
.placeholder(R.drawable.placeholder) //
.error(R.drawable.error) //
.transform(new GrayscaleTransformation(picasso)) //
.into(updateViews, R.id.image, appWidgetIds);
}
}