新浪微博客户端开发详解(三)

上一篇博文中详细讲解了auth2.0的认证过程,本篇博文会详细讲解获取微博数据并显示出来的效果。

首先看效果图:

微博数据获取之后展示效果如图所示。

(一)

首先我们从新浪微博的开放平台说起。
身份认证之后,开发者会有三十组接口可以调用。

微博普通读取接口






上面所示的接口是三十组接口中的一组接口,可以说是一类接口,这些接口是获取微博数据的,读取接口。意思就是说获取到微博数据。

我在这里详细讲解一个接口,剩下的接口举一反三就可以的,道理是一样的。

点击第二个接口链接,进入到该接口的详细说明的网页,详细展示如下:

2/statuses/friends timeline

statuses/friends_timeline

获取当前登录用户及其所关注用户的最新微博  

//这里是该接口的作用的说明,该接口实现什么样的功能

URL

https://api.weibo.com/2/statuses/friends_timeline.json        

 //这里是该接口的具体的url,向服务器请求就是向服务器的该接口请求,实现获取当前登录用户及其所关注用户的最新微博的功能

支持格式

JSON 

//这里说明的是请求的参数的格式,使用的是JSON格式数据向服务器发 

//送参数,这里说明一下,新浪微博的开放接口统一使用JSON格式数 

//据,服务器返回的数据也是JSON格式的,需要对JSON数据进行解

 //析,不过JSON格式数据非常简单

HTTP请求方式

GET 

//这里指明了向服务器请求的方式,使用GET方式进行请求

是否需要登录

 //这里指明了该接口是需要用户登录之后才可以获取该接口提供的数据, 

//如果这里是“否”,说明该接口可以在用户没有登录的情况下实现该接 

//口提供的功能。例如:分享接口就不用用户登录就可以实现。
关于登录授权,参见 如何登录授权

访问授权限制

访问级别:普通接口 
频次限制: 
关于频次限制,参见 接口访问权限说明


//这里说明该接口的访问级别,一般的开发者身份认证成功之后会有三十

//组接口可以使用,不过是普通接口,还有一些高级接口一般开发者还不

//能使用。什么情况下可以使用呢?可以到我的应用->接口管理->申请权 //限里面可以申请高级接口。

//另外需要说明的是频次限制,这个是该接口的访问频率的限制次数,这  

//样的限制很明显就是为了安全嘛。不过不用担心,这个是开发阶段才有 

//这样的限制。

请求参数 

//请求参数明什么说的了,人家写的很详细

  必选 类型及范围 说明
source false string 采用OAuth授权方式不需要此参数,其他授权方式为必填参数,数值为应用的AppKey。
access_token false string 采用OAuth授权方式为必填参数,其他授权方式不需要此参数,OAuth授权后获得。
since_id false int64 若指定此参数,则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0。
max_id false int64 若指定此参数,则返回ID小于或等于max_id的微博,默认为0。
count false int 单页返回的记录条数,最大不超过100,默认为20。
page false int 返回结果的页码,默认为1。
base_app false int 是否只获取当前应用的数据。0为否(所有数据),1为是(仅当前应用),默认为0。
feature false int 过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。
trim_user false int 返回值中user字段开关,0:返回完整user字段、1:user字段仅返回user_id,默认为0。

注意事项

调用样例及调试工具

API测试工具

返回结果 

//返回结果使用的是JSON格式的数据

JSON示例

