新浪微博客户端开发之授权登录+获取微博列表

闲篇:
最近实在是乱得不行,至于怎么乱我也不知该怎么说,那么久没发博客就证明了这点,一般如果小巫有做详尽的计划,并把时间投入到上面的话,我是可以用最短的时间里把新浪微博客户端给整出来的,但现在进度很慢,我越来越不像个称职的程序猿,因为现在的生活已经不再是代码,更多的是想多享受跟朋友们在一起的快乐。这些话也不多说了,关于这个项目,其实我也头痛了一整子,我很久之前就买了一本李宁的Android应用开发实战,里面很大篇幅就是介绍这个客户端的开发,因为一开始我是比较迷惑的,迷惑他到底有没有使用新浪开发平台提供的SDK,然而整个授权过程到获取微博的各种数据又是怎样的?我还在考虑我要做成一个怎样的,是否只是单纯的模仿呢?反正种种疑虑,一阵子找不到北了。后来我花时间研究了他的代码,慢慢的也找了了一些门道,明白了他从是怎么把整个客户端搭建起来的,他没有使用新浪给我们提供的SDK,而似乎是把SDK的实现给翻出来了,因为SDK仅仅提供了获取微博数据的封装,开发者就只需调用API,知道怎么传参数就行了,所以很多高手是不会直接使用新浪提供的Android SDK。要我从头到尾开发,把所有业务逻辑实现,我实在不行,所以会直接参考Android应用开发实战所提供的代码,把整个新浪微博客户端开发出来给大家看看,这也算是小巫的一个提升,与大家共同进步。
正篇:
本篇博客呢,主要介绍如何进行授权认证,如何获取微博列表,因为代码比较多,我不会全部都能贴出来,整个客户端也没有开发完毕,我也是实现一个功能,然后整理博客发表出来,如果想要项目源码的,可以直接找我,我的QQ:659982592, 如果我在的话,我会尽快答复你的。
先来看看本篇博客想要实现的效果图:
看到以上的效果,我想很多人都想知道是如何实现的,不急,我们慢慢来看,这个可不是能一口吃掉的螃蟹。
我先简单介绍一下以上效果图的业务流程,启动程序后,首先需要获取用户授权,假如已经授权过了,就不会出现提示用户输入登录的界面,授权成功后,直接获取微博数据,显示到微博列表当中。整个流程其实也蛮简单的,现在我们来看看代码。
/Wwj_sina_weibo/src/com/wwj/sina/weibo/WeiboMain.java
这个类用来切换底部标签的,比较高的版本已经不推荐用这种方法实现,不过没关系,反正高版本兼容低版本。
package com.wwj.sina.weibo;import android.app.TabActivity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;/*** 主Activity* @author wwj* 通过点击RadioGroup下的RadioButton来切换不同界面*/
@SuppressWarnings("deprecation")
public class WeiboMain extends TabActivity {public TabHost mTabhost;public static final String TAB_HOME = "TabHome";public static final String TAB_MSG = "TabMsg";public static final String TAB_SELF = "TabSelfInfo";public static final String TAB_DISCOVE = "TabDiscove";public static final String TAB_MORE = "TabMore";RadioButton radio_button0;public static RadioGroup indexGroup;@SuppressWarnings("static-access")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 设置无标题requestWindowFeature(getWindow().FEATURE_NO_TITLE);this.setContentView(R.layout.tabbar);radio_button0 = (RadioButton) this.findViewById(R.id.tabbar_home);radio_button0.setChecked(true); // 设置首页按钮默认是按下状态mTabhost = (TabHost) findViewById(android.R.id.tabhost); // 获取TabHost// 设定标签、制定一个标签作为选项卡指示符TabSpec tabSpec1 = mTabhost.newTabSpec(TAB_HOME).setIndicator(TAB_HOME);// 指定一个加载activity的Intent对象作为选项卡内容tabSpec1.setContent(new Intent(WeiboMain.this, HomeActivity.class));mTabhost.addTab(tabSpec1); // 添加第一个子页TabSpec tabSpec2 = mTabhost.newTabSpec(TAB_MSG).setIndicator(TAB_MSG);tabSpec2.setContent(new Intent(WeiboMain.this, MessageActivity.class));mTabhost.addTab(tabSpec2); // 添加第二个子页TabSpec tabSpec3 = mTabhost.newTabSpec(TAB_SELF).setIndicator(TAB_SELF);tabSpec3.setContent(new Intent(WeiboMain.this, SelfInfoActivity.class));mTabhost.addTab(tabSpec3); // 添加第三个子页TabSpec tabSpec4 = mTabhost.newTabSpec(TAB_DISCOVE).setIndicator(TAB_DISCOVE);tabSpec4.setContent(new Intent(WeiboMain.this, DiscoveActivity.class));mTabhost.addTab(tabSpec4); // 添加第四个子页TabSpec tabSpec5 = mTabhost.newTabSpec(TAB_MORE).setIndicator(TAB_MORE);tabSpec5.setContent(new Intent(WeiboMain.this, MoreActivity.class));mTabhost.addTab(tabSpec5); // 添加第一个子页this.indexGroup = (RadioGroup) this.findViewById(R.id.main_radio);// 实现RadioGroup的子选项点击监听indexGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(RadioGroup group, int checkedId) {switch (checkedId) {case R.id.tabbar_home: // 首页mTabhost.setCurrentTabByTag(TAB_HOME);break;case R.id.tabbar_message:// 信息mTabhost.setCurrentTabByTag(TAB_MSG);break;case R.id.tabbar_me: // 个人资料mTabhost.setCurrentTabByTag(TAB_SELF);break;case R.id.tabbar_discove: // 发现mTabhost.setCurrentTabByTag(TAB_DISCOVE);break;case R.id.tabbar_more: // 更多mTabhost.setCurrentTabByTag(TAB_MORE);}}});}
}
/Wwj_sina_weibo/src/com/wwj/sina/weibo/HomeActivity.java
这个Activity就是显示微博列表的界面,代码还不完善,这里只看关键点。
package com.wwj.sina.weibo;import java.util.List;import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;import com.weibo.net.Weibo;
import com.wwj.sina.weibo.adapter.WeiboListAdapter;
import com.wwj.sina.weibo.interfaces.Const;
import com.wwj.sina.weibo.library.StorageManager;
import com.wwj.sina.weibo.library.WeiboData;
import com.wwj.sina.weibo.library.WeiboManager;
import com.wwj.sina.weibo.listener.impl.StatusRequestListenerImpl;
import com.wwj.sina.weibo.object.Status;
import com.wwj.sina.weibo.object.User;
import com.wwj.sina.weibo.util.Tools;
import com.wwj.sina.weibo.view.PullToRefreshListView;
import com.wwj.sina.weibo.view.PullToRefreshListView.OnRefreshListener;
import com.wwj.sina.weibo.workqueue.DoneAndProcess;
import com.wwj.sina.weibo.workqueue.WorkQueueMonitor;
import com.wwj.sina.weibo.workqueue.task.ParentTask;
import com.wwj.sina.weibo.workqueue.task.PullFileTask;/*** 主界面* * @author wwj 用于显示公共微博*/
public class HomeActivity extends Activity implements Const, OnClickListener,OnItemClickListener, OnItemLongClickListener, OnMenuItemClickListener,DoneAndProcess {public PullToRefreshListView weiboListView; // 自定义ListViewprivate Weibo weibo; // 微博对象public HomeData homeData; // 主界面数据public TextView username; // 用户名,显示在标题栏public Button btn_post_weibo; // 发布微博public Button btn_reload; // 加载新微博private LinearLayout linearLayoutHome;private Handler handler = new Handler() {public void handleMessage(android.os.Message msg) {};};// 微博列表主界面的数据public static class HomeData {public WeiboListAdapter weiboListAdapter;public WorkQueueMonitor imageWorkQueueMonitor;public WorkQueueMonitor taskWorkQueueMonitor;public User user;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.home);getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);loadView();getWindow().setBackgroundDrawable(null);weibo = Tools.getWeibo(this);     // 获取微博对象homeData = (HomeData) getLastNonConfigurationInstance();if (homeData == null) {homeData = new HomeData();if (!(weibo == null || !weibo.isSessionValid())) {@SuppressWarnings("unchecked")List<Status> statuses = StorageManager.loadList(this,Const.HOME);if (statuses == null) {statuses = WeiboManager.getHomeTimeline(this);}homeData.weiboListAdapter = WeiboData.loadWeiboListData(this,Const.HOME, weiboListView, statuses);}}}@Overrideprotected void onResume() {super.onResume();}@Overridepublic Object onRetainNonConfigurationInstance() {return homeData;}// 加载视图private void loadView() {weiboListView = (PullToRefreshListView) findViewById(R.id.weibolist);linearLayoutHome = (LinearLayout) findViewById(R.id.ll_home_layout);username = (TextView) this.findViewById(R.id.tv_home_name);btn_post_weibo = (Button) this.findViewById(R.id.btn_home_post_weibo);btn_reload = (Button) this.findViewById(R.id.btn_home_reload);weiboListView.setOnItemClickListener(this);btn_post_weibo.setOnClickListener(this);btn_reload.setOnClickListener(this);}@Overridepublic void onClick(View v) {Intent intent = null;GlobalObject globalObject = Tools.getGlobalObject(this);switch (v.getId()) {case R.id.btn_home_post_weibo: // 发布微博intent = new Intent(HomeActivity.this, PostWeibo.class);startActivity(intent);break;case R.id.btn_home_reload: // 刷新列表long sinceId = homeData.weiboListAdapter.getMaxId() + 1;WeiboManager.getHomeTimeline(this, sinceId, 0,DEFAULT_STATUS_COUNT, true, new StatusRequestListenerImpl(this, linearLayoutHome, HOME));View homeReloadAnim = findViewById(R.id.pb_home_reload);View homeReload = findViewById(R.id.btn_home_reload);homeReloadAnim.setVisibility(View.VISIBLE);homeReload.setVisibility(View.GONE);break;}}@Overridepublic void doneProcess(ParentTask task) {Message msg = new Message();msg.obj = task;if (task instanceof PullFileTask) {msg.what = HANDLER_TYPE_LOAD_PROFILE_IMAGE;msg.obj = ((PullFileTask) task).fileUrl;}handler.sendMessage(msg);}@Overridepublic boolean onMenuItemClick(MenuItem item) {return false;}@Overridepublic boolean onItemLongClick(AdapterView<?> parent, View view,int position, long id) {return false;}@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position,long id) {Intent intent = null;switch (parent.getId()) {case R.id.weibolist:// 按更多项// 注意:更多点击的位置if (position == homeData.weiboListAdapter.getCount()) {long maxId = homeData.weiboListAdapter.getMinId() - 1;WeiboManager.getHomeTimeline(this, 0, maxId,DEFAULT_STATUS_COUNT, true,new StatusRequestListenerImpl(this, linearLayoutHome,HOME));homeData.weiboListAdapter.showMoreAnim();} else {   // 点击列表项Status status = homeData.weiboListAdapter.getStatus(position);if (status != null) {intent = new Intent(this, WeiboViewer.class);WeiboViewer.status = status;startActivity(intent);}}break;}}}
上面使用到一个Tools类,这是一个工具类,用来获取微博对象和一些转换操作,稍微看一下
/Wwj_sina_weibo/src/com/wwj/sina/weibo/util/Tools.java
package com.wwj.sina.weibo.util;import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.ImageSpan;
import android.view.View;
import android.widget.ImageView;import com.weibo.net.Weibo;
import com.wwj.sina.weibo.GlobalObject;
import com.wwj.sina.weibo.interfaces.Const;
import com.wwj.sina.weibo.library.FaceMan;public class Tools implements Const {/*** 获取微博对象* @param activity* @return*/public static Weibo getWeibo(Activity activity) {GlobalObject globalObject = (GlobalObject) activity.getApplicationContext();return globalObject.getWeibo(activity);}/*** 判断当前是否有微博对象* @param activity* @return*/public static boolean hasWeibo(Activity activity) {GlobalObject globalObject = (GlobalObject) activity.getApplicationContext();return globalObject.getWeibo() == null ? false : true;}public static GlobalObject getGlobalObject(Activity activity) {GlobalObject globalObject = (GlobalObject) activity.getApplicationContext();return globalObject;}// 将微博的日期字符串转换为Date对象public static Date strToDate(String str) {// sample:Tue May 31 17:46:55 +0800 2011// E:周 MMM:字符串形式的月,如果只有两个M,表示数值形式的月 Z表示时区(+0800)SimpleDateFormat sdf = new SimpleDateFormat("E MMM dd HH:mm:ss Z yyyy",Locale.US);Date result = null;try {result = sdf.parse(str);} catch (Exception e) {// TODO: handle exception}return result;}public static void dataTransfer(InputStream is, OutputStream os) {byte[] buffer = new byte[8192];int count = 0;try {while((count = is.read(buffer)) > -1) {os.write(buffer, 0, count);}} catch (Exception e) {}}public static void userVerified(ImageView imageView, int verifiedType) {if (verifiedType >= 0) {imageView.setVisibility(View.VISIBLE);switch (verifiedType) {case 0:case 220:imageView.setImageLevel(verifiedType);break;default:imageView.setImageLevel(1);break;}}}public static SpannableString changeTextToFace(Context context,Spanned spanned) {String text = spanned.toString();SpannableString spannableString = new SpannableString(spanned);Pattern pattern = Pattern.compile("\\[[^\\]]+\\]");Matcher matcher = pattern.matcher(text);boolean b = true;while (b = matcher.find()) {String faceText = text.substring(matcher.start(), matcher.end());int resourceId = FaceMan.getResourceId(faceText);if (resourceId > 0) {Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId);ImageSpan imageSpan = new ImageSpan(bitmap);spannableString.setSpan(imageSpan, matcher.start(),matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);}}return spannableString;}public static String atBlue(String s) {StringBuilder sb = new StringBuilder();int commonTextColor = Color.BLACK;int signColor = Color.BLUE;int state = 1;String str = "";for (int i = 0; i < s.length(); i++) {switch (state) {case 1: // 普通字符状态// 遇到@if (s.charAt(i) == '@') {state = 2;str += s.charAt(i);}// 遇到#else if (s.charAt(i) == '#') {str += s.charAt(i);state = 3;}// 添加普通字符else {if (commonTextColor == Color.BLACK)sb.append(s.charAt(i));elsesb.append("<font color='" + commonTextColor + "'>"+ s.charAt(i) + "</font>");}break;case 2: // 处理遇到@的情况// 处理@后面的普通字符if (Character.isJavaIdentifierPart(s.charAt(i))) {str += s.charAt(i);}else {// 如果只有一个@,作为普通字符处理if ("@".equals(str)) {sb.append(str);}// 将@及后面的普通字符变成蓝色else {sb.append(setTextColor(str, String.valueOf(signColor)));}// @后面有#的情况,首先应将#添加到str里,这个值可能会变成蓝色,也可以作为普通字符,要看后面还有没有#了if (s.charAt(i) == '#') {str = String.valueOf(s.charAt(i));state = 3;}// @后面还有个@的情况,和#类似else if (s.charAt(i) == '@') {str = String.valueOf(s.charAt(i));state = 2;}// @后面有除了@、#的其他特殊字符。需要将这个字符作为普通字符处理else {if (commonTextColor == Color.BLACK)sb.append(s.charAt(i));elsesb.append("<font color='" + commonTextColor + "'>"+ s.charAt(i) + "</font>");state = 1;str = "";}}break;case 3: // 处理遇到#的情况// 前面已经遇到一个#了,这里处理结束的#if (s.charAt(i) == '#') {str += s.charAt(i);sb.append(setTextColor(str, String.valueOf(signColor)));str = "";state = 1;}// 如果#后面有@,那么看一下后面是否还有#,如果没有#,前面的#作废,按遇到@处理else if (s.charAt(i) == '@') {if (s.substring(i).indexOf("#") < 0) {sb.append(str);str = String.valueOf(s.charAt(i));state = 2;} else {str += s.charAt(i);}}// 处理#...#之间的普通字符else {str += s.charAt(i);}break;}}if (state == 1 || state == 3) {sb.append(str);} else if (state == 2) {if ("@".equals(str)) {sb.append(str);} else {sb.append(setTextColor(str, String.valueOf(signColor)));}}return sb.toString();}public static String setTextColor(String s, String color) {String result = "<font color='" + color + "'>" + s + "</font>";return result;}public static String getTimeStr(Date oldTime, Date currentDate) {long time1 = currentDate.getTime();long time2 = oldTime.getTime();long time = (time1 - time2) / 1000;if (time >= 0 && time < 60) {return "刚才";} else if (time >= 60 && time < 3600) {return time / 60 + "分钟前";} else if (time >= 3600 && time < 3600 * 24) {return time / 3600 + "小时前";} else {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");return sdf.format(oldTime);}}
}

上面定义了一个getWeibo()的方法,可以看到它是通过GlobalObject来获取微博对象的,那在看看这个类

/Wwj_sina_weibo/src/com/wwj/sina/weibo/GlobalObject.java
package com.wwj.sina.weibo;import android.app.Activity;
import android.app.Application;import com.weibo.net.Weibo;
import com.wwj.sina.weibo.interfaces.Const;
import com.wwj.sina.weibo.listener.AuthDialogListener;
import com.wwj.sina.weibo.net.PullFile;
import com.wwj.sina.weibo.object.Consumer;
import com.wwj.sina.weibo.workqueue.WorkQueueMonitor;
import com.wwj.sina.weibo.workqueue.WorkQueueStorage;public class GlobalObject extends Application implements Const{private Weibo weibo;private WorkQueueStorage workQueueStorage;private WorkQueueMonitor imageWorkQueueMonitor;private WorkQueueMonitor taskWorkQueueMonitor;public Weibo getWeibo(Activity activity) {if (weibo == null || !weibo.isSessionValid()) {weibo = Weibo.getInstance();     // 获取Weibo对象weibo.setupConsumerConfig(Consumer.consumerKey, Consumer.consumerSecret);weibo.setRedirectUrl(Consumer.redirectUrl);weibo.authorize(activity, new AuthDialogListener(activity));}return weibo;}public Weibo getWeibo() {return weibo;}public WorkQueueStorage getWorkQueueStorage() {if (workQueueStorage == null){workQueueStorage = new WorkQueueStorage();}return workQueueStorage;}public WorkQueueMonitor getWorkQueueMonitor(Activity activity) {if (imageWorkQueueMonitor == null) {imageWorkQueueMonitor = new WorkQueueMonitor(activity, getWorkQueueStorage(), new PullFile(), MONITOR_TYPE_IMAGE);imageWorkQueueMonitor.start();}return imageWorkQueueMonitor;}}

可以看到这个类是Application级别的,说明最先加载的是这个类,来看这个类定义的getWeibo()方法,现在很直观啦,这里就是获取授权认证的地方。设置好consumerKey和consumerSecret后,就可以调用authorize()方法进行授权了。这个方法在Weibo这个类当中,这个类很重要,能不能使用微博功能就看它了。

/Wwj_sina_weibo/src/com/weibo/net/Weibo.java
/** Copyright 2011 Sina.** Licensed under the Apache License and Weibo License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**    http://www.open.weibo.com*    http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.weibo.net;import java.io.IOException;
import java.net.MalformedURLException;import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.CookieSyncManager;/*** Encapsulation main Weibo APIs, Include: 1. getRquestToken , 2.* getAccessToken, 3. url request. Used as a single instance class. Implements a* weibo api as a synchronized way.* * @author ZhangJie (zhangjie2@staff.sina.com.cn)*/
public class Weibo {// public static String SERVER = "http://api.t.sina.com.cn/";public static String SERVER = "https://api.weibo.com/2/";public static String URL_OAUTH_TOKEN = "http://api.t.sina.com.cn/oauth/request_token";public static String URL_AUTHORIZE = "http://api.t.sina.com.cn/oauth/authorize";public static String URL_ACCESS_TOKEN = "http://api.t.sina.com.cn/oauth/access_token";public static String URL_AUTHENTICATION = "http://api.t.sina.com.cn/oauth/authenticate";public static String URL_OAUTH2_ACCESS_TOKEN = "https://api.weibo.com/oauth2/access_token";// public static String URL_OAUTH2_ACCESS_AUTHORIZE =// "http://t.weibo.com:8093/oauth2/authorize";public static String URL_OAUTH2_ACCESS_AUTHORIZE = "https://api.weibo.com/oauth2/authorize";private static String APP_KEY = "";private static String APP_SECRET = "";private static Weibo mWeiboInstance = null;private Token mAccessToken = null;private RequestToken mRequestToken = null;private WeiboDialogListener mAuthDialogListener;private static final int DEFAULT_AUTH_ACTIVITY_CODE = 32973;public static final String TOKEN = "access_token";public static final String EXPIRES = "expires_in";public static final String DEFAULT_REDIRECT_URI = "wbconnect://success";// 暂不支持public static final String DEFAULT_CANCEL_URI = "wbconnect://cancel";// 暂不支持private String mRedirectUrl;private Weibo() {Utility.setRequestHeader("Accept-Encoding", "gzip");Utility.setTokenObject(this.mRequestToken);mRedirectUrl = DEFAULT_REDIRECT_URI;}/*** 获取单例* @return*/public synchronized static Weibo getInstance() {if (mWeiboInstance == null) {mWeiboInstance = new Weibo();}return mWeiboInstance;}// 设置accessTokenpublic void setAccessToken(AccessToken token) {mAccessToken = token;}public Token getAccessToken() {return this.mAccessToken;}/*** 设置第三方key和secret* @param consumer_key* @param consumer_secret*/public void setupConsumerConfig(String consumer_key, String consumer_secret) {Weibo.APP_KEY = consumer_key;Weibo.APP_SECRET = consumer_secret;}public static String getAppKey() {return Weibo.APP_KEY;}public static String getAppSecret() {return Weibo.APP_SECRET;}public void setRequestToken(RequestToken token) {this.mRequestToken = token;}public static String getSERVER() {return SERVER;}public static void setSERVER(String sERVER) {SERVER = sERVER;}// 设置oauth_verifierpublic void addOauthverifier(String verifier) {mRequestToken.setVerifier(verifier);}public String getRedirectUrl() {return mRedirectUrl;}/*** 设置第三方回调页* @param mRedirectUrl*/public void setRedirectUrl(String mRedirectUrl) {this.mRedirectUrl = mRedirectUrl;}/*** Requst sina weibo open api by get or post* * @param url*            Openapi request URL.* @param params*            http get or post parameters . e.g.*            gettimeling?max=max_id&min=min_id max and max_id is a pair of*            key and value for params, also the min and min_id* @param httpMethod*            http verb: e.g. "GET", "POST", "DELETE"* @throws IOException* @throws MalformedURLException* @throws WeiboException*/public String request(Context context, String url, WeiboParameters params, String httpMethod,Token token) throws WeiboException {String rlt = Utility.openUrl(context, url, httpMethod, params, this.mAccessToken);return rlt;}/**/public RequestToken getRequestToken(Context context, String key, String secret,String callback_url) throws WeiboException {Utility.setAuthorization(new RequestTokenHeader());WeiboParameters postParams = new WeiboParameters();postParams.add("oauth_callback", callback_url);String rlt;rlt = Utility.openUrl(context, Weibo.URL_OAUTH_TOKEN, "POST", postParams, null);RequestToken request = new RequestToken(rlt);this.mRequestToken = request;return request;}public AccessToken generateAccessToken(Context context, RequestToken requestToken)throws WeiboException {Utility.setAuthorization(new AccessTokenHeader());WeiboParameters authParam = new WeiboParameters();authParam.add("oauth_verifier", this.mRequestToken.getVerifier()/* "605835" */);authParam.add("source", APP_KEY);String rlt = Utility.openUrl(context, Weibo.URL_ACCESS_TOKEN, "POST", authParam,this.mRequestToken);AccessToken accessToken = new AccessToken(rlt);this.mAccessToken = accessToken;return accessToken;}public AccessToken getXauthAccessToken(Context context, String app_key, String app_secret,String usrname, String password) throws WeiboException {Utility.setAuthorization(new XAuthHeader());WeiboParameters postParams = new WeiboParameters();postParams.add("x_auth_username", usrname);postParams.add("x_auth_password", password);postParams.add("oauth_consumer_key", APP_KEY);String rlt = Utility.openUrl(context, Weibo.URL_ACCESS_TOKEN, "POST", postParams, null);AccessToken accessToken = new AccessToken(rlt);this.mAccessToken = accessToken;return accessToken;}/*** 获取Oauth2.0的accesstoken* * https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&* client_secret=YOUR_CLIENT_SECRET&grant_type=password&redirect_uri=* YOUR_REGISTERED_REDIRECT_URI&username=USER_NAME&pasword=PASSWORD* * @param context* @param app_key* @param app_secret* @param usrname* @param password* @return* @throws WeiboException*/public Oauth2AccessToken getOauth2AccessToken(Context context, String app_key,String app_secret, String usrname, String password) throws WeiboException {Utility.setAuthorization(new Oauth2AccessTokenHeader());WeiboParameters postParams = new WeiboParameters();postParams.add("username", usrname);postParams.add("password", password);postParams.add("client_id", app_key);postParams.add("client_secret", app_secret);postParams.add("grant_type", "password");String rlt = Utility.openUrl(context, Weibo.URL_OAUTH2_ACCESS_TOKEN, "POST", postParams,null);Oauth2AccessToken accessToken = new Oauth2AccessToken(rlt);this.mAccessToken = accessToken;return accessToken;}/*** Share text content or image to weibo .* */public boolean share2weibo(Activity activity, String accessToken, String tokenSecret,String content, String picPath) throws WeiboException {if (TextUtils.isEmpty(accessToken)) {throw new WeiboException("token can not be null!");}// else if (TextUtils.isEmpty(tokenSecret)) {// throw new WeiboException("secret can not be null!");// }if (TextUtils.isEmpty(content) && TextUtils.isEmpty(picPath)) {throw new WeiboException("weibo content can not be null!");}Intent i = new Intent(activity, ShareActivity.class);i.putExtra(ShareActivity.EXTRA_ACCESS_TOKEN, accessToken);i.putExtra(ShareActivity.EXTRA_TOKEN_SECRET, tokenSecret);i.putExtra(ShareActivity.EXTRA_WEIBO_CONTENT, content);i.putExtra(ShareActivity.EXTRA_PIC_URI, picPath);activity.startActivity(i);return true;}private boolean startSingleSignOn(Activity activity, String applicationId,String[] permissions, int activityCode) {return false;}public static boolean flag = false;private void startDialogAuth(Activity activity, String[] permissions) {if(flag == true) return;WeiboParameters params = new WeiboParameters();if (permissions.length > 0) {params.add("scope", TextUtils.join(",", permissions));}CookieSyncManager.createInstance(activity);dialog(activity, params, new WeiboDialogListener() {public void onComplete(Bundle values) {flag = false;// ensure any cookies set by the dialog are savedCookieSyncManager.getInstance().sync();if (null == mAccessToken) {mAccessToken = new Token();}mAccessToken.setToken(values.getString(TOKEN));mAccessToken.setExpiresIn(values.getString(EXPIRES));if (isSessionValid()) {Log.d("Weibo-authorize","Login Success! access_token=" + mAccessToken.getToken() + " expires="+ mAccessToken.getExpiresIn());mAuthDialogListener.onComplete(values);} else {Log.d("Weibo-authorize", "Failed to receive access token");mAuthDialogListener.onWeiboException(new WeiboException("Failed to receive access token."));}}public void onError(DialogError error) {flag = false;Log.d("Weibo-authorize", "Login failed: " + error);mAuthDialogListener.onError(error);}public void onWeiboException(WeiboException error) {flag = false;Log.d("Weibo-authorize", "Login failed: " + error);mAuthDialogListener.onWeiboException(error);}public void onCancel() {flag = false;Log.d("Weibo-authorize", "Login canceled");mAuthDialogListener.onCancel();}});flag = true;}/*** User-Agent Flow* * @param activity* * @param listener*            授权结果监听器*/public void authorize(Activity activity, final WeiboDialogListener listener) {authorize(activity, new String[] {}, DEFAULT_AUTH_ACTIVITY_CODE, listener);}@SuppressWarnings("unused")private void authorize(Activity activity, String[] permissions,final WeiboDialogListener listener) {authorize(activity, permissions, DEFAULT_AUTH_ACTIVITY_CODE, listener);}private void authorize(Activity activity, String[] permissions, int activityCode,final WeiboDialogListener listener) {Utility.setAuthorization(new Oauth2AccessTokenHeader());boolean singleSignOnStarted = false;mAuthDialogListener = listener;// Prefer single sign-on, where available.if (activityCode >= 0) {singleSignOnStarted = startSingleSignOn(activity, APP_KEY, permissions, activityCode);}// Otherwise fall back to traditional dialog.if (!singleSignOnStarted) {startDialogAuth(activity, permissions);}}@SuppressWarnings("unused")private void authorizeCallBack(int requestCode, int resultCode, Intent data) {}public void dialog(Context context, WeiboParameters parameters,final WeiboDialogListener listener) {parameters.add("client_id", APP_KEY);parameters.add("response_type", "token");parameters.add("redirect_uri", mRedirectUrl);parameters.add("display", "mobile");if (isSessionValid()) {parameters.add(TOKEN, mAccessToken.getToken());}String url = URL_OAUTH2_ACCESS_AUTHORIZE + "?" + Utility.encodeUrl(parameters);if (context.checkCallingOrSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) {Utility.showAlert(context, "Error","Application requires permission to access the Internet");} else {new WeiboDialog(this, context, url, listener).show();}}public boolean isSessionValid() {if (mAccessToken != null) {return (!TextUtils.isEmpty(mAccessToken.getToken()) && (mAccessToken.getExpiresIn() == 0 || (System.currentTimeMillis() < mAccessToken.getExpiresIn())));}return false;}
}

你们自己找到authorize()看看呗,怎么弹出那个登录界面的自己研究去,这个方法需要传入一个listener,

就是这个类了AuthDialogListener
/Wwj_sina_weibo/src/com/wwj/sina/weibo/listener/AuthDialogListener.java
授权完成后,回调这个类的onComplete方法,然后就可以获取微博数据了。
package com.wwj.sina.weibo.listener;import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;import com.weibo.net.DialogError;
import com.weibo.net.WeiboDialogListener;
import com.weibo.net.WeiboException;
import com.wwj.sina.weibo.HomeActivity;
import com.wwj.sina.weibo.adapter.WeiboListAdapter;
import com.wwj.sina.weibo.interfaces.Const;
import com.wwj.sina.weibo.library.StorageManager;
import com.wwj.sina.weibo.library.WeiboData;
import com.wwj.sina.weibo.library.WeiboManager;
import com.wwj.sina.weibo.object.User;
import com.wwj.sina.weibo.util.SettingUtil;public class AuthDialogListener implements WeiboDialogListener {private Activity activity;public AuthDialogListener(Activity activity) {super();this.activity = activity;}public void onComplete(Bundle values) {// 保存access_token 和 expires_inString token = values.getString("access_token");String expires_in = values.getString("expires_in");SettingUtil.set(activity, SettingUtil.ACCESS_TOKEN, token);SettingUtil.set(activity, SettingUtil.EXPIRES_IN, expires_in);Toast.makeText(activity, "认证成功", Toast.LENGTH_SHORT).show();HomeActivity homeActivity = (HomeActivity) activity;WeiboListAdapter weiboListAdapter = null;long uid = Long.parseLong(values.getString("uid"));User user = WeiboManager.getUser(activity, uid);if (user != null) {homeActivity.username.setText(user.name);StorageManager.setValue(activity, "uid", uid); // 保存用户UID}weiboListAdapter = WeiboData.loadWeiboListData(activity, Const.HOME,homeActivity.weiboListView);homeActivity.homeData.weiboListAdapter = weiboListAdapter;}public void onWeiboException(WeiboException e) {// 当认证过程中捕获到WeiboException时调用Toast.makeText(activity, "Auth exception:" + e.getMessage(),Toast.LENGTH_LONG).show();}public void onError(DialogError e) {// Oauth2.0认证过程中,当认证对话框中的webView接收数据出现错误时调用此方法Toast.makeText(activity, "Auth error:" + e.getMessage(),Toast.LENGTH_LONG).show();}public void onCancel() {// Oauth2.0认证过程中,如果认证窗口被关闭或认证取消时调用Toast.makeText(activity, "Auth cancel", Toast.LENGTH_LONG).show();}}

执行完这个就已经把数据显示出来了

 WeiboData.loadWeiboListData(activity, Const.HOME,homeActivity.weiboListView);
这里又有一个WeiboData这个类,看看就明白了
package com.wwj.sina.weibo.library;import java.util.List;import android.app.Activity;
import android.widget.ListView;import com.wwj.sina.weibo.adapter.WeiboListAdapter;
import com.wwj.sina.weibo.interfaces.Const;
import com.wwj.sina.weibo.object.Status;
import com.wwj.sina.weibo.util.Tools;public class WeiboData implements Const {public static WeiboListAdapter loadWeiboListData(Activity activity,int type, ListView listView) {return loadWeiboListData(activity, type, listView, null);}public static WeiboListAdapter loadWeiboListData(Activity activity,int type, ListView listView, List<Status> statuses) {WeiboListAdapter adapter = null;if (Tools.hasWeibo(activity)) {switch (type) {case HOME:if (statuses == null)statuses = WeiboManager.getHomeTimeline(activity);adapter = new WeiboListAdapter(activity, statuses, type);break;default:break;}listView.setAdapter(adapter);}return adapter;}
}

可以看到,这里ListView就直接setAdapter了。来看到第二个loadWeiboListData方法,有一个List<Status> statuses参数,这个就是保存微博数据的参数了,是怎么得到的?我们又可以看到一个类WeiboManager里有一个getHomeTimeline的方法,这个就是返回微博数据的方法。

等不及了,进去看看。
/Wwj_sina_weibo/src/com/wwj/sina/weibo/library/WeiboManager.java
package com.wwj.sina.weibo.library;import java.io.File;
import java.util.List;import android.app.Activity;import com.weibo.net.AsyncWeiboRunner;
import com.weibo.net.AsyncWeiboRunner.RequestListener;
import com.weibo.net.Weibo;
import com.weibo.net.WeiboParameters;
import com.wwj.sina.weibo.interfaces.Const;
import com.wwj.sina.weibo.object.Consumer;
import com.wwj.sina.weibo.object.Status;
import com.wwj.sina.weibo.object.User;
import com.wwj.sina.weibo.util.Tools;
import com.wwj.sina.weibo.workqueue.DoneAndProcess;
import com.wwj.sina.weibo.workqueue.WorkQueueStorage;
import com.wwj.sina.weibo.workqueue.task.PullFileTask;/*** 微博管理类,提供方法获取微博数据* * @author Administrator* */
public class WeiboManager implements Const {public static List<Status> getHomeTimeline(Activity activity) {return getHomeTimeline(activity, 0, 0, DEFAULT_STATUS_COUNT);}private static List<Status> getHomeTimeline(Activity activity,long sinceId, long maxId, int count) {return getHomeTimeline(activity, sinceId, maxId, count, false, null);}/*** 获取当前登录用户及其所关注用户的最新微博* * @param activity* @param sinceId* @param maxId* @param count* @param async*            是否同步* @param listener* @return*/@SuppressWarnings("unchecked")public static List<Status> getHomeTimeline(Activity activity,long sinceId, long maxId, int count, boolean async,RequestListener listener) {// 访问接口urlString url = Weibo.SERVER + "statuses/home_timeline.json";// 获取微博对象Weibo weibo = Tools.getWeibo(activity);if (weibo == null || !weibo.isSessionValid()) {return null;}WeiboParameters bundle = new WeiboParameters();bundle.add("source", Consumer.consumerKey);if (sinceId != 0)bundle.add("since_id", String.valueOf(sinceId));if (maxId != 0)bundle.add("max_id", String.valueOf(maxId));if (count != 0)bundle.add("count", String.valueOf(count));List<Status> statuses = null;try {if (!async) {// 请求获取JSON数据String json = weibo.request(activity, url, bundle, "GET",weibo.getAccessToken());statuses = JSONAndObject.convert(Status.class, json, "statuses");} else {AsyncWeiboRunner asyncWeiboRunner = new AsyncWeiboRunner(weibo);asyncWeiboRunner.request(activity, url, bundle, "GET", listener);}} catch (Exception e) {}return statuses;}public static String getImageurl(Activity activity, String url) {return getImageurl(activity, url, null);}public static String getImageurl(Activity activity, String url,DoneAndProcess doneAndProcess) {String result = null;if (url == null || "".equals(url))return result;result = PATH_FILE_CACHE + "/" + url.hashCode();File file = new File(PATH_FILE_CACHE + "/" + url.hashCode());if (file.exists()) {return result;} else {WorkQueueStorage workQueueStorage = Tools.getGlobalObject(activity).getWorkQueueStorage();if (workQueueStorage != null) {if (doneAndProcess == null) {workQueueStorage.addDoneWebFileUrl(url);} else {PullFileTask pullFileTask = new PullFileTask();pullFileTask.doneAndProcess = doneAndProcess;pullFileTask.fileUrl = url;workQueueStorage.addTask(pullFileTask);}}result = null;}return result;}public static boolean hasPicture(Status status) {if (status.thumbnail_pic != null && !"".equals(status.thumbnail_pic))return true;if (status.retweeted_status != null) {if (status.retweeted_status.thumbnail_pic != null&& !"".equals(status.retweeted_status.thumbnail_pic)) {return true;}}return false;}public static User getUser(Activity activity, long uid) {return getUser(activity, uid, null, false, null);}public static User getUser(Activity activity, String screen_name) {return getUser(activity, 0, screen_name, false, null);}public static User getUser(Activity activity, long uid, String screen_name,boolean async, RequestListener listener) {String url = Weibo.SERVER + "users/show.json";Weibo weibo = Tools.getWeibo(activity);if (weibo == null || !weibo.isSessionValid()) {return null;}User user = null;WeiboParameters bundle = new WeiboParameters();bundle.add("source", Consumer.consumerKey);if (uid > 0) {bundle.add("uid", String.valueOf(uid));} else if (screen_name != null) {bundle.add("screen_name", screen_name);} else {return user;}try {if (!async) {String json = weibo.request(activity, url, bundle, "GET",weibo.getAccessToken());user = new User();JSONAndObject.convertSingleObject((Object) user, json);} else {AsyncWeiboRunner asyncWeiboRunner = new AsyncWeiboRunner(weibo);asyncWeiboRunner.request(activity, url, bundle, "GET", listener);}} catch (Exception e) {}return user;}}

只看 getHomeTimeline()这个方法,一直追踪,很快就可以知道这个微博数据是怎么得到的了。

  public static List<Status> getHomeTimeline(Activity activity,long sinceId, long maxId, int count, boolean async,RequestListener listener) {// 访问接口urlString url = Weibo.SERVER + "statuses/home_timeline.json";// 获取微博对象Weibo weibo = Tools.getWeibo(activity);if (weibo == null || !weibo.isSessionValid()) {return null;}WeiboParameters bundle = new WeiboParameters();bundle.add("source", Consumer.consumerKey);if (sinceId != 0)bundle.add("since_id", String.valueOf(sinceId));if (maxId != 0)bundle.add("max_id", String.valueOf(maxId));if (count != 0)bundle.add("count", String.valueOf(count));List<Status> statuses = null;try {if (!async) {// 请求获取JSON数据String json = weibo.request(activity, url, bundle, "GET",weibo.getAccessToken());statuses = JSONAndObject.convert(Status.class, json, "statuses");} else {AsyncWeiboRunner asyncWeiboRunner = new AsyncWeiboRunner(weibo);asyncWeiboRunner.request(activity, url, bundle, "GET", listener);}} catch (Exception e) {}return statuses;}

就是这个方法了,通过调用weibo对象的request()方法,返回Json字符串,通过解析得到的JSON字符串得到statuses数组。这里需要进行的转换,全靠JSONAndObject这个类

package com.wwj.sina.weibo.library;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;import org.json.JSONArray;
import org.json.JSONObject;import android.util.Log;import com.wwj.sina.weibo.interfaces.WeiboObject;public class JSONAndObject {/*** 将一个对象转换为JSON格式的字符串,只转换public类型的变量* * @param obj* @return*/public static String convertSingleObjectToJson(Object obj) {String json = null;if (obj == null) {return json;}Field[] fields = obj.getClass().getFields();json = "{";// 开始转换每一个public类型的变量for (int i = 0; i < fields.length; i++) {try {Field field = fields[i];if (field.getType() == String.class) {// 属性值为null, 用空字符串取代String temp = ((field.get(obj) == null) ? "" : String.valueOf(field.get(obj)));// 处理字符串中的双引号// JSON字符串中不能直接使用双引号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字符串json += "\"" + field.getName() + "\":"+ convertSingleObjectToJson(fieldObject);} else {continue;}}if (i < fields.length - 1) {json += ",";}} catch (Exception e) {}}json += "}";return json;}/*** 将obj转换为JSON字符串,该字符串必须是一个对象 其中obj必须是一个List,而且JSON字符串必须包含一个propertyName* 制定的属性,属性值是JSON数组,该数组与obj指定的List对应 类似于hometimeline.json返回的JSON字符串的逆过程* * @param obj* @param propertyName* @return*/public static String covertObjectToJson(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;}/*** 将json字符串转换为List* * @param c* @param json* @param propertyName*            这个参数用来制定属性的对象,而且这个属性值必须是一个数组* @return*/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;}/*** 使用该方法需要先创建一个object,传入第一个参数 将JSON格式的数据转换为一个对象 json参数的值必须是一个JSON格式的对象,不能是数组* * @param obj* @param json* @return*/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 {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) {}}}} catch (Exception e) {}return obj;}
}

到这里基本上把核心的代码贴完了,不过这里还有有一个类确实比较重要的, Utility这个类涉及到的都是Http通信,我们直接拿来用就可以的,不需要我们自己去写。要我说能完全自主开发出新浪微博客户端那确实很牛叉了,光是Http通信这一块,如果对http不熟悉,根本就不知道怎么来搞。

以上的代码并不是全部,但这些代码已经很有用了,对自己理解微博客户端的实现有了很大的启发。可能有些地方没能面面俱到,请见谅。下一篇博客可能会是关于发布一条微博的实现,敬请期待吧。

新浪微博客户端开发之授权登录+获取微博列表相关推荐

  1. iOS新浪微博客户端开发(4)——自定义微博Cell的实现

    首先看一下效果图(不带转发微博和带转发微博):   一.微博Cell布局分析 微博Cell的详细布局如下图所示,其主要控件有:头像.昵称.微博时间.来源.微博正文.如果有被转发微博,还包括被转发微博的 ...

  2. java 微博sdk_Java新浪微博客户端开发第一步

    Java新浪微博客户端开发***步 一.获得开发应用所需的"通行证"App Key和App Secret 首先必须要有新浪的账号.再打开http://open.weibo.com/ ...

  3. java 微博客户端,Java新浪微博客户端开发第二步

    上一篇:Java新浪微博客户端开发***步中有下图,这个access_token就是接下来要用到的. 关于access_token的有效时间: 更多关于access_token与Oauth2,请参看: ...

  4. 微博java客户端开发教程_Java新浪微博客户端开发第四步

    这一步是对之前进行较大的改动.增加的类也比较多.包结构如下: 0.Main:主函数入口 1.MainDialog:主界面 2.WeiboPanel:StatusPanel及CommentPanel的父 ...

  5. 新浪微博客户端开发--显示单条微博

    新浪微博客户端开发--显示单条微博 2013年11月23日 新浪微博客户端开发记录 前面两篇博客介绍了如何调用新浪微博API发布微博,任务队列的实现流程,自定义微博adapter的实现,异步更新UI的 ...

  6. Java新浪微博客户端开发第二步

    上一篇:Java新浪微博客户端开发第一步中有下图,这个access_token就是接下来要用到的. 关于access_token的有效时间: 授权级别 测试 普通 中级 高级 合作 授权有效期 1天 ...

  7. android 新浪微博客户端开发

    [转载]android开发新浪微博客户端 完整攻略 分类: android 2011-04-23 22:45 3193人阅读 评论(4) 收藏 举报 开始接触学习android已经有3个礼拜了,一直都 ...

  8. 新浪微博客户端开发开篇

    新浪微博客户端开发开篇 2013年7月28日新浪微博客户端启动啦 项目启动原因 其实想开发新浪微博客户端已经是很早之前的一件事情了,之前是因为开发<简.美音乐播放器>所以就没有启动它,开发 ...

  9. Java新浪微博客户端开发第四步

    这一步是对之前进行较大的改动.增加的类也比较多.包结构如下: 0.Main:主函数入口 1.MainDialog:主界面 2.WeiboPanel:StatusPanel及CommentPanel的父 ...

最新文章

  1. 这周末,清华迎来了最小的学生和最牛的老师,Science都点赞的那种
  2. 按次计费接口的简单实现思路
  3. 云计算机内存不足怎么办,网易云音乐提示内存不足,电脑提示内存不足-
  4. 前端学习(2906):Vite 解决了 Webpack 哪些问题
  5. JAVA输出菱形并使用绝对值,案例用绝对值的方法打印出菱形
  6. 仿生软体机器人就业咋样_工业之美|静若灯笼、动如鱿鱼,仿生届又出黑科技产品...
  7. 机器人学中的状态估计 中文版_《机器人学中的状态估计》-05偏差,匹配和外点...
  8. POJ1664 放苹果【递推+记忆化递归】
  9. python输入一个字母标识符_Python基础入门语法和变量类型(一)
  10. java实时解析mysql日志,利用maxwell 组件实时监听Mysql的Binlog日志,并且把解析的json格式数据发送到kafka窗口供实时消费...
  11. 浅谈React Event实现原理
  12. 联想计算机启天m6900价格,联想启天m6900内存及基本【参数介绍】
  13. MySQL 安全审计、容灾备份、数据恢复
  14. w10怎么自动锁定计算机,教你如何设置Win10系统自动锁屏?
  15. C#两种方法输出1~100间的质数(素数)
  16. 关于M0、M1、M2货币最近所学
  17. android monkey,Android Monkey搭建 你不用了解的
  18. Matlab 仿真——直流电机速度控制(1)直流电机建模
  19. WARN Error while fetching metadata with correlation id 13 : {test=LEADER_NOT_AVAILABLE}
  20. 字典遍历时不能修改字典元素

热门文章

  1. 计算机实训安全教育,暑期实践 | 计算机学院举行暑期社会实践动员暨安全教育大会...
  2. Excel替换文本内容的3种操作方法比较
  3. 量化策略:驾驭交易之轴心点研究(一)
  4. mPEG2000-PHOS,磷酸盐修饰的单功能线性PEG,mPEG2000-Phosphate
  5. 期货交易应该如何界定交易中的时间周期?
  6. 甘肃敦煌戈壁滩108KM 挑战的感想
  7. vim简单用法-配合pycharm
  8. 针对中亚地区政府部门的攻击:通过Office漏洞传播新型Hawkball后门
  9. 8.0时代的微信营销怎么玩,才能挖掘用户最大价值
  10. 网页设计HTML如何制作选项卡,jQuery制作网页版选项卡