public class TransientUsersAdapter extends ArrayAdapter<TransientUser> {

	private LayoutInflater mInflater;

	public TransientUsersAdapter(Context context, List<TransientUser> users) {
		super(context, R.layout.friend_row, users);
		mInflater = LayoutInflater.from(context);
	}

	private static class ViewHolder {
		ImageView avatar;
		TextView screenName;
		// TextView remark;
		ImageView verified;
		ImageView toggleFollowing;
		TextView location;
		TextView description;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		View view = convertView;
		ViewHolder holder;
		if (view == null) {
			view = mInflater.inflate(R.layout.friend_row, null);
			holder = new ViewHolder();
			holder.avatar = (ImageView) view.findViewById(R.id.avatar);
			holder.screenName = (TextView) view.findViewById(R.id.nick);
			holder.verified = (ImageView) view.findViewById(R.id.verified);
			holder.toggleFollowing = (ImageView) view.findViewById(R.id.toggle_following);
			holder.location = (TextView) view.findViewById(R.id.location);
			holder.description = (TextView) view.findViewById(R.id.description);
			view.setTag(holder);
		} else {
			holder = (ViewHolder) view.getTag();
		}

		TransientUser user = getItem(position);
		Picasso.with(getContext())
				.load(user.avatarUrl)
				.placeholder(R.drawable.error)
				.error(R.drawable.error)
				.into(holder.avatar);
		holder.screenName.setText(user.screenName);
		// 是否加V
		if (user.verified) {
			holder.verified.setVisibility(View.VISIBLE);
		} else {
			holder.verified.setVisibility(View.GONE);
		}
		holder.location.setText(user.location);
		holder.description.setText(TextUtils.isEmpty(user.description)
				? getContext().getText(R.string.no_description) : user.description);
		return view;
	}
} public class FriendshipsAPI {

	private static final String BASE_URI = CatnutAPI.API_DOMAIN + "/2/friendships/";

	/**
	 * 获取用户的关注列表
	 *
	 * @param uid         需要查询的用户UID
	 * @param count       单页返回的记录条数，默认为50，最大不超过200
	 * @param cursor      返回结果的游标，下一页用返回值里的next_cursor，上一页用previous_cursor，默认为0。
	 * @param trim_status 返回值中user字段中的status字段开关，0：返回完整status字段、1：status字段仅返回status_id，默认为1
	 * @return 用户的关注列表api
	 */
	public static CatnutAPI friends(long uid, int count, int cursor, int trim_status) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("friends.json")
				.append("?uid=").append(uid)
				.append("&count=").append(CatnutUtils.optValue(count, 50))
				.append("&cursor=").append(CatnutUtils.optValue(cursor, 0))
				.append("&trim_status=").append(CatnutUtils.optValue(trim_status, 1));
		return new CatnutAPI(Request.Method.GET, uri, true, null);
	}

	/**
	 * 获取用户的关注列表
	 *
	 * @param screen_name 需要查询的用户昵称。
	 * @param count       单页返回的记录条数，默认为50，最大不超过200
	 * @param cursor      返回结果的游标，下一页用返回值里的next_cursor，上一页用previous_cursor，默认为0。
	 * @param trim_status 返回值中user字段中的status字段开关，0：返回完整status字段、1：status字段仅返回status_id，默认为1
	 * @return 用户的关注列表api
	 */
	public static CatnutAPI friends(String screen_name, int count, int cursor, int trim_status) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("friends.json")
				.append("?screen_name=").append(CatnutAPI.encode(screen_name))
				.append("&count=").append(CatnutUtils.optValue(count, 50))
				.append("&cursor=").append(CatnutUtils.optValue(cursor, 0))
				.append("&trim_status=").append(CatnutUtils.optValue(trim_status, 1));
		return new CatnutAPI(Request.Method.GET, uri, true, null);
	}

	/**
	 * 获取用户的粉丝列表
	 *
	 * @param uid         需要查询的用户UID
	 * @param count       单页返回的记录条数，默认为50，最大不超过200
	 * @param cursor      返回结果的游标，下一页用返回值里的next_cursor，上一页用previous_cursor，默认为0
	 * @param trim_status 返回值中user字段中的status字段开关，0：返回完整status字段、1：status字段仅返回status_id，默认为1
	 * @return api
	 */
	public static CatnutAPI followers(long uid, int count, int cursor, int trim_status) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("followers.json")
				.append("?uid=").append(uid)
				.append("&count=").append(CatnutUtils.optValue(count, 50))
				.append("&cursor=").append(CatnutUtils.optValue(cursor, 0))
				.append("&trim_status=").append(CatnutUtils.optValue(trim_status, 1));
		return new CatnutAPI(Request.Method.GET, uri, true, null);
	}

	/**
	 * 获取用户的粉丝列表
	 *
	 * @param screen_name 需要查询的用户昵称。
	 * @param count       单页返回的记录条数，默认为50，最大不超过200
	 * @param cursor      返回结果的游标，下一页用返回值里的next_cursor，上一页用previous_cursor，默认为0
	 * @param trim_status 返回值中user字段中的status字段开关，0：返回完整status字段、1：status字段仅返回status_id，默认为1
	 * @return api
	 */
	public static CatnutAPI followers(String screen_name, int count, int cursor, int trim_status) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("followers.json")
				.append("?screen_name=").append(CatnutAPI.encode(screen_name))
				.append("&count=").append(CatnutUtils.optValue(count, 50))
				.append("&cursor=").append(CatnutUtils.optValue(cursor, 0))
				.append("&trim_status=").append(CatnutUtils.optValue(trim_status, 1));
		return new CatnutAPI(Request.Method.GET, uri, true, null);
	}

	/**
	 * 关注一个用户
	 *
	 * @param screen_name 需要关注的用户昵称
	 * @param rip         开发者上报的操作用户真实IP，形如：211.156.0.1
	 * @return api
	 */
	public static CatnutAPI create(String screen_name, String rip) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("create.json")
				.append("?screen_name=").append(CatnutAPI.encode(screen_name))
				.append("&rip=").append(rip);
		return new CatnutAPI(Request.Method.POST, uri, true, null);
	}

	/**
	 * 取消关注一个用户
	 *
	 * @param screen_name 需要取消关注的用户昵称
	 * @return api
	 */
	public static CatnutAPI destroy(String screen_name) {
		StringBuilder uri = new StringBuilder(BASE_URI);
		uri.append("destroy.json")
				.append("?screen_name=").append(CatnutAPI.encode(screen_name));
		return new CatnutAPI(Request.Method.POST, uri, true, null);
	}
} public class UserAPI {

	private static final String BASE_URI = CatnutAPI.API_DOMAIN + "/2/users/";

	/**
	 * 根据用户ID获取用户信息
	 *
	 * @param uid 需要查询的用户ID
	 * @return api
	 */
	public static CatnutAPI profile(long uid) {
		return new CatnutAPI(
			Request.Method.GET,
			BASE_URI + "show.json?uid=" + uid,
			true,
			null
		);
	}

	/**
	 * 根据用户名获取用户信息
	 *
	 * @param screen_name 需要查询的用户昵称
	 * @return api
	 */
	public static CatnutAPI profile(String screen_name) {
		return new CatnutAPI(
				Request.Method.GET,
				BASE_URI + "show.json?screen_name=" + CatnutAPI.encode(screen_name),
				true,
				null
		);
	}
} public class CatnutAPI {

	/** 新浪微博api域名（注意貌似有些api不是用这个的=.=） */
	public static final String API_DOMAIN = "https://api.weibo.com";

	/** {@link com.android.volley.Request.Method} */
	public final int method;
	/** http uri */
	public final String uri;
	/** 是否需要传递access token */
	public final boolean authRequired;
	/** 是否需要在map里设置请求参数，通常用于post和put方法，没有赋null即可 */
	public final Map<String, String> params;

	public CatnutAPI(int method, CharSequence uri, boolean authRequired, Map<String, String> params) {
		this.method = method;
		this.uri = uri.toString();
		this.authRequired = authRequired;
		this.params = params;
	}

	public static String encode(String string) {
		String encode = null;
		try {
			encode = URLEncoder.encode(string, "utf-8");
		} catch (UnsupportedEncodingException e) {
		}
		return encode;
	}
} public class CatnutApp extends Application {

	public static final String TAG = "CatnutApp";

	/** singleton */
	private static CatnutApp sApp;
	/** http request header for weibo' s access token */
	private static Map<String, String> sAuthHeaders;

	private RequestQueue mRequestQueue;
	private SharedPreferences mPreferences;
	private AccessToken mAccessToken;

	@Override
	public void onCreate() {
		super.onCreate();
		sApp = this;
		mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
		mRequestQueue = Volley.newRequestQueue(this, new OkHttpStack());
		checkAccessToken();
	}

	/** 检查用户是否已经成功授权，并且授权是否过期 */
	private void checkAccessToken() {
		if (mAccessToken == null) {
			mAccessToken = getAccessToken();
		}

		if (mAccessToken != null) {
			long now = System.currentTimeMillis();
			if (now > mAccessToken.expires_in) {
				// 保存用户的uid信息意义不大，因为只是清掉了uid，他所保存的其它信息依然存在
				Log.d(TAG, "用户授权已过期，清除授权信息...");
				invalidateAccessToken();
			} else {
				Log.d(TAG, "授权将在" + DateUtils.getRelativeTimeSpanString(mAccessToken.expires_in) + "过期！");
			}
		}
	}

	public SharedPreferences getPreferences() {
		return mPreferences;
	}

	/**
	 * 保存授权用户的授权信息
	 *
	 * @param json 包含access token的json对象
	 */
	public void saveAccessToken(JSONObject json) {
		Log.d(TAG, "save access token...");
		mPreferences.edit()
				.putLong(UID, json.optLong(UID))
				.putString(ACCESS_TOKEN, json.optString(ACCESS_TOKEN))
				.putLong(EXPIRES_IN, json.optLong(EXPIRES_IN) * 1000 + System.currentTimeMillis())
				.commit();
	}

	/**
	 * 注销用户
	 */
	public void invalidateAccessToken() {
		Log.d(TAG, "invalidate access token...");
		mPreferences.edit()
				.remove(UID)
				.remove(EXPIRES_IN)
				.remove(ACCESS_TOKEN)
				.commit();
		mAccessToken = null;
		if (sAuthHeaders != null) {
			sAuthHeaders.clear();
		}
		sAuthHeaders = null;
	}

	/**
	 * 获取已经成功授权用户的信息
	 *
	 * @return AccessToken
	 */
	public AccessToken getAccessToken() {
		if (mAccessToken == null) {
			long uid = mPreferences.getLong(UID, 0L);
			long expiresIn = mPreferences.getLong(EXPIRES_IN, 0L);
			String accessToken = mPreferences.getString(ACCESS_TOKEN, null);
			// 当且仅当三项都完备的时候我们才会初始化授权信息对象！
			if (uid != 0L && expiresIn != 0L && accessToken != null) {
				mAccessToken = new AccessToken(uid, expiresIn, accessToken);
			}
		}
		return mAccessToken;
	}

	/**
	 * 获取异步http请求队列对象
	 *
	 * @return RequestQueue
	 */
	public RequestQueue getRequestQueue() {
		return mRequestQueue;
	}

	/**
	 * 获得boolean偏好
	 *
	 * @param key
	 * @param defaultValue
	 * @return a boolean with default res bool value
	 */
	public static boolean getBoolean(int key, int defaultValue) {
		SharedPreferences pref = sApp.getPreferences();
		Resources res = sApp.getResources();
		return sApp.getPreferences().getBoolean(sApp.getString(key), res.getBoolean(defaultValue));
	}

	/**
	 * 直接以静态方法获取应用程序对象
	 *
	 * @return CatnutApp
	 */
	public static CatnutApp getTingtingApp() {
		return sApp;
	}

	/**
	 * 获得在请求头部设置access token的map，避免每次都在uri中append上access token
	 * <p/>
	 * 没有授权则返回空的map
	 *
	 * @return headers with access token
	 */
	public static Map<String, String> getAuthHeaders() {
		// 如果没有授权，返回null
		AccessToken accessToken = sApp.getAccessToken();
		if (accessToken == null) {
			return Collections.emptyMap();
		}

		if (sAuthHeaders == null) {
			sAuthHeaders = new HashMap<String, String>(1);
			sAuthHeaders.put("Authorization", "OAuth2 " + accessToken.access_token);
		}
		return sAuthHeaders;
	}
} public class CatnutRequest extends Request<JSONObject> {

	public static final String TAG = "CatnutRequest";

	protected Context mContext;
	protected CatnutAPI mApi;
	protected CatnutProcessor<JSONObject> mProcessor;
	protected Response.Listener<JSONObject> mListener;

	public CatnutRequest(
		Context context,
		CatnutAPI api,
		CatnutProcessor<JSONObject> processor,
		Response.Listener<JSONObject> listener,
		Response.ErrorListener errorListener) {
		super(api.method, api.uri, errorListener);
		mContext = context;
		mApi = api;
		mProcessor = processor;
		mListener = listener;
	}

	@Override
	protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
		try {
			String jsonString =
					new String(response.data, HttpHeaderParser.parseCharset(response.headers));
			Response<JSONObject> success = Response.success(new JSONObject(jsonString),
					HttpHeaderParser.parseCacheHeaders(response));
			if (mProcessor != null) {
				// do in background...
				mProcessor.asyncProcess(mContext, success.result);
			}
			return success;
		} catch (UnsupportedEncodingException e) {
			return Response.error(new ParseError(e));
		} catch (JSONException je) {
			return Response.error(new ParseError(je));
		} catch (Exception ex) {
			Log.e(TAG, "process error!", ex);
			return Response.error(new VolleyError(response));
		}
	}

	@Override
	protected void deliverResponse(JSONObject response) {
		if (mListener == null) {
			Log.d(TAG, "finish http request without response on main-thread!");
		} else {
			mListener.onResponse(response);
		}
	}

	@Override
	public Map<String, String> getHeaders() throws AuthFailureError {
		return mApi.authRequired ? CatnutApp.getAuthHeaders() : super.getHeaders();
	}

	@Override
	protected Map<String, String> getParams() throws AuthFailureError {
		return mApi.params == null ? super.getParams() : mApi.params;
	}
} public final class User implements CatnutMetadata<JSONObject, ContentValues> {

	private static final String TAG = "User";

	public static final String TABLE = "users";
	public static final String SINGLE = "user";
	public static final String MULTIPLE = "users";

	public static final String next_cursor = "next_cursor";
	public static final String total_number = "total_number";

	// singleton
	public static final User METADATA = new User();

	private User() {
	}

	/** 昵称 */
	public static final String screen_name = "screen_name";
	/** 友好显示名称 */
	public static final String name = "name";
	/** 所在省级ID int */
	public static final String province = "province";
	/** 所在省级ID int */
	public static final String city = "city";
	/** 所在地 */
	public static final String location = "location";
	/** 个人描述 */
	public static final String description = "description";
	/** 博客地址 */
	public static final String url = "url";
	/** 头像地址（中图），50×50像素 */
	public static final String profile_image_url = "profile_image_url";
	/** 主页封面图片（桌面） */
	public static final String cover_image = "cover_image";
	/** 主页封面图片（手机） */
	public static final String cover_image_phone = "cover_image_phone";
	/** 微博统一URL地址 */
	public static final String profile_url = "profile_url";
	/** 个性化域名 */
	public static final String domain = "domain";
	/** 微号 */
	public static final String weihao = "weihao";
	/** 性别，m：男、f：女、n：未知 */
	public static final String gender = "gender";
	/** 粉丝数 int */
	public static final String followers_count = "followers_count";
	/** 关注数 int */
	public static final String friends_count = "friends_count";
	/** 微博数 int */
	public static final String statuses_count = "statuses_count";
	/** 收藏数 int */
	public static final String favourites_count = "favourites_count";
	/** 用户创建（注册）时间 */
	public static final String created_at = "created_at";
	/** 暂未支持 boolean */
	public static final String following = "following";
	/** 是否允许所有人给我发私信 boolean */
	public static final String allow_all_act_msg = "allow_all_act_msg";
	/** 是否允许标识用户的地理位置 boolean */
	public static final String geo_enabled = "geo_enabled";
	/** 是否是微博认证用户，即加V用户 boolean */
	public static final String verified = "verified";
	/** 暂未支持 int */
	public static final String verified_type = "verified_type";
	/** 用户备注信息，只有在查询用户关系时才返回此字段 */
	public static final String remark = "remark";
	/** int */
//	public static final String ptype = "ptype";
	/** 是否允许所有人对我的微博进行评论 boolean */
	public static final String allow_all_comment = "allow_all_comment";
	/** 头像地址（大图），180×180像素 */
	public static final String avatar_large = "avatar_large";
	/** 头像地址（高清），高清头像原图 */
	public static final String avatar_hd = "avatar_hd";
	/** 认证原因 */
	public static final String verified_reason = "verified_reason";
	/** 是否关注当前登录用户 boolean */
	public static final String follow_me = "follow_me";
	/** 在线状态，0：不在线、1：在线 int */
	public static final String online_status = "online_status";
	/** 互粉数 int */
	public static final String bi_followers_count = "bi_followers_count";
	/** 当前的语言版本，zh-cn：简体中文，zh-tw：繁体中文，en：英语 */
	public static final String lang = "lang";
	/** int */
//	public static final String star = "star";
	/** int */
//	public static final String mbtype = "mbtype";
	/** int */
//	public static final String mbrank = "mbrank";
	/** int */
//	public static final String block_word = "block_word";
	/** 关联最新的一条微博 long */
	public static final String status_id = "status_id";

	@Override
	public String ddl() {
		Log.i(TAG, "create table [users]...");

		StringBuilder ddl = new StringBuilder("CREATE TABLE ");
		ddl.append(TABLE).append("(")
				.append(BaseColumns._ID).append(" int primary key,")
				.append(screen_name).append(" text,")
				.append(name).append(" text,")
				.append(province).append(" int,")
				.append(city).append(" int,")
				.append(location).append(" text,")
				.append(description).append(" text,")
				.append(url).append(" text,")
				.append(profile_image_url).append(" text,")
				.append(cover_image).append(" text,")
				.append(cover_image_phone).append(" text,")
				.append(profile_url).append(" text,")
				.append(domain).append(" text,")
				.append(weihao).append(" text,")
				.append(gender).append(" text,")
				.append(followers_count).append(" int,")
				.append(friends_count).append(" int,")
				.append(statuses_count).append(" int,")
				.append(favourites_count).append(" int,")
				.append(created_at).append(" text,")
				.append(following).append(" int,")
				.append(allow_all_act_msg).append(" int,")
				.append(geo_enabled).append(" int,")
				.append(verified).append(" int,")
				.append(verified_type).append(" int,")
				.append(remark).append(" int,")
//				.append(ptype).append(" int,")
				.append(allow_all_comment).append(" int,")
				.append(avatar_large).append(" text,")
				.append(avatar_hd).append(" text,")
				.append(verified_reason).append(" text,")
				.append(follow_me).append(" int,")
				.append(online_status).append(" int,")
				.append(bi_followers_count).append(" int,")
				.append(lang).append(" text"); // 注意最后一个字段！
//				.append(star).append(" int,")
//				.append(mbtype).append(" int,")
//				.append(mbrank).append(" int,")
//				.append(block_word).append(" int");
		return ddl.append(")").toString();
	}

	@Override
	public ContentValues convert(JSONObject json) {
		ContentValues user = new ContentValues();
		user.put(BaseColumns._ID, json.optLong(Constants.ID));
		user.put(screen_name, json.optString(screen_name));
		user.put(name, json.optString(name));
		user.put(province, json.optInt(province));
		user.put(city, json.optInt(city));
		user.put(location, json.optString(location));
		user.put(description, json.optString(description));
		user.put(url, json.optString(url));
		user.put(profile_image_url, json.optString(profile_image_url));
		user.put(cover_image, json.optString(cover_image));
		user.put(cover_image_phone, json.optString(cover_image_phone));
		user.put(profile_url, json.optString(profile_url));
		user.put(domain, json.optString(domain));
		user.put(weihao, json.optString(weihao));
		user.put(gender, json.optString(gender));
		user.put(followers_count, json.optInt(followers_count));
		user.put(friends_count, json.optInt(friends_count));
		user.put(statuses_count, json.optInt(statuses_count));
		user.put(favourites_count, json.optInt(favourites_count));
		user.put(created_at, json.optString(created_at));
		user.put(following, json.optBoolean(following));
		user.put(allow_all_act_msg, json.optBoolean(allow_all_act_msg));
		user.put(geo_enabled, json.optBoolean(geo_enabled));
		user.put(verified, json.optBoolean(verified));
		user.put(verified_type, json.optInt(verified_type));
		user.put(remark, json.optString(remark));
//		user.put(ptype, json.optInt(ptype));
		user.put(allow_all_comment, json.optBoolean(allow_all_comment));
		user.put(avatar_large, json.optString(avatar_large));
		user.put(avatar_hd, json.optString(avatar_hd));
		user.put(verified_reason, json.optString(verified_reason));
		user.put(follow_me, json.optBoolean(follow_me));
		user.put(online_status, json.optInt(online_status));
		user.put(bi_followers_count, json.optInt(bi_followers_count));
		user.put(lang, json.optString(lang));
//		user.put(star, json.optString(star));
//		user.put(mbtype, json.optInt(mbtype));
//		user.put(mbrank, json.optInt(mbrank));
//		user.put(block_word, json.optInt(block_word));
		// 添加最新微博id
		if (json.has(SINGLE)) {
			user.put(status_id, json.optJSONObject(Status.SINGLE).optLong(Constants.ID));
		}
		return user;
	}
} public class WeiboAPIError implements Parcelable {

	private static final String TAG = "WeiboAPIError";

	public static final String ERROR_CODE = "error_code";
	public static final String REQUEST = "request";
	public static final String ERROR = "error";

	public final int error_code;
	public final String request;
	public final String error;

	public WeiboAPIError(int error_code, String request, String error) {
		this.error_code = error_code;
		this.request = request;
		this.error = error;
	}

	/**
	 * 从volley中解析错误
	 *
	 * @param volleyError
	 * @return WeiboAPIError
	 */
	public static WeiboAPIError fromVolleyError(VolleyError volleyError) {
		// very bad situation!
		if (volleyError.networkResponse == null) {
			String message = volleyError.getLocalizedMessage();
			return new WeiboAPIError(-1, null,
					TextUtils.isEmpty(message) ? volleyError.getClass().getName() : message);
		}
		// 很可能出现response body为空的情况
		String jsonString;
		try {
			jsonString = new String(volleyError.networkResponse.data);
		} catch (Exception e) {
			Log.wtf(TAG, e);
			// fall back to http error!
			String msg = volleyError.getLocalizedMessage();
			return new WeiboAPIError(
					volleyError.networkResponse.statusCode,
					null,
					TextUtils.isEmpty(msg) ? "Unknown error! Please try again later:-(" : msg);
		}

		try {
			JSONObject jsonObject = new JSONObject(jsonString);
			return new WeiboAPIError(
					jsonObject.optInt(ERROR_CODE),
					jsonObject.optString(REQUEST),
					jsonObject.optString(ERROR)
			);
		} catch (JSONException e) {
			Log.wtf(TAG, e.getLocalizedMessage());
		}
		// never happen！
		return null;
	}

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeInt(error_code);
		dest.writeString(request);
		dest.writeString(error);
	}

	public static final Parcelable.Creator<WeiboAPIError> CREATOR = new Creator<WeiboAPIError>() {
		@Override
		public WeiboAPIError createFromParcel(Parcel source) {
			return new WeiboAPIError(
				source.readInt(),
				source.readString(),
				source.readString()
			);
		}

		@Override
		public WeiboAPIError[] newArray(int size) {
			return new WeiboAPIError[size];
		}
	};
} public class UserProcessor {

	/**
	 * 将我的好友持久化到本地
	 */
	public static class UsersProcessor implements CatnutProcessor<JSONObject> {

		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			JSONArray jsonArray = data.optJSONArray(User.MULTIPLE);
			ContentValues[] users = new ContentValues[jsonArray.length()];
			// 可能会包含有这条用户最新的微博存在
			ContentValues[] tweets = null;
			boolean hasTweet = jsonArray.optJSONObject(0).has(Status.SINGLE);
			if (hasTweet) {
				tweets = new ContentValues[users.length];
			}

			for (int i = 0; i < users.length; i++) {
				JSONObject user = jsonArray.optJSONObject(i);
				users[i] = User.METADATA.convert(user);
				if (hasTweet) {
					tweets[i] = Status.METADATA.convert(user.optJSONObject(Status.SINGLE));
				}
			}

			context.getContentResolver().bulkInsert(CatnutProvider.parse(User.MULTIPLE), users);
			if (hasTweet) {
				context.getContentResolver().bulkInsert(CatnutProvider.parse(Status.MULTIPLE), tweets);
			}
		}
	}

	/**
	 * 持久化单个用户的信息
	 */
	public static class UserProfileProcessor implements CatnutProcessor<JSONObject> {

		@Override
		public void asyncProcess(Context context, JSONObject data) throws Exception {
			ContentValues user = User.METADATA.convert(data);
			// 如果包含了用户最新的一条微博
			if (data.has(Status.SINGLE)) {
				JSONObject json = data.getJSONObject(Status.SINGLE);
				ContentValues tweet = Status.METADATA.convert(json);
				// 注意，新浪此时返回的内联微博没有uid这项！
				tweet.put(Status.uid, data.optLong(Constants.ID));
				context.getContentResolver().insert(CatnutProvider.parse(Status.MULTIPLE), tweet);
			}
			context.getContentResolver().insert(CatnutProvider.parse(User.MULTIPLE), user);
		}
	}
} public abstract class TransientRequest<T> extends Request<T> {

	private CatnutAPI mApi;
	private Response.Listener<T> mListener;

	public TransientRequest(CatnutAPI api, Response.Listener<T> listener, Response.ErrorListener errorListener) {
		super(api.method, api.uri, errorListener);
		mListener = listener;
		mApi = api;
	}

	@Override
	public Map<String, String> getHeaders() throws AuthFailureError {
		return mApi.authRequired ? CatnutApp.getAuthHeaders() : super.getHeaders();
	}

	@Override
	protected void deliverResponse(T response) {
		if (mListener != null) {
			mListener.onResponse(response);
		}
	}
} public class TransientUser {

	public long id;
	public String screenName;
	public String location;
	public String description;
	public boolean verified;
	public String avatarUrl;

	public static TransientUser convert(JSONObject jsonObject) {
		TransientUser user = new TransientUser();
		user.id = jsonObject.optLong(Constants.ID);
		user.screenName = jsonObject.optString(User.screen_name);
		user.location = jsonObject.optString(User.location);
		user.description = jsonObject.optString(User.description);
		user.verified = jsonObject.optBoolean(User.verified);
		user.avatarUrl = jsonObject.optString(User.profile_image_url);
		return user;
	}
} public class SwipeRefreshListFragment extends ListFragment {
	private SwipeRefreshLayout mSwipeRefreshLayout;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
							 Bundle savedInstanceState) {

		// Create the list fragment's content view by calling the super method
		final View listFragmentView = super.onCreateView(inflater, container, savedInstanceState);

		// Now create a SwipeRefreshLayout to wrap the fragment's content view
		mSwipeRefreshLayout = new ListFragmentSwipeRefreshLayout(container.getContext());

		// Add the list fragment's content view to the SwipeRefreshLayout, making sure that it fills
		// the SwipeRefreshLayout
		mSwipeRefreshLayout.addView(listFragmentView,
				ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

		// Make sure that the SwipeRefreshLayout will fill the fragment
		mSwipeRefreshLayout.setLayoutParams(
				new ViewGroup.LayoutParams(
						ViewGroup.LayoutParams.MATCH_PARENT,
						ViewGroup.LayoutParams.MATCH_PARENT));

		// Now return the SwipeRefreshLayout as this fragment's content view
		return mSwipeRefreshLayout;
	}

	/**
	 * Set the {@link android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener} to listen for
	 * initiated refreshes.
	 *
	 * @see android.support.v4.widget.SwipeRefreshLayout#setOnRefreshListener(android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener)
	 */
	public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener listener) {
		mSwipeRefreshLayout.setOnRefreshListener(listener);
	}

	/**
	 * Returns whether the {@link android.support.v4.widget.SwipeRefreshLayout} is currently
	 * refreshing or not.
	 *
	 * @see android.support.v4.widget.SwipeRefreshLayout#isRefreshing()
	 */
	public boolean isRefreshing() {
		return mSwipeRefreshLayout.isRefreshing();
	}

	/**
	 * Set whether the {@link android.support.v4.widget.SwipeRefreshLayout} should be displaying
	 * that it is refreshing or not.
	 *
	 * @see android.support.v4.widget.SwipeRefreshLayout#setRefreshing(boolean)
	 */
	public void setRefreshing(boolean refreshing) {
		mSwipeRefreshLayout.setRefreshing(refreshing);
	}

	/**
	 * Set the color scheme for the {@link android.support.v4.widget.SwipeRefreshLayout}.
	 *
	 * @see android.support.v4.widget.SwipeRefreshLayout#setColorScheme(int, int, int, int)
	 */
	public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
		mSwipeRefreshLayout.setColorScheme(colorRes1, colorRes2, colorRes3, colorRes4);
	}

	/**
	 * @return the fragment's {@link android.support.v4.widget.SwipeRefreshLayout} widget.
	 */
	public SwipeRefreshLayout getSwipeRefreshLayout() {
		return mSwipeRefreshLayout;
	}

	/**
	 * Sub-class of {@link android.support.v4.widget.SwipeRefreshLayout} for use in this
	 * {@link android.support.v4.app.ListFragment}. The reason that this is needed is because
	 * {@link android.support.v4.widget.SwipeRefreshLayout} only supports a single child, which it
	 * expects to be the one which triggers refreshes. In our case the layout's child is the content
	 * view returned from
	 * {@link android.support.v4.app.ListFragment#onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle)}
	 * which is a {@link android.view.ViewGroup}.
	 *
	 * <p>To enable 'swipe-to-refresh' support via the {@link android.widget.ListView} we need to
	 * override the default behavior and properly signal when a gesture is possible. This is done by
	 * overriding {@link #canChildScrollUp()}.
	 */
	private class ListFragmentSwipeRefreshLayout extends SwipeRefreshLayout {

		public ListFragmentSwipeRefreshLayout(Context context) {
			super(context);
		}

		/**
		 * As mentioned above, we need to override this method to properly signal when a
		 * 'swipe-to-refresh' is possible.
		 *
		 * @return true if the {@link android.widget.ListView} is visible and can scroll up.
		 */
		@Override
		public boolean canChildScrollUp() {
			final ListView listView = getListView();
			if (listView.getVisibility() == View.VISIBLE) {
				return canListViewScrollUp(listView);
			} else {
				return false;
			}
		}

	}

	/**
	 * Utility method to check whether a {@link ListView} can scroll up from it's current position.
	 * Handles platform version differences, providing backwards compatible functionality where
	 * needed.
	 */
	private static boolean canListViewScrollUp(ListView listView) {
		if (android.os.Build.VERSION.SDK_INT >= 14) {
			// For ICS and above we can call canScrollVertically() to determine this
			return ViewCompat.canScrollVertically(listView, -1);
		} else {
			// Pre-ICS we need to manually check the first visible item and the child view's top
			// value
			return listView.getChildCount() > 0 &&
					(listView.getFirstVisiblePosition() > 0
							|| listView.getChildAt(0).getTop() < listView.getPaddingTop());
		}
	}
} public class ProfileActivity extends Activity implements FragmentManager.OnBackStackChangedListener {

	private CatnutApp mApp;
	private EasyTracker mTracker;

	private Handler mHandler = new Handler();

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mApp = CatnutApp.getTingtingApp();
		if (savedInstanceState == null) {
			final FragmentManager fragmentManager = getFragmentManager();
			fragmentManager.addOnBackStackChangedListener(this);
			Intent intent = getIntent();
			final long uid = intent.getLongExtra(Constants.ID, 0L);
			Uri uri = intent.getData();
			String screenName =
					uri == null ? intent.getStringExtra(User.screen_name) : uri.getLastPathSegment();
			fragmentManager
					.beginTransaction()
					.replace(android.R.id.content, ProfileFragment.getFragment(uid, screenName))
					.commit();
			if (mApp.getPreferences().getBoolean(getString(R.string.pref_enable_analytics), true)) {
				mTracker = EasyTracker.getInstance(this);
			}
		}
		ActionBar bar = getActionBar();
		bar.setIcon(R.drawable.ic_title_profile_default);
		bar.setDisplayHomeAsUpEnabled(true);
	}

	@Override
	protected void onStart() {
		super.onStart();
		if (mTracker != null) {
			mTracker.activityStart(this);
		}
	}

	@Override
	protected void onStop() {
		super.onStop();
		if (mTracker != null) {
			mTracker.activityStop(this);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(Menu.NONE, R.id.pref, Menu.NONE, R.string.pref)
				.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
			case android.R.id.home:
				this.navigateUpTo(getIntent());
				break;
			case R.id.pref:
				startActivity(SingleFragmentActivity.getIntent(this, SingleFragmentActivity.PREF));
				break;
			default:
				break;
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	public void onBackStackChanged() {
		invalidateOptionsMenu();
	}

	public void flipCard(Fragment fragment, String tag, boolean backStack) {
		FragmentTransaction fragmentTransaction = getFragmentManager()
				.beginTransaction();
		fragmentTransaction
				.setCustomAnimations(
						R.animator.card_flip_right_in, R.animator.card_flip_right_out,
						R.animator.card_flip_left_in, R.animator.card_flip_left_out)
				.replace(android.R.id.content, fragment, tag);
		if (backStack) {
			fragmentTransaction.addToBackStack(null);
		}
		fragmentTransaction.commit();
		mHandler.post(new Runnable() {
			@Override
			public void run() {
				invalidateOptionsMenu();
			}
		});
	}
}
import android.app.ActionBar;
import android.app.Activity;
import android.app.ListFragment;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.Toast;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;
import org.catnut.R;
import org.catnut.adapter.TransientUsersAdapter;
import org.catnut.api.FriendshipsAPI;
import org.catnut.api.UserAPI;
import org.catnut.core.CatnutAPI;
import org.catnut.core.CatnutApp;
import org.catnut.core.CatnutRequest;
import org.catnut.metadata.User;
import org.catnut.metadata.WeiboAPIError;
import org.catnut.processor.UserProcessor;
import org.catnut.support.TransientRequest;
import org.catnut.support.TransientUser;
import org.catnut.support.app.SwipeRefreshListFragment;
import org.catnut.ui.ProfileActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/*
 * The MIT License (MIT)
 * Copyright (c) 2014 longkai
 * The software shall be used for good, not evil.
 */
package org.catnut.fragment;



/**
 * 关注者列表界面
 *
 * @author longkai
 */
public class TransientUsersFragment extends SwipeRefreshListFragment implements AbsListView.OnScrollListener, SwipeRefreshLayout.OnRefreshListener {

	private static final String TAG = "TransientUsersFragment";

	private int mNext_cursor = 0;
	private int mTotal_number = 0;

	private RequestQueue mRequestQueue;

	private String mScreenName;
	private boolean mFollowing;

	private ArrayList<TransientUser> mUsers;
	private TransientUsersAdapter mAdapter;

	private int mLastTotalCount;

	private Response.Listener<List<TransientUser>> listener = new Response.Listener<List<TransientUser>>() {
		@Override
		public void onResponse(List<TransientUser> response) {
			mLastTotalCount = mAdapter.getCount();
			mUsers.addAll(response);
			mAdapter.notifyDataSetChanged();
			setRefreshing(false);
		}
	};

	private Response.ErrorListener errorListener = new Response.ErrorListener() {
		@Override
		public void onErrorResponse(VolleyError error) {
			Log.e(TAG, "load friends error!", error);
			WeiboAPIError weiboAPIError = WeiboAPIError.fromVolleyError(error);
			Toast.makeText(getActivity(), weiboAPIError.error, Toast.LENGTH_LONG).show();
			setRefreshing(false);
		}
	};

	public static TransientUsersFragment getFragment(String screenName, boolean following) {
		Bundle args = new Bundle();
		args.putString(User.screen_name, screenName);
		args.putBoolean(TAG, following);
		TransientUsersFragment transientUsersFragment = new TransientUsersFragment();
		transientUsersFragment.setArguments(args);
		return transientUsersFragment;
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		mRequestQueue = CatnutApp.getTingtingApp().getRequestQueue();
		Bundle args = getArguments();
		mScreenName = args.getString(User.screen_name);
		mFollowing = args.getBoolean(TAG);
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		mUsers = new ArrayList<TransientUser>();
		mAdapter = new TransientUsersAdapter(getActivity(), mUsers);
	}

	@Override
	public void onViewCreated(View view, Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		ViewGroup viewGroup = (ViewGroup) view;
		setOnRefreshListener(this);
		// go！开始加载数据吧！
		load(true);
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		getListView().setOnScrollListener(this);
		setEmptyText(getString(R.string.no_more));
		setListAdapter(mAdapter);
	}

	@Override
	public void onStart() {
		super.onStart();
		ActionBar bar = getActivity().getActionBar();
		bar.setIcon(R.drawable.ic_title_people);
		bar.setTitle(getString(
				mFollowing ? R.string.his_followings : R.string.his_followers, mScreenName)
		);
	}

	@Override
	public void onListItemClick(ListView l, View v, int position, long id) {
		final TransientUser user = mUsers.get(position);
		CatnutAPI api = UserAPI.profile(user.id);
		final ProgressDialog loading = ProgressDialog.show(getActivity(), null, getString(R.string.loading));
		loading.show();
		mRequestQueue.add(new CatnutRequest(
				getActivity(),
				api,
				new UserProcessor.UserProfileProcessor(),