{ "statuses": [ { "created_at": "Tue May 31 17:46:55 +0800 2011", "id": 11488058246, "text": "求关注。", "source": "新浪微博", "favorited": false, "truncated": false, "in_reply_to_status_id": "", "in_reply_to_user_id": "", "in_reply_to_screen_name": "", "geo": null, "mid": "5612814510546515491", "reposts_count": 8, "comments_count": 9, "annotations": [], "user": { "id": 1404376560, "screen_name": "zaku", "name": "zaku", "province": "11", "city": "5", "location": "北京 朝阳区", "description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。", "url": "http://blog.sina.com.cn/zaku", "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1", "domain": "zaku", "gender": "m", "followers_count": 1204, "friends_count": 447, "statuses_count": 2908, "favourites_count": 0, "created_at": "Fri Aug 28 00:00:00 +0800 2009", "following": false, "allow_all_act_msg": false, "remark": "", "geo_enabled": true, "verified": false, "allow_all_comment": true, "avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1", "verified_reason": "", "follow_me": false, "online_status": 0, "bi_followers_count": 215 } }, ... ], "ad": [ { "id": 3366614911586452, "mark": "AB21321XDFJJK" }, ... ], "previous_cursor": 0, // 暂时不支持 "next_cursor": 11488013766, // 暂时不支持 "total_number": 81655 }

关于错误返回值与错误代码,参见 错误代码说明

返回字段说明

返回值字段 字段类型 字段说明
created_at string 微博创建时间
id int64 微博ID
mid int64 微博MID
idstr string 字符串型的微博ID
text string 微博信息内容
source string 微博来源
favorited boolean 是否已收藏,true:是,false:否
truncated boolean 是否被截断,true:是,false:否
in_reply_to_status_id string (暂未支持)回复ID
in_reply_to_user_id string (暂未支持)回复人UID
in_reply_to_screen_name string (暂未支持)回复人昵称
thumbnail_pic string 缩略图片地址,没有时不返回此字段
bmiddle_pic string 中等尺寸图片地址,没有时不返回此字段
original_pic string 原始图片地址,没有时不返回此字段
geo object 地理信息字段 详细
user object 微博作者的用户信息字段 详细
retweeted_status object 被转发的原微博信息字段,当该微博为转发微博时返回 详细
reposts_count int 转发数
comments_count int 评论数
attitudes_count int 表态数
mlevel int 暂未支持
visible object 微博的可见性及指定可见分组信息。该object中type取值,0:普通微博,1:私密微博,3:指定分组微博,4:密友微博;list_id为分组的组号
pic_ids object 微博配图ID。多图时返回多图ID,用来拼接图片url。用返回字段thumbnail_pic的地址配上该返回字段的图片ID,即可得到多个图片url。
ad object array 微博流内的推广微博ID

以上便是对于一个接口的详细说明了。看懂了一个接口,搞明白之后,其他的接口跟这个异曲同工,举一反三,就很容易看懂,没什么问题。真正带开发只用看参数和返回值就可以了。

(二)

下面我贴出该接口的实现类。

public class HomeAcitivity extends Activity
{
	private ListView listview ;
	private ProgressBar progressbar;
	private List<Status> statuses;//每条微博看作一个对象Status,获取的好友微博使用List保存
	private Intent intent;
	private int postionIndex;
	private Oauth2AccessToken mAccessToken;//这个类非常关键,三十组接口的调用均需要使用该类中所保存的Token和code值、expire值等
	private WeiboManager weibomanager;//这个是自动义的微博管理类,使用该类获取或者操作微博。就是说一切对于微博的操作均在微博管理类中实现,稍后会贴出该类的代码
	@SuppressLint("HandlerLeak")
	private Handler handler = new Handler()
//这是android中的消息传递机制,因为访问网络数据在最新的android sdk中是不允许在主线程中访问网络资源,必须另外开启线程,当另外的线程获取到资源之后,利用handler把数据返回到主线程,下面的message就是封装了利用handler传递到主线程的数据。
	{
		@SuppressWarnings({ "unchecked", "static-access" })
		public void handleMessage(Message msg)
		{
			switch (msg.what)
			{
			case 0x111:
				progressbar.setVisibility(View.GONE);
				listview.setVisibility(View.VISIBLE);
				statuses = (List<Status>)msg.obj;//获取成功之后,利用message封装数据,利用handler把数据传递到主线程
				msg.obj = statuses;		
			listview.setAdapter(new WeiboListAdapter(HomeAcitivity.this,statuses,R.id.publicStatus));
		handler.sendMessage(msg);}}.start();//初始状态下,加载新微博
		listview.setOnItemLongClickListener(new OnItemLongClickListener() 
			//对每条微博设置一个监听器,长按可以弹出一个菜单项,菜单项中包括刷新微博、查看单条微博、退出等。
		{	@Override
			public boolean onItemLongClick(AdapterView<?> arg0, View v, int postion, long id){
			boolean bl = listview.showContextMenu();
			postionIndex = postion;
			return true;
			}
		});
		listview.setOnCreateContextMenuListener(new OnCreateContextMenuListener(){
//这个是初始化菜单项,利用R。menu.menu实现,需要在res资源文件夹下的建立一个固定名字为
//menu的文件夹,该文件夹下有一个固定名字的menu.xml实现,如果各位看官不知道这个怎么回事,请另外搜索如何定义菜单项
		    @Override    
		    public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo info) {     
		    	MenuInflater inflator = getMenuInflater();
				inflator.inflate(R.menu.menu,menu);
		        menu.setHeaderTitle("菜单");
		    }
		});
		mAccessToken = AccessTokenKeeper.readAccessToken(HomeAcitivity.this);//这个是获取Token的工具类,在sinasdk-core.jar中已经实现好了,可以直接使用
	}
	@Override
	public boolean onContextItemSelected(MenuItem item)//这个方法就是对定义的菜单中的每一项可以实现的功能
	{
		switch (item.getItemId())
		{
		case R.id.refresh://刷新
		<span style="white-space:pre">	</span>new Thread()
			{
				@SuppressWarnings("static-access")
				@Override
				public void run()
				{
					List<Status> statuses1 = weibomanager.getHomeTimeline();
					Message msg = new Message();
					msg.obj = statuses1;
					msg.what = 0x110;
					handler.sendMessage(msg);
				}
			}.start();
			break;
		case R.id.detail://查看微博
			WeiboListAdapter weibolistadapter = new WeiboListAdapter(HomeAcitivity.this,statuses,R.id.detail);
			Status status = weibolistadapter.getItem(postionIndex);
			if(status != null)
			{
				Bundle bundle = new Bundle();
				bundle.putSerializable("status", status);
				Intent intent = new Intent(HomeAcitivity.this,StatusDetailActivity.class);
				intent.putExtras(bundle);
				startActivity(intent);
			}else {		}
			break;
		case R.id.exit://退出
			AlertDialog.Builder alertdialog = new AlertDialog.Builder(HomeAcitivity.this);
			alertdialog.setTitle("退出微博");
			alertdialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
			{
				@Override
				public void onClick(DialogInterface arg0, int arg1)
				{
					AccessTokenKeeper.clear(HomeAcitivity.this);
					Log.d("clear==",mAccessToken.toString());
					AccountAPI accountapi = new AccountAPI(mAccessToken);
					requestListener requestLis = new requestListener();
					accountapi.endSession(requestLis);
					String json = null;
					json = requestLis.get();
					Message msg2 = new Message();
					msg2.obj = json + "null";
					msg2.what = 0x120;
					handler.sendMessage(msg2);
					finish();
				}
			});
			alertdialog.setNegativeButton(android.R.string.cancel,new DialogInterface.OnClickListener()
			{
				@Override
				public void onClick(DialogInterface dialog, int which)
				{		
				}
			});
			alertdialog.show();
			break;
		default:
			break;
		}
		return super.onContextItemSelected(item);
		}
}

上面的代码是activity实现类,根据我的注释应该还是很容易理解的,不过里面用到了weiboManager类和WeiboListAdapter适配器,在此详细说明WeiboManager类和WeiboListAdapter类.

(三)

