新浪微博PC客户端(DotNet WinForm版)—— 初探
最近兴趣使然尝试了一下使用DotNet技术实现新浪微博PC客户端,几天时间,目前实现登录、微博列表、发布纯文本微博功能,新浪API调用基本没什么难度,在微博列表形式处理上着实让我烦躁了一阵子,Windows Form使用不多,这次开发也感觉有些捉襟见肘。
环境:
操作系统:Windows 7 Ultimate
集成开发环境:Visual Studio 2010 Ultimate
.NET Framework:3.5
先看一下截图:
1、登录界面
2、 登录
3、第一次运行主界面加载
4、主页面
5、翻页
6)如果博文中有图,点击小图可以查看大图
新浪微博API返回的数据类型有两种:XML和JSON。在登录时,我直接用DataSet读的XML数据存储到一个静态用户信息类中;而博客列表数据我读取的是JSON格式的数据,并将JSON格式的数据反序列化为泛型对象(类)集合,这里的类是嵌套类。
然后绘制列表,显示数据的控件全部是动态添加的,这里最烦的是位置,可能也是这个原因导致每页20条数据加载比较慢(接口默认一页返回20条),最快也要2秒,经常要4-6秒,后面提供下载,大家可以试试。另外,影响速度的原因可能还有:1)网速;2)输出图片,有的是直接输出大图,在回复的源微博中如果有图片,总是获取不到缩略图,只好依次判断小图、中图、大图,先有哪个输出哪个。 用Label显示文本行间距没法调,只好做了个自定义控件,主要是根据字体大小了增大行距。
界面虽然使用皮肤(ssk),比Window Form原始的界面好看的多,但是比起Adobe的那个AIR还是有差距,或许可以尝试使用WPF。
后续要实现的功能(还没全部看API,有的功能不一定提供API):
1)转发、收藏、回复
2)发帖可以使用表情、可以上传图片
3)关注、粉丝列表
4)查看自己微博、他人微博列表
5)个人的一些配置信息
6)主界面左边想放新浪微博登录后,个人首页右边的内容
... ...
工作量还挺大:(的。
目前经过处理,程序运行后占用的内存在4M-25M这样的一个范围 ,如果不处理会一直攀升,100M、200M、... ... 。
在翻页时,加载显示瞬时CPU占用率能够达到30%左右,一般比较低,5%以下。
滚屏时,CPU占用率一般18%以下,都是瞬时的。
大家看到的数据异步加载使用了多线程,主要是使用了BackgroundWorker。
除了上面的问题,在滚屏时,滚动的内容有点闪,还没找到好的解决办法。
欢迎有兴趣的人一起讨论,提宝贵意见。有经验的人做个指导:)。
最后附上读取新浪微博API的部分代码:
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using MBClient.Entities;
using System.Runtime.Serialization.Json;
using System.Data;
using System.Drawing;
namespace MBClient.API
{
internal class ReadDataBySinaAPI
{
internal ReadDataBySinaAPI() { }
/// <summary>
/// 根据用户登录信息登录并读取用户信息到DataSet
/// 如果用户新浪通行证身份验证成功且用户已经开通微博则返回 http状态为 200;
/// 如果是不则返回401的状态和错误信息。此方法用了判断用户身份是否合法且已经开通微博。
/// http://open.t.sina.com.cn/wiki/index.php/Account/verify_credentials
/// </summary>
/// <param name="url">API URL (XML Format)http://api.t.sina.com.cn/account/verify_credentials.xml?source=AppKey
/// </param>
/// <returns></returns>
internal static DataSet ReadXMLDataToDataSet(string url)
{
CredentialCache mycache = new CredentialCache();
mycache.Add(new Uri(url), "Basic", new NetworkCredential(UserInfo.UserName, UserInfo.Password));
WebRequest myReq = WebRequest.Create(url);
myReq.Credentials = mycache;
myReq.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes(UserInfo.UserNamePassword)));
WebResponse wr = myReq.GetResponse();
DataSet ds = new DataSet();
using (Stream receiveStream = wr.GetResponseStream())
{
StreamReader reader = new StreamReader(receiveStream, Encoding.UTF8);
//string content = reader.ReadToEnd();
ds.ReadXml(reader);
reader.Close();
}
wr.Close();
return ds;
}
internal static string ReadXMLDataToString(string url)
{
CredentialCache mycache = new CredentialCache();
mycache.Add(new Uri(url), "Basic", new NetworkCredential(UserInfo.UserName, UserInfo.Password));
WebRequest myReq = WebRequest.Create(url);
myReq.Credentials = mycache;
myReq.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes(UserInfo.UserNamePassword)));
WebResponse wr = myReq.GetResponse();
string strData = null;
using (Stream receiveStream = wr.GetResponseStream())
{
StreamReader reader = new StreamReader(receiveStream, Encoding.UTF8);
strData = reader.ReadToEnd();
reader.Close();
}
wr.Close();
return strData;
}
/// <summary>
/// 以JSON格式字符串返回用户所有关注用户最新n条微博信息。和用户“我的首页”返回内容相同。
/// http://open.t.sina.com.cn/wiki/index.php/Statuses/friends_timeline
/// </summary>
/// <param name="url">API URL (JSON Format) http://api.t.sina.com.cn/statuses/friends_timeline.json?source=AppKey
/// </param>
/// <returns></returns>
internal static string ReadJsonDataToString(string url)
{
CredentialCache mycache = new CredentialCache();
mycache.Add(new Uri(url), "Basic", new NetworkCredential(UserInfo.UserName, UserInfo.Password));
WebRequest myReq = WebRequest.Create(url);
myReq.Credentials = mycache;
myReq.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes(UserInfo.UserNamePassword)));
WebResponse wr = myReq.GetResponse();
string content = null;
using (Stream receiveStream = wr.GetResponseStream())
{
StreamReader reader = new StreamReader(receiveStream, Encoding.UTF8);
content = reader.ReadToEnd();
reader.Close();
}
wr.Close();
return content;
}
/// <summary>
/// 将JSON格式字符串反序列化为Status集合对象
/// </summary>
/// <param name="url">同ReadJsonDataToString()</param>
/// <returns></returns>
internal static List<Status> DeserializeJsonToListObject(string url)
{
List<Status> listObj;
using (MemoryStream stream = new MemoryStream())
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List<Status>));
StreamWriter wr = new StreamWriter(stream);
string strJson = ReadJsonDataToString(url);
wr.Write(strJson);
wr.Flush();
stream.Position = 0;
Object obj = ser.ReadObject(stream);
listObj = (List<Status>)obj;
wr.Close();
}
return listObj;
}
/// <summary>
/// 获取并输出图片
/// </summary>
/// <param name="imgUrl">图片URL地址</param>
/// <returns></returns>
internal static Image GetAvatarImage(string imgUrl)
{
Uri myUri = new Uri(imgUrl);
WebRequest webRequest = WebRequest.Create(myUri);
WebResponse webResponse = webRequest.GetResponse();
Bitmap myImage = new Bitmap(webResponse.GetResponseStream());
return (Image)myImage;
}
/// <summary>
/// 发表一条微博
/// </summary>
/// <param name="url">API地址
/// http://api.t.sina.com.cn/statuses/update.json
/// </param>
/// <param name="data">AppKey和微博内容
/// "source=123456&status=" + System.Web.HttpUtility.UrlEncode(微博内容);
/// </param>
/// <returns></returns>
internal static bool PostBlog(string url,string data)
{
try
{
System.Net.WebRequest webRequest = System.Net.WebRequest.Create(url);
System.Net.HttpWebRequest httpRequest = webRequest as System.Net.HttpWebRequest;
System.Net.CredentialCache myCache = new System.Net.CredentialCache();
myCache.Add(new Uri(url), "Basic",
new System.Net.NetworkCredential(UserInfo.UserName, UserInfo.Password));
httpRequest.Credentials = myCache;
httpRequest.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(
new System.Text.ASCIIEncoding().GetBytes(UserInfo.UserName + ":" +
UserInfo.Password)));
httpRequest.Method = "POST";
httpRequest.ContentType = "application/x-www-form-urlencoded";
System.Text.Encoding encoding = System.Text.Encoding.UTF8;//System.Text.Encoding.ASCII
byte[] bytesToPost = encoding.GetBytes(data);//System.Web.HttpUtility.UrlEncode(data)
httpRequest.ContentLength = bytesToPost.Length;
System.IO.Stream requestStream = httpRequest.GetRequestStream();
requestStream.Write(bytesToPost, 0, bytesToPost.Length);
requestStream.Close();
return true;
}
catch
{
return false;
}
}
}
}
感谢博客园的这篇文章,参考价值很大:http://www.cnblogs.com/cmt/archive/2010/05/13/1733904.html
注:AppKey以内置,是我申请的,每IP每用户一小时内限调用350次API。
PS:别拿来干坏事... ...
附一条完整的博客内容数据(XML格式)
<created_at>Wed Nov 24 21:24:04 +0800 2010</created_at>
<id>3858478793</id>
<text>很好的推论。 //@白手套的小咪:这是罗永浩流传最广的名言之一。给他改一改:没有哪只鸟来到世间是为了躲枪子儿的。所以,手上拿着枪,并用“枪打出头鸟”这样的道理教训人的那些人,一定要明白,每只鸟都梦想出头,你打到老死也打不完的。</text>
<source>
<a href="http://t.sina.com.cn">新浪微博</a>
</source>
<favorited>false</favorited>
<truncated>false</truncated>
<geo/>
<in_reply_to_status_id></in_reply_to_status_id>
<in_reply_to_user_id></in_reply_to_user_id>
<in_reply_to_screen_name></in_reply_to_screen_name>
<user>
<id>1197161814</id>
<screen_name>李开复</screen_name>
<name>李开复</name>
<province>11</province>
<city>1000</city>
<location>北京</location>
<description>创新工场CEO</description>
<url>http://blog.sina.com.cn/kaifulee</url>
<profile_image_url>http://tp3.sinaimg.cn/1197161814/50/1290146312/1</profile_image_url>
<domain>kaifulee</domain>
<gender>m</gender>
<followers_count>2384862</followers_count>
<friends_count>132</friends_count>
<statuses_count>1039</statuses_count>
<favourites_count>2</favourites_count>
<created_at>Fri Aug 28 00:00:00 +0800 2009</created_at>
<following>false</following>
<verified>true</verified>
<allow_all_act_msg>false</allow_all_act_msg>
<geo_enabled>true</geo_enabled>
</user>
<retweeted_status>
<created_at>Wed Nov 24 17:35:07 +0800 2010</created_at>
<id>3853748245</id>
<text>“希望那些喜欢用“枪打出头鸟”这样的道理教训年轻人,并且因此觉得自己很成熟的中国人有一天能够明白这样一个事实,那就是:有的鸟来到世间,是为了做它该做的事,而不是专门躲枪子儿的。”</text>
<source>
<a href="http://t.sina.com.cn">新浪微博</a>
</source>
<favorited>false</favorited>
<truncated>false</truncated>
<geo/>
<in_reply_to_status_id></in_reply_to_status_id>
<in_reply_to_user_id></in_reply_to_user_id>
<in_reply_to_screen_name></in_reply_to_screen_name>
<user>
<id>1672204023</id>
<screen_name>Ran-w</screen_name>
<name>Ran-w</name>
<province>11</province>
<city>5</city>
<location>北京 朝阳区</location>
<description>什么都不知道总比什么都知道要好。慢慢经历那个不知道的未来。</description>
<url>http://blog.sina.com.cn/wongr11</url>
<profile_image_url>http://tp4.sinaimg.cn/1672204023/50/1290577674/0</profile_image_url>
<domain>wongr</domain>
<gender>f</gender>
<followers_count>757</followers_count>
<friends_count>178</friends_count>
<statuses_count>521</statuses_count>
<favourites_count>4</favourites_count>
<created_at>Mon Dec 21 00:00:00 +0800 2009</created_at>
<following>false</following>
<verified>false</verified>
<allow_all_act_msg>false</allow_all_act_msg>
<geo_enabled>true</geo_enabled>
</user>
</retweeted_status>
</status>
status是博主发表的博文,retweeted_status是回复或者转发了哪条博文。