WeiboManager类比较简单,使用的是已经实现好的工具类。先看代码:

public class WeiboManager implements Constants
{
	private static String json;
	private static List<Status> statuses = null;
	private static Oauth2AccessToken mAccessToken;
	private static StatusesAPI statusesapi;
	public WeiboManager(){}//默认构造器
	public WeiboManager(Context context)//需要Context参数的构造器,这里使用Context最好
	{
		mAccessToken = AccessTokenKeeper.readAccessToken(context);
		statusesapi= new StatusesAPI(mAccessToken);
		//微博接口api,这个工具类是在sinasdk.jar包中已经封装好了,可以直接使用,这个jar包的下载地址我会在
		//博文最后给出jar包的下载地址,供大家使用。当然这个jar包的工具类远不止这一个
		//AccountAPI CommentsAPI CommonAPI FavoritesAPI FriendshipsAPI PlaceAPI RegisterAPI等等这个api接口均在
		//该jar包中定义好,开发者只需给定参数调用接口即可。
	}

	@SuppressWarnings("unchecked")
	public static List<Status> getHomeTimeline()
	{
		/**
		* StatusesAPI是微博api接口,里面定义的方法有很多,
		*	publicTimeline 返回最新的公共微博
		*	friendsTimeline 获取当前登录用户及其所关注用户的最新微博
		*	friendsTimelineIds 获取当前登录用户及其所关注用户的最新微博的ID
		*	userTimeline 获取某个用户最新发表的微博列表(与下面这个方法参数不同)
		*	userTimeline 获取某个用户最新发表的微博列表
		*	repostTimeline 获取指定微博的转发微博列表
		*	等等 这些方法均是在StatusesAPI工具类中已经实现好的,我在这里直接使用homeTimeline获取好友微博列表
		*	的方法。由于并不是做很大的应用,这里只是学习测试,所以这里的参数也是给定的。
		*	
		*	homeTimeline方法中的参数说明如下:
		* 获取当前登录用户及其所关注用户的最新微博
		*	public void homeTimeline( long since_id, long max_id, int count, int page,
		*		boolean base_app, FEATURE feature, boolean trim_user, RequestListener listener)
		* @param since_id 若指定此参数,则返回ID比since_id大的微博(即比since_id时间晚的微博),默认为0。
		* @param max_id 若指定此参数,则返回ID小于或等于max_id的微博,默认为0。
		* @param count 单页返回的记录条数,默认为50。
		* @param page 返回结果的页码,默认为1。
		* @param base_app 是否只获取当前应用的数据。false为否(所有数据),true为是(仅当前应用),默认为false。
		* @param feature 过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。
		* @param trim_user 返回值中user字段开关,false:返回完整user字段、true:user字段仅返回user_id,默认为false。
		* @param listener
		*	对于RequestListener监听器我在详细说明一下,如果大家的记性好的话,这个监听器大家是用到过的。
		*	在进行auth2.0授权认证的时候,获取Token值的时候,这个Token值得获取就是通过监听器回调得到的,原理还这里用
		*	到的是一样的。
		*	当调用该方法的时候,会把该方法中的new RequestListener()匿名监听器对象作为参数传递,当服务器返回响应
		*	会自动的调用该监听器的方法,如果成功,调用onComplete方法,服务器返回的数据就是response参数的值
		*	如果服务器由于某种原因错误,则会调用其他的方法。
		**/
		statusesapi.homeTimeline(0L,0L,10,1,false,FEATURE.ALL,false,new RequestListener(){
			@Override
			public void onComplete(String response)
			{
				if (response == null)
				{
					return;
				}
				this.json = response;//服务器返回的数据是JSON格式的字符串数据,
				statuses = JSONAndObject.convert(Status.class, json, "statuses");//这里把json字符串数据转化为微博对象
			}
			@Override
			public void onComplete4binary(ByteArrayOutputStream responseOS)
			{
				// TODO Auto-generated method stub
			}
			@Override
			public void onIOException(IOException e)
			{
				// TODO Auto-generated method stub
			}
			@Override
			public void onError(WeiboException e)
			{
				// TODO Auto-generated method stub
			}
		});
		return statuses;
	}
}

	/**
	*
	*该工具类就是把json字符串转化为微博对象,是一个工具类,可以直接使用的。
	**/
public class JSONAndObject
{
	// 使用该方法时需要先创建一个Object,传入第1个参数
	public static Object convertSingleObject(Object obj, String json)
	{
		if (obj == null || json == null)
			return obj;
		try
		{
			// 只使用public类型字段
			Field[] fields = obj.getClass().getFields();
			if (fields != null)
			{
				JSONObject jsonObject = new JSONObject(json);

				for (Field field : fields)
				{
					try
					{
						// 如果json中没有相应的key,会抛出异常,继续扫描下一个key
						Object objValue = jsonObject.get(field.getName());
						// 字符串类型
						if (field.getType() == String.class)
						{
							field.set(obj, String.valueOf(objValue));
						}
						// long类型
						else if (field.getType() == long.class)
						{
							field.set(obj,Long.valueOf(String.valueOf(objValue)));
						}
						// int类型
						else if (field.getType() == int.class)
						{
							field.set(obj,Integer.valueOf(String.valueOf(objValue)));
						}
						// boolean类型
						else if (field.getType() == boolean.class)
						{
							field.set(obj, Boolean.getBoolean(String.valueOf(objValue)));
						}
						// Object类型(WeiboObject类型)
						else
						{
							Object fieldObject = field.getType().newInstance();
							if (fieldObject instanceof WeiboObject)
							{
								convertSingleObject(fieldObject,String.valueOf(objValue));
								field.set(obj, fieldObject);
							}
						}
					}
					catch (Exception e)
					{
						// TODO: handle exception
					}
				}
			}
		}
		catch (Exception e)
		{
		}
		return obj;
	}

	// 将json字符串转换为List
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static List convert(Class c, String json, String propertyName)
	{
		List objs = null;
		if (c == null || json == null)
			return objs;
		try
		{
			// 只使用public类型字段
			Field[] fields = c.getFields();
			if (fields != null)
			{
				String jsonStr = json;
				if (propertyName != null)
				{
					JSONObject jsonObject = new JSONObject(json);
					jsonStr = jsonObject.get(propertyName).toString();
				}
				JSONArray jsonArray = new JSONArray(jsonStr);
				objs = new ArrayList();
				for (int i = 0; i < jsonArray.length(); i++)
				{
					Object obj = c.newInstance();
					objs.add(obj);
					convertSingleObject(obj, jsonArray.getString(i));
				}
			}
		}
		catch (Exception e)
		{
			Log.d("convert==", e.getMessage());
		}
		return objs;
	}

	public static String convertSingleObjectToJson(Object obj)
	{
		String json = null;
		if (obj == null)
		{
			return json;
		}
		Field[] fields = obj.getClass().getFields();
		json = "{";
		for (int i = 0; i < fields.length; i++)
		{
			try
			{
				Field field = fields[i];
				if (field.getType() == String.class)
				{
					String temp = ((field.get(obj) == null) ? "" : String
							.valueOf(field.get(obj)));

					temp = temp.replaceAll("\"", "\\\\\"");
					json += "\"" + field.getName() + "\":\"" + temp + "\"";
				}
				// long类型
				else if (field.getType() == long.class)
				{
					json += "\"" + field.getName() + "\":" + field.getLong(obj);
				}
				// int类型
				else if (field.getType() == int.class)
				{
					json += "\"" + field.getName() + "\":" + field.getInt(obj);
				}
				// boolean类型
				else if (field.getType() == boolean.class)
				{
					json += "\"" + field.getName() + "\":"
							+ field.getBoolean(obj);
				}
				// Object类型(WeiboObject类型)
				else
				{
					Object fieldObject = field.get(obj);
					if (fieldObject instanceof WeiboObject)
					{
						json += "\"" + field.getName() + "\":"
								+ convertSingleObjectToJson(fieldObject);
					}
					else
					{
						continue;
					}
				}
				if (i < fields.length - 1)
				{
					json += ",";
				}
			}
			catch (Exception e)
			{
				// TODO: handle exception
			}
		}
		json += "}";
		return json;
	}

	public static String convertObjectToJson(Object obj, String propertyName)
	{
		String json = null;
		if (obj == null)
			return json;
		if (obj instanceof List)
		{
			List list = (List) obj;
			if (propertyName != null)
			{
				// 包含一个属性的对象,这个属性是对象数组
				json = "{\"" + propertyName + "\":[";
			}
			else
			{
				// 对象数组
				json = "[";
			}
			for (int i = 0; i < list.size(); i++)
			{
				Object item = list.get(i);
				json += convertSingleObjectToJson(item);
				if (i < list.size() - 1)
					json += ",";
			}
			if (propertyName != null)
			{
				json += "]}";
			}
			else
			{
				json = "]";
			}
		}
		return json;
	}
}

(四)

下面给出WeiboListAdapter类的实现代码,大家应该对BaseAdapter很熟悉了。
public class WeiboListAdapter extends BaseAdapter implements Serializable
{
	Activity activity;
	protected LayoutInflater layoutInflater;
	protected int faceType;
	protected List<Status> statuses;
	public static View view = null;
	@Override
	public int getCount()
	{
		return statuses.size();
	}
	@Override
	public Status getItem(int position)
	{
		return statuses.get(position);
	}
	@Override
	public long getItemId(int position)
	{
		return position;
	}
	//这里使用的是图片下载机制,使用异步方式下载
	public static Drawable loadImage(String url,ImageView imgeview)
	{
		AsyncImageLoader asyncImageLoader = new AsyncImageLoader();
		Drawable drawable = asyncImageLoader.loadDrawable(url,imgeview , new ImageCallback()
		{  
		    @Override  
		    public void imageLoaded(Drawable imageDrawable, ImageView imageView,String imageUrl)
		    {  
		    	imageView.setImageDrawable(imageDrawable);
		    }
		});
		return drawable;
	}
	@Override
	public View getView(int position, View convertView, ViewGroup parent)
	{
		if (convertView == null)
		{
			convertView = layoutInflater.inflate(R.layout.weibosdkdemo_list_item, null);
		}
		View weiboListItem = convertView.findViewById(R.id.linearlayout_weibo_list_item);
		if (position < statuses.size())
		{
			weiboListItem.setVisibility(View.VISIBLE);
			Status status = statuses.get(position);
			TextView statusText = (TextView) convertView.findViewById(R.id.textview_text);//微博文本内容
			TextView name = (TextView) convertView.findViewById(R.id.textview_name);//微博作者
			TextView createdAt = (TextView) convertView.findViewById(R.id.textview_created_at);//微博发布时间
			
			ImageView profileImage = (ImageView)convertView.findViewById(R.id.imageview_profile_image);//微博作者头像
			profileImage.setImageResource(R.drawable.portrait);//设置微博作者头像(采用默认头像)
			
			ImageView picture = (ImageView) convertView.findViewById(R.id.imageview_picture);//微博带图像标志
			ImageView statusImage = (ImageView) convertView.findViewById(R.id.imageview_status_image);//微博内容图片
			ImageView verified = (ImageView) convertView.findViewById(R.id.imageview_verified);//认证用户图标
			verified.setVisibility(View.GONE);
			if (status.user != null)//如果微博作者不空
			{
				Tools.userVerified(verified, status.user.verified_type);
			}

			LinearLayout insideContent = (LinearLayout) convertView
					.findViewById(R.id.linearlayout_inside_content);//微博内容的线性布局
			ImageView retweetdetailImage = (ImageView) convertView
					.findViewById(R.id.imageview_retweetdetail_image);//转发微博的图片

			TextView retweetdetailText = (TextView) convertView
					.findViewById(R.id.textview_retweetdetail_text);//转发微博的文本内容
			TextView source = (TextView) convertView.findViewById(R.id.textview_source);//显示微博来源
			// 当前微博有图像,设置微博图片可见
			//如果微博中存在图片(包含转发微博中的图片),则设置图片标记可见
			//如果微博中存在图片(不是转发)则设置图片可见
			//如果转发微博中存在图片 则设置转发微博图片可见
			//初始化
			picture.setVisibility(View.INVISIBLE);
			statusImage.setVisibility(View.GONE);
			retweetdetailImage.setVisibility(View.GONE);
			if (WeiboManager.hasPicture(status))
			{
				if ( (status.thumbnail_pic != null) && (!"".equals(status.thumbnail_pic) ))//如果微博有图片
					{
						statusImage.setVisibility(View.VISIBLE);
						picture.setVisibility(View.VISIBLE);
					}
				if(status.retweeted_status != null)
				{
					if ( (status.retweeted_status.thumbnail_pic != null)
							&& (!"".equals(status.retweeted_status.thumbnail_pic)))
					{
						retweetdetailImage.setVisibility(View.VISIBLE);//如果转发微博内容有图片
						picture.setVisibility(View.VISIBLE);
					}
				}
			}else//否则设置微博图片不可见
			{
				picture.setVisibility(View.INVISIBLE);
				statusImage.setVisibility(View.GONE);
				retweetdetailImage.setVisibility(View.GONE);
			}
			// 装载图像  头像设置
			Drawable profilePortrait=null;
			if (status.user != null)
			{
				profilePortrait = loadImage(status.user.profile_image_url,profileImage);//如果微博用户存在
			}
			if(profilePortrait == null)
			{
				profileImage.setImageResource(R.drawable.portrait);
			}else
			{
	//			profileImage.setImageDrawable(profilePortrait);
			}
			profilePortrait=null;//再次初始化
			//装载微博图片,默认情况下为GONE
			Drawable profileStatusImage = null;
			if (status != null && status.thumbnail_pic!="")
			{
				profileStatusImage = loadImage(status.thumbnail_pic,statusImage);
			}
			profileStatusImage = null;
			statusText.setText(Tools.changeTextToFace(activity,Html.fromHtml(Tools.atBlue(status.text))));//设置微博文本内容,同时对字符进行处理
			if (status.user != null)
				name.setText(status.user.name);//设置微博作者
			
			createdAt.setText(Tools.getTimeStr(status.getCreatedAt(),new Date()));//设置微博发布时间
			source.setText("来自  " + status.getTextSource());//设置微博来源
			if (status.retweeted_status != null	&& status.retweeted_status.user != null)//如果是转发微博,并且转发微博的作者存在
			{
				insideContent.setVisibility(View.VISIBLE);//设置转发微博内容的线性布局可见
				retweetdetailText.setText(Html.fromHtml(Tools.atBlue("@"
						+ status.retweeted_status.user.name + ":"
						+ status.retweeted_status.text)));//设置转发微博的文本内容,同时对字符进行处理
				Drawable profileStatusThumbnailPic = null;
				if (status != null && status.retweeted_status.thumbnail_pic != "")
				{
					profileStatusThumbnailPic = loadImage(status.retweeted_status.thumbnail_pic,retweetdetailImage);
				}
				profileStatusThumbnailPic = null;
			}else//否则设置微博内容的线性布局不可见
			{
				insideContent.setVisibility(View.GONE);
			}
		}
		else//如果获取position位置 超过了微博List的长度,设置不可见
		{
			weiboListItem.setVisibility(View.GONE);
		}
		view = convertView;
		return convertView;
	}
	
	public WeiboListAdapter(Activity activity)//构造器
	{
		this.activity = activity;
	}
	public WeiboListAdapter(Activity activity, List<Status> statuses, int faceType)
	{
		this.activity = activity;
		this.faceType = faceType;
		layoutInflater = (LayoutInflater) activity
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		this.statuses = new ArrayList<Status>();
		if (statuses != null)
			this.statuses.addAll(statuses);
	}
}


WeiboListAdapter类中的布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
     <LinearLayout
        android:id="@+id/linearlayout_weibo_list_item"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="top"
        android:orientation="horizontal" >
		<!-- 左侧的布局 -->
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="top"
            android:orientation="horizontal" >

            <FrameLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" >
				<!-- 头像 -->
                <ImageView
                    android:id="@+id/imageview_profile_image"
                    android:layout_width="61dp"
                    android:layout_height="61dp"
                    android:paddingBottom="5dp"
                    android:paddingLeft="5dp"
                    android:paddingRight="5dp"
                    android:paddingTop="10dp"
                    android:src="@drawable/portrait" />
				<!-- 认证图像 -->
                <ImageView
                    android:id="@+id/imageview_verified"
                    android:layout_width="18dp"
                    android:layout_height="18dp"
                    android:layout_gravity="right|bottom"
                    android:src="@drawable/v_blue"
                    android:visibility="gone" />
            </FrameLayout>
			<!-- 右侧布局 -->
            <RelativeLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:padding="5dp" >
				<!-- 显示用户名 -->
                <TextView
                    android:id="@+id/textview_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="#F00"
                    android:textSize="14sp" />

                <LinearLayout
                    android:id="@+id/linearlayout_time"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="5dp"
                    android:layout_toRightOf="@id/textview_name"
                    android:gravity="right"
                    android:orientation="horizontal" >
					<!-- 如果微博带图像会显示一个标记 -->
                    <ImageView
                        android:id="@+id/imageview_picture"
                        android:layout_width="16dp"
                        android:layout_height="12dp"
                        android:layout_marginTop="3dp"
                        android:src="@drawable/pic"
                        android:visibility="invisible" />
					<!-- 显示微博发布时间 -->
                    <TextView
                        android:id="@+id/textview_created_at"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="6dp"
                        android:textColor="#F18D00"
                        android:textSize="12sp" />
                </LinearLayout>

                <LinearLayout
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_below="@id/textview_name"
                    android:layout_marginRight="5dp"
                    android:orientation="vertical" >
					<!-- 显示微博内容 -->
                    <TextView
                        android:id="@+id/textview_text"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:autoLink="all"
                        android:linksClickable="false"
                        android:textColor="#000"
                        android:textSize="14sp" />

                    <LinearLayout
                        android:id="@+id/linearlayout_inside_content"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:background="@drawable/content_border"
                        android:orientation="vertical"
                        android:visibility="gone" >
						<!-- 如果是转发微博,显示原微博内容   -->
                        <TextView
                            android:id="@+id/textview_retweetdetail_text"
                            android:layout_width="fill_parent"
                            android:layout_height="wrap_content"
                            android:autoLink="all"
                            android:linksClickable="false"
                            android:textColor="#000"
                            android:textSize="14sp" />
						<!-- 显示原微博图像 -->
                        <ImageView
                            android:id="@+id/imageview_retweetdetail_image"
                            android:layout_width="fill_parent"
                            android:layout_height="80dp"
                            android:layout_marginTop="5dp"
                            android:src="@drawable/pic_loading"
                            android:visibility="gone" />
                    </LinearLayout>
					<!-- 显示微博图像(不是转发微博) -->
                    <ImageView
                        android:id="@+id/imageview_status_image"
                        android:layout_width="fill_parent"
                        android:layout_height="80dp"
                        android:layout_marginTop="5dp"
                        android:src="@drawable/pic_loading"
                        android:visibility="gone" />
					<!-- 显示微博来源 -->
                    <TextView
                        android:id="@+id/textview_source"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="6dp"
                        android:linksClickable="false"
                        android:textColor="#000"
                        android:textSize="14sp" />
                </LinearLayout>
            </RelativeLayout>
        </LinearLayout>
    </LinearLayout>
</FrameLayout>

(五)

在WeiboListAdapter类中使用到了AsyncImageLoader这个类,这个类的功能是异步下载图片,这个类是网上面的大神做的,我只直接拿过来用的,再次贴出来供大家学习下,由于不知道出处了,所以请原作者勿怪勿怪哈^_^

public class AsyncImageLoader
{
	/**
	 * 软引用对象,在响应内存需要时,由垃圾回收器决定是否清除此对象。软引用对象最常用于实现内存敏感的缓存。
	 */
	private HashMap<String, SoftReference<Drawable>> imageCache;
	public AsyncImageLoader()
	{
		imageCache = new HashMap<String, SoftReference<Drawable>>();
	}

	@SuppressLint("HandlerLeak")
	public Drawable loadDrawable(final String imageUrl, 
			final ImageView imageView, final ImageCallback imagecallback)
	{
		if (imageCache.containsKey(imageUrl))
		{
			// 从缓存中读取
			SoftReference<Drawable> softReference = imageCache.get(imageUrl);
			Drawable drawable = softReference.get();
			if (drawable != null)
			{
				return drawable;
			}
		}
		final Handler handler = new Handler()
		{
			@Override
			public void handleMessage(Message msg)
			{
				super.handleMessage(msg);
				imagecallback.imageLoaded((Drawable) msg.obj, imageView, imageUrl);
			}
		};
		new Thread()
		{
			public void run()
			{
				Drawable drawable = loadImageFromUrl(imageUrl);
				imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
				//这种方法性能更好。主要区别在Message对象的创建上。
				/*obtainmessage()是从消息池中拿来一个msg 不需要另开辟空间
				* new需要重新申请,效率低,obtianmessage可以循环利用;
				*/
				Message message = handler.obtainMessage(0, drawable);
				handler.sendMessage(message);
			}
		}.start();
		return null;
	}
	public static Drawable loadImageFromUrl(String urlPath)
	{
		URL url;
		InputStream is = null;
		try
		{
			url = new URL(urlPath);
			is = (InputStream) url.getContent();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		Drawable drawable = Drawable.createFromStream(is, "src");
		return drawable;
	}
}

这个类的代码看上去也非常简单,使用弱引用的功能,说实话,之前没有用过弱引用,现在用了一下,是不是感觉挺不错的。不过弱引用还是有一点不好的地方,因为在内存紧张的应用当中,使用弱引用会被垃圾回收机制回收,此次使用弱引用指向图片的缓存,如果垃圾回收,那么就是这张图片显示不出来了,所以当图片显示不出来的时候不仅仅是网络的问题了,还有可能是缓存或者内存的问题了。所以使用缓存-弱引用有这样的小小的不爽的地方,但是它的性能却是好的,因为总比内存溢出要好很多!

至此,一个微博列表已经实现了。马马虎虎写这么多,我也知道表述清楚了没有,还希望各位见谅哈!








0. 下载: 本程序可自由修改, 自由分发, 可在http://download.csdn.net/user/lgg201下载 1. 分页的需求 信息的操纵和检索是当下互联网和企业信息系统承担的主要责任. 信息检索是从大量的数据中找到符合条件的数据以用户界面展现给用户. 符合条件的数据通常会有成千上万条, 而用户的单次信息接受量是很小的, 因此, 如果一次将所有符合用户条件的数据展现给用户, 对于多数场景, 其中大部分数据都是冗余的. 信息检索完成后, 是需要经过传输(从存储介质到应用程序)和相关计算(业务逻辑)的, 因此, 我们需要一种分段的信息检索机制来降低这种冗余. 分页应运而生. 2. 分页的发展 基本的分页程序, 将数据按照每页记录数(page_size)将数据分为ceil(total_record / page_size)页, 第一次为用户展现第一段的数据, 后续的交互过程中, 用户可以选择到某一页对数据进行审阅. 后来, 主要是在微博应用出现后, 由于其信息变化很快, 而其特性为基于时间线增加数据, 这样, 基本的分页程序不能再满足需求了: a) 当获取下一页时, 数据集可能已经发生了很多变化, 翻页随时都可能导致数据重复或跳跃; b) 此类应用采用很多采用一屏展示多段数据的用户界面, 更加加重了数据重复/跳跃对用户体验的影响. 因此, 程序员们开始使用since_id的方式, 将下一次获取数据的点记录下来, 已减轻上述弊端. 在同一个用户界面, 通过用户阅读行为自动获取下一段/上一段数据的确比点击"下一页"按钮的用户体验要好, 但同样有弊端: a) 当用户已经到第100页时, 他要回到刚才感兴趣的第5页的信息时, 并不是很容易, 这其实是一条设计应用的规则, 我们不能让用户界面的单页屏数过多, 这样会降低用户体验; b) 单从数据角度看, 我们多次读取之间的间隔时间足够让数据发生一些变化, 在一次只展示一屏时, 我们很难发现这些问题(因此不影响用户体验), 然而当一页展示100屏数据时, 这种变化会被放大, 此时, 数据重复/跳跃的问题就会再次出现; c) 从程序的角度看, 将大量的数据放置在同一个用户界面, 必然导致用户界面的程序逻辑受到影响. 基于以上考虑, 目前应用已经开始对分页进行修正, 将一页所展示的屏数进行的限制, 同时加入了页码的概念, 另外也结合since_id的方式, 以达到用户体验最优, 同时保证数据逻辑的正确性(降低误差). 3. 分页的讨论 感谢xp/jp/zq/lw四位同事的讨论, 基于多次讨论, 我们分析了分页程序的本质. 主要的结论点如下: 1) 分页的目的是为了分段读取数据 2) 能够进行分页的数据一定是有序的, 哪怕他是依赖数据库存储顺序. (这一点换一种说法更容易理解: 当数据集没有发生变化时, 同样的输入, 多次执行, 得到的输出顺序保持不变) 3) 所有的分段式数据读取, 要完全保证数据集的一致性, 必须保证数据集顺序的一致性, 即快照 4) 传统的分页, 分段式分页(每页内分为多段)归根结底是对数据集做一次切割, 映射到mysql的sql语法上, 就是根据输入求得limit子句, 适用场景为数据集变化频率低 5) since_id类分页, 其本质是假定已有数据无变化, 将数据集的某一个点的id(在数据集中可以绝对定位该数据的相关字段)提供给用户侧, 每次携带该id读取相应位置的数据, 以此模拟快照, 使用场景为数据集历史数据变化频率低, 新增数据频繁 6) 如果存在一个快照系统, 能够为每一个会话发起时的数据集产生一份快照数据, 那么一切问题都迎刃而解 7) 在没有快照系统的时候, 我们可以用since_id的方式限定数据范围, 模拟快照系统, 可以解决大多数问题 8) 要使用since_id方式模拟快照, 其数据集排序规则必须有能够唯一标识其每一个数据的字段(可能是复合的) 4. 实现思路 1) 提供SQL的转换函数 2) 支持分段式分页(page, page_ping, ping, ping_size), 传统分页(page, page_size), 原始分页(offset-count), since_id分页(prev_id, next_id) 3) 分段式分页, 传统分页, 原始分页在底层均转换为原始分页处理 5. 实现定义 ping_to_offset 输入: page #请求页码, 范围: [1, total_page], 超过范围以边界计, 即0修正为1, total_page + 1修正为total_page ping #请求段号, 范围: [1, page_ping], 超过范围以边界计, 即0修正为1, page_ping + 1修正为page_ping page_ping #每页分段数, 范围: [1, 无穷] count #要获取的记录数, 当前应用场景含义为: 每段记录数, 范围: [1, 无穷] total_record #总记录数, 范围: [1, 无穷] 输出: offset #偏移量 count #读取条数 offset_to_ping 输入: offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] page_ping #每页分段数, 范围: [1, 无穷] count #读取条数, 范围: [1, 无穷] 输出: page #请求页码 ping #请求段号 page_ping #每页分段数 count #要获取的记录数, 当前应用场景含义为: 每段记录数 page_to_offset 输入: page #请求页码, 范围: [1, total_page], 超过范围以边界计, 即0修正为1, total_page + 1修正为total_page total_record #总记录数, 范围: [1, 无穷] count #要获取的记录数, 当前应用场景含义为: 每页条数, 范围: [1, 无穷] 输出: offset #偏移量 count #读取条数 offset_to_page 输入: offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] count #读取条数, 范围: [1, 无穷] 输出: page #请求页码 count #要获取的记录数, 当前应用场景含义为: 每页条数 sql_parser #将符合mysql语法规范的SQL语句解析得到各个组件 输入: sql #要解析的sql语句 输出: sql_components #SQL解析后的字段 sql_restore #将SQL语句组件集转换为SQL语句 输入: sql_components #要还原的SQL语句组件集 输出: sql #还原后的SQL语句 sql_to_count #将符合mysql语法规范的SELECT语句转换为获取计数 输入: sql_components #要转换为查询计数的SQL语句组件集 alias #计数字段的别名 输出: sql_components #转换后的查询计数SQL语句组件集 sql_add_offset 输入: sql_components #要增加偏移的SQL语句组件集, 不允许存在LIMIT组件 offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] count #要获取的记录数, 范围: [1, 无穷] 输出: sql_components #已增加LIMIT组件的SQL语句组件集 sql_add_since #增加since_id式的范围 输入: sql_components #要增加范围限定的SQL语句组件集 prev_id #标记上一次请求得到的数据左边界 next_id #标记上一次请求得到的数据右边界 输出: sql_components #增加since_id模拟快照的范围限定后的SQL语句组件集 datas_boundary #获取当前数据集的边界 输入: sql_components #要读取的数据集对应的SQL语句组件集 datas #结果数据集 输出: prev_id #当前数据集左边界 next_id #当前数据集右边界 mysql_paginate_query #执行分页支持的SQL语句 输入: sql #要执行的业务SQL语句 offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] count #读取条数, 范围: [1, 无穷] prev_id #标记上一次请求得到的数据左边界 next_id #标记上一次请求得到的数据右边界 输出: datas #查询结果集 offset #偏移量 count #读取条数 prev_id #当前数据集的左边界 next_id #当前数据集的右边界 6. 实现的执行流程 分段式分页应用(page, ping, page_ping, count): total_record = sql_to_count(sql); (offset, count) = ping_to_offset(page, ping, page_ping, count, total_record) (datas, offset, count) = mysql_paginate_query(sql, offset, count, NULL, NULL); (page, ping, page_ping, total_record, count) = offset_to_ping(offset, page_ping, count, total_record); return (datas, page, ping, page_ping, total_record, count); 传统分页应用(page, count): total_record = sql_to_count(sql); (offset, count) = page_to_offset(page, count, total_record) (datas, offset, count) = mysql_paginate_query(sql, offset, count, NULL, NULL); (page, total_record, count) = offset_to_page(offset, count, total_record); return (datas, page, total_record, count); since_id分页应用(count, prev_id, next_id): total_record = sql_to_count(sql); (datas, offset, count, prev_id, next_id) = mysql_paginate_query(sql, NULL, count, prev_id, next_id); return (count, prev_id, next_id); 复合型分段式分页应用(page, ping, page_ping, count, prev_id, next_id): total_record = sql_to_count(sql); (offset, count) = ping_to_offset(page, ping, page_ping, count, total_record) (datas, offset, count, prev_id, next_id) = mysql_paginate_query(sql, offset, count, prev_id, next_id); (page, ping, page_ping, total_record, count) = offset_to_ping(offset, page_ping, count, total_record); return (datas, page, ping, page_ping, total_record, count, prev_id, next_id); 复合型传统分页应用(page, count, prev_id, next_id): total_record = sql_to_count(sql); (offset, count) = page_to_offset(page, count, total_record) (datas, offset, count, prev_id, next_id) = mysql_paginate_query(sql, offset, count, prev_id, next_id); (page, total_record, count) = offset_to_page(offset, count, total_record); return (datas, page, total_record, count, prev_id, next_id); mysql_paginate_query(sql, offset, count, prev_id, next_id) need_offset = is_null(offset); need_since = is_null(prev_id) || is_null(next_id); sql_components = sql_parser(sql); if ( need_offset ) : sql_components = sql_add_offset(sql_components, offset, count); endif if ( need_since ) : sql_components = sql_add_since(sql_components, prev_id, next_id); endif sql = sql_restore(sql_components); datas = mysql_execute(sql); (prev_id, next_id) = datas_boundary(sql_components, datas); ret = (datas); if ( need_offset ) : append(ret, offset, count); endif if ( need_since ) : append(ret, prev_id, next_id); endif return (ret); 7. 测试点 1) 传统分页 2) 分段分页 3) 原始分页 4) since_id分页 5) 复合型传统分页 6) 复合型分段分页 7) 复合型原始分页 8. 测试数据构建 DROP DATABASE IF EXISTS `paginate_test`; CREATE DATABASE IF NOT EXISTS `paginate_test`; USE `paginate_test`; DROP TABLE IF EXISTS `feed`; CREATE TABLE IF NOT EXISTS `feed` ( `feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', `ctime` INT NOT NULL COMMENT '微博创建时间', `content` CHAR(20) NOT NULL DEFAULT '' COMMENT '微博内容', `transpond_count` INT NOT NULL DEFAULT 0 COMMENT '微博转发数' ) COMMENT '微博表'; DROP TABLE IF EXISTS `comment`; CREATE TABLE IF NOT EXISTS `comment` ( `comment_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '评论ID', `content` CHAR(20) NOT NULL DEFAULT '' COMMENT '评论内容', `feed_id` INT NOT NUL COMMENT '被评论微博ID' ) COMMENT '评论表'; DROP TABLE IF EXISTS `hot`; CREATE TABLE IF NOT EXISTS `hot` ( `feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', `hot` INT NOT NULL DEFAULT 0 COMMENT '微博热度' ) COMMENT '热点微博表'; 9. 测试用例: 1) 搜索最热微博(SELECT f.feed_id, f.content, h.hot FROM feed AS f JOIN hot AS h ON f.feed_id = h.feed_id ORDER BY hhot DESC, f.feed_id DESC) 2) 搜索热评微博(SELECT f.feed_id, f.content, COUNT(c.*) AS count FROM feed AS f JOIN comment AS c ON f.feed_id = c.feed_id GROUP BY c.feed_id ORDER BY count DESC, f.feed_id DESC) 3) 搜索热转微博(SELECT feed_id, content, transpond_count FROM feed ORDER BY transpond_count DESC, feed_id DESC) 4) 上面3种场景均测试7个测试点 10. 文件列表 readme.txt 当前您正在阅读的开发文档 page.lib.php 分页程序库 test_base.php 单元测试基础函数 test_convert.php 不同分页之间的转换单元测试 test_parse.php SQL语句解析测试 test_page.php 分页测试
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值