虎扑体育客户端zen源码学习笔记
2015-03-05 14:21
92 查看
SOURCE
==================ZenLogin登录
ZenLoginActivity
动态注册Boardcastprotected void onResume() { super.onResume(); try { IntentFilter filter = new IntentFilter(); filter.addAction(ZenLoginModel.ZEN_LOGIN_FINISHED); filter.addAction(ZenLoginModel.ZEN_LOGIN_FAILED); registerReceiver(mBoradcastReceiver, filter); } catch (Exception e) { e.printStackTrace(); } }
broadcastReceiver
private BroadcastReceiver mBoradcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { mLoading.hide(); String action = (String) intent.getAction(); if (action.equals(ZenLoginModel.ZEN_LOGIN_FINISHED)) { ZenMyBoardsModel boardModel = ZenMyBoardsModel.getInstance(); boardModel.load(); //加载板块 ZenNotificationModel.getInstance().load(); AppMsg appMsg = AppMsg.makeText(ZenLoginActivity.this, "登录成功", AppMsg.STYLE_INFO); appMsg.show(); Handler handler = new Handler(getMainLooper()); handler.postDelayed(new Runnable() { @Override public void run() { finish(); } }, 2000); //... } }
注意,需要在onpause/onDestory中unregister:
unregisterReceiver(mBoradcastReceiver);
在ZenLoginModel处理完登录动作后
mContext.sendBroadcast(new Intent(ZEN_LOGIN_FINISHED));
broadcastReceiver 异步退出activity. broadcastReceiver / activity 不在同一个线程吗.
登录最终目的就是得到登录账号的cookie.代码中保存为ZenUtils.gettoken().如果已经登录过,会保存在SharedPreferences中.否则从web获取,代码:
public void OnResponse(String response) {
try {
if (response != null) {
System.out.println("response: " + response);
JSONObject json = new JSONObject(response);
JSONObject result = json.getJSONObject("result");
JSONObject user = result.getJSONObject("user");
userInfo.userName = user.getString("username");
userInfo.uid = user.getString("uid");
userInfo.token = user.getString("token");
isLogedin = true;
save();
fetchMSGToken();
mContext.sendBroadcast(new Intent(ZEN_LOGIN_FINISHED)); }
return;
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("zen login failed!");
mContext.sendBroadcast(new Intent(ZEN_LOGIN_FAILED));
}
=============
以ZenMyBoardsModel为例分析model代码
此model使用get方法获得板块数据.示例url
example: http://mobileapi.hupu.com/1/1.1.1/bbs/getusercollectedboards?token=15538936|aWxvdHVv|1f4b|198a84db2223ce42d77a62d67a64517a|2223ce42d77a62d6
第一次接触web客户端,从域名可以看出原来网站为了拓展都预留一个api用于查询数据库?
连接,encoding(to utf-8),发送,getResponse,decoding(from utf-8),得到:
{“islogin”:1,”result”:[{“fid”:”151”,”cid”:”198”,”name”:”国家队-世界杯”},{“fid”:”63”,”cid”:”234”,”name”:”同城约战”},{“fid”:”34”,”cid”:”174”,”name”:”步行街”},{“fid”:”130”,”cid”:”1”,”name”:”篮球场”},{“fid”:”1048”,”cid”:”1”,”name”:”湿乎乎的话题”},{“fid”:”24”,”cid”:”232”,”name”:”中国篮球”},{“fid”:”2552”,”cid”:”1”,”name”:”深篮讨论区”},{“fid”:”43”,”cid”:”1”,”name”:”篮球图片”},{“fid”:”37”,”cid”:”1”,”name”:”NBA选秀-NCAA”},{“fid”:”1028”,”cid”:”1”,”name”:”NBA2K专区”},{“fid”:”3864”,”cid”:”57”,”name”:”极限运动X-GAMES”},{“fid”:”82”,”cid”:”1”,”name”:”凯尔特人区”},{“fid”:”3312”,”cid”:”82”,”name”:”凯尔特人生活区”},{“fid”:”85”,”cid”:”1”,”name”:”骑士专区”},{“fid”:”105”,”cid”:”1”,”name”:”马刺专区”},{“fid”:”3316”,”cid”:”105”,”name”:”马刺生活区”},{“fid”:”127”,”cid”:”1”,”name”:”快船专区”}]}
另外返回数据json使用utf-8编码.直接用在线解码会不成功.
如:
u80a5\u80a0\u714e\u86cb 肥肠煎蛋
应该使用 肥肠煎蛋 refer to link
Fiddler header :
User-Agent: Fiddler
Content-Type: application/json; charset=utf-8
Host: mobileapi.hupu.com
设计思路:
private ZenOnResponseListener mOnResponseListener :
ZenJSONUtil.WriteJSONToFile(ZenJSONUtil.ZEN_MY_BOARDS_JSON, jsonString); ZenJSONUtil.reloadMyBoards();
版块主题列表列表加载
ZenMenuFragment.javamBoards.setOnChildClickListener(new OnChildClickListener() { public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { //切换到板块主题列表 switchContent(board.get("fid"), board.get("name")); ZenThreadsFragment fragment = new ZenThreadsFragment(); //... ac.switchContent(fragment, name); } }
以下的调用不一定在同一个函数中,只按照timeline排序. 缩进表示层次关系(可能跨多层)
ZenThreasFragment.java ZenThreadsModel.java
actureListView.setOnItemClickListener(...); //获取主题列表数据 model init mModel = new ZenThreadsModel(mContext, fid); mModel.refresh(); load(mPage);//ZenThreadsModel.java String url = String.format(ZenThreadsModel.ZenThreadsURL, mFid, page); public void OnResponse(String response) Intent intent = new Intent(ZenThreadsModel.DidFailedLoad); mContext.sendBroadcast(intent); public void onReceive(Context context, Intent intent) //BroadcastReceiver mBroadcastReceiver mThreadsAdapter.array = mModel.threads; //Adapter更新listView mThreadsAdapter.notifyDataSetChanged(); mList.scrollTo(0, 0);
关于Adapter和listView的初始化见
ZenThreadsFragment.onActivityCreated
帖子内容加载
重点actureListView.setOnItemClickListener(new OnItemClickListener() Intent intent = new Intent(this, ZenContentActivity.class);
首先切换到另一个Activity了,具体网络通信模型和前面类似.
ZenReplyModel.java
下面使用这个 thread demo url 做例子:
http://mobileapi.hupu.com/1/1.1.1/bbs/getthreaddata?type=2&tid=12063126&boardpw=&token=15538936|aWxvdHVv|1f4b|198a84db2223ce42d77a62d67a64517a|2223ce42d77a62d6
return data:
{“islogin”:1,”result”:{“tid”:”12063126”,”fid”:”34”,”username”:”肥肠煎蛋”,”uid”:”20676879”,”subject”:”在火车上收一张图,发现自己的世界灰暗了”,”postdate”:”1425009205”,”lastpost”:”1425064033”,”lastposter”:”李时念”,”replies”:”693”,”locked”:”0”,”digest”:”0”,”lights”:”8”,”recs”:”15”,”content”:”
众jr看这张图什么颜色?我看的是淡紫加有点奇怪的绿色,但同学和下面的英文都告诉我,他们看得是白金或者蓝黑。
<%/center>
来自 Zen For Android <%/a>”,”boardname”:”步行街”,”boardurl”:”“,”url”:”“}}
此时webView只有帖子主题没回复,处理数据函数
private void parserThreadDataResponse(String response)完成了这些工作:
返回json保存在threadData对象
发送广播Intent successIntent = new Intent(ZenReplyModel.ZenThreadDataDidFinishedLoad);
广播后最终在private void onThreadDataFinished(ZenThreadData data)处理主题数据后续.加载完主题数据,添加到webview后,开始加载回复数据,主要内容:
thread.loadUrl("javascript:clearPost('')"); //清除页面内容 String .format("javascript:addMainPost('%s', '%s', '%s', '%s', '%s', '%s');", data.subject, postInfo, data.postdate, data.author, "楼主", data.content); //load it .. mModel.loadReplies(mPage); //加载回复数据
加载了两条javascrpit语句.这些语句都在 hupu_post.html 模型中.
loadRelies 重复了上面类似的过程.加载json数据,调用javaScript语句,添加网页内容.完成后发送广播:
Intent successIntent = new Intent(ZenReplyModel.ZenRepliesDidFinishedLoad);
类似的,接收广播后处理下一步渲染:
private void onThreadRepliesFinished()
这一步开始收尾处理view:
pullToRefreshWebView.onRefreshComplete(); //webview complete loading. mLoading.hide();
and finished reply post handling over javascript.
e.g.
thread.loadUrl("javascript:addLightTitle('热门跟帖', 'true')"); //... ZenThreadReply reply = model.lightReplies.get(i); String js = String .format("javascript:addLightPost('%s', '%s', '%s', '%s', '%d', '%s')", reply.author, "", reply.light, reply.content, i, reply.pid); thread.loadUrl(js);
总结
客户端是通过
thread.loadDataWithBaseURL(“file:///android_asset/”, html, “text/html”,
“utf-8”, null);
加载模板.再获得API json数据,结合javascript脚本生成帖子的.可以比较m.hupu.com 和zen生成的页面并不一样.如图:
借助端点工具查看各阶段的webView.
至于这个模板(assert/hupu_post.html)从何而来,我就不得而知了.
Fragment的应用
android Fragments详解四:管理fragment下面仍然是ZenContentActivity相关的代码.
ZenMenuBar的实现(8个按键动作)
Menu 封装了所有使用这个MenuBar的按钮操作,使用了Adapter设计模式的,其中按钮相应函数:public void OnMenuItemClick(int type) { switch (type) { case ZenMenuBar.MENU_LIGHT: light(); break; case ZenMenuBar.MENU_REPLY: reply(); break; case ZenMenuBar.MENU_COPY: copy(); break; case ZenMenuBar.MENU_PM: pm(); break; case ZenMenuBar.MENU_REFRESH: refresh(); break; case ZenMenuBar.MENU_COMMENT: comment(); break; case ZenMenuBar.MENU_RECOMMEND: recommend(); break; case ZenMenuBar.MENU_ARCHIVE: archive(); break; } }
light,refresh操作和前面类似,加载这个文件:
String url = String.format(Locale.getDefault(), ZEN_LIGHT_URL, mFid, mTid, pid, URLEncoder.encode(token, "utf-8"));
然后处理完response json后在 mBroadcastReceiver 调用javascript渲染:
mLoading.hide(); String lights = intent.getStringExtra("light"); String js = String.format(Locale.getDefault(), "javascript:lightSuccess('%s', '%s', %d)", lights, mReplyData.pid, mArea); thread.loadUrl(js); AppMsg appMsg = AppMsg.makeText(ZenContentActivity.this, "点亮成功", AppMsg.STYLE_INFO); appMsg.show();
recommend实现
推荐使用和web端一样的链接,但是他们的cookie是通用的:
private static final String ZEN_RECOMMEND_URL = "http://bbs.hupu.com/indexinfo/buddys.php"; String tokenEncoded = URLEncoder.encode(token, "utf-8"); mConnection = new ZenURLConnection(ZEN_RECOMMEND_URL); mConnection.setOnResponseListener(mRecommendListener); mConnection.setHttpMethod("POST"); mConnection.addRequestHeader("Content-Type", "application/x-www-form-urlencoded ; charset=UTF-8"); mConnection.addRequestHeader("Cookie", "u=" + tokenEncoded + ";"); mConnection.addRequestHeader("X-Requested-With", "XMLHttpRequest"); mConnection.addRequestHeader("Referer", "http://bbs.hupu.com/" + mTid + ".html"); String httpBody = "fid=" + mFid + "&act=rc" + "&cid=" + mTid + "&title=" + URLEncoder.encode(title, "utf-8") + "&rmmsg=" + URLEncoder.encode(content, "utf-8") + "&type=1"; mConnection.setHttpBody(httpBody); mConnection.startAsychronous();
Comment/Reply实现
Comment实现比上面要复杂些.
上传图片:
String token = ZenUtils.getToken(); if (token != null) { String boundary = "----pluploadboundary" + ZenUtils.timestamp(); mUploadConnection = new ZenURLConnection(ZEN_UPLOAD_URL); mUploadConnection.setHttpMethod("POST"); mUploadConnection.addRequestHeader("Cookie", "u=" + URLEncoder.encode(token, "utf-8")); mUploadConnection.addRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary); InputStream body = boundary(image, boundary, isGif); if (body != null) { mUploadConnection.setHttpInputStream(body); String response = mUploadConnection.startSychronous(); if (response != null) { System.out.println("upload: " + response); JSONObject json = new JSONObject(response); if (json.has("pic")) { String url = json.getString("pic"); return url; } } } }
注意boundary 是内容分割符.客户端往服务器上传内容需要靠分割符识别不同类型多媒体的request.参考
rfc2616 session 19.2.请求主体在
boundary中构造.请求成功后返回这样的一个key {pic:url} 的json数据reponse.
然后来看完整的comment过程是如何调用上面的上传函数:
ZenAssetsModel model = ZenAssetsModel.getInstance(); ArrayList<Map<String, Object>> images = model.getAssets(); ArrayList<String> urls = new ArrayList<String>(); ZenPostModel postModel = new ZenPostModel(mFid); for (Map<String, Object> image : images) { String type = (String) image.get("type"); InputStream obj = (InputStream) image.get("input"); String url = null; if (type.equals("jpg")) { url = postModel.uploadImage(obj, false); } else { url = postModel.uploadImage(obj, true); } if (url != null) { urls.add(url); } }
最后上传评论:
if (pid != null && !pid.equals("")) { String response = oldcomment(content, pid, urls); return response; } else { String response = newcomment(content, pid, title, urls);
newcomment 提交的header在前面都有用到过.cookie,refer,User-Agent.body部分含有复杂的信息构建了一段html段落.包括引用,作者,回复楼层,和”来自Zen”尾巴等等.部分代码:
contentBuf.append(content); if (urls != null) { for (String url : urls) { contentBuf.append("<br><br><img src=\"" + url + "\"><br><br>"); } } contentBuf.append(ZEN_TAIL); //ZENTAIL 为content添加尾巴 private static final String ZEN_TAIL = "<br><small class=\"f666\"><a style=\"color:#666\" href=\"http://rogerqian.github.com/zen_1.2.1.apk\" target=\"_blank\">来自 Zen For Android</a></small>";
END
就写到这了.时间轴上这是最后停笔的地方.我只是想接触下http协议.老早之前就大概明白一个互联网产品客户端是怎么写出来的了.后面的坑也不填了~反正也没人看.寒假本来只是想看两个客户端源码,接触了解下http,web服务器相关知识.作为非计算机专业科班出身的学生的一个知识补充.还买了http翻了一半.忽然惊醒还有几个月就要找实习了,才觉得补充过头了,竟然耗了整整一个寒假的时间.赶紧打住回到底层代码~为那个实习工作努力去!
关于javaScript调用ZenBridge函数
refer to :
Android addJavaScriptInterface
第三方插件
SherlockActivity
只需要添加以下代码public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: toggle(); break; case R.id.zen_new_post:
即可实现如下效果:
zen.id.new_post is one item of a menu resource.
PullToRefreshListView
略Zen客户端中 MVC模型的应用:
略设计思路 | 相关知识
碎碎念
基础回顾–抽象类概念public abstract class BaseExpandableListAdapter implements ExpandableListAdapter, HeterogeneousExpandableList
implements 无需实现
ArrayList 也能这样遍历
public ArrayList<ZenTopicData> topics; for (ZenTopicData topic : topics)
//ArrayList 也能这样遍历
移除一个inflate 动态加载的View
/** * 移除一个inflate 动态加载的View * @param view */ private void removeFromSuperView(View view) { ViewGroup superView = (ViewGroup) view.getParent(); if (superView != null) { superView.removeView(view); } }
Instantiates a layout XML file into its corresponding
/* * Instantiates a layout XML file into its corresponding {@link android.view.View} * objects. It is never used directly. Instead, use * {@link android.app.Activity#getLayoutInflater()} or * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance * that is already hooked up to the current context and correctly configured * for the device you are running on. For example: */ LayoutInflater inflater = (LayoutInflater)context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
动态生成View多使用FrameLayout
inflate.inflate(R.layout.zen_menu_bar_more, null);
xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/zen_menu_container" android:layout_width="match_parent" android:layout_height="match_parent" >
另一种oclick监听方法
<ImageButton android:contentDescription="@null" android:id="@+id/zen_back_btn" android:onClick="OnBottomItemClick" />
java:
public void OnBottomItemClick(View v) { switch (v.getId()) { case R.id.zen_back_btn: finish(); break; case R.id.zen_prev_btn: hint(); mLoading.show("正在加载..."); mModel.prev(); break; // case etc.. }
快速写入应用私有文件
OutputStream output = context.openFileOutput(fileName, Context.MODE_PRIVATE);
other
这个开源版本中,有一些广告插件的代码.但是release中看不到广告.所以不做研究导入工程
需要导入所有 proprerity-android-library-工程源码并打开
布局原理
挑选一些布局方案分析一下.首先是主题列表中独立的自定义View
效果图是这样的:
回复数量那里会有一个伪随机的颜色变化,实现多彩效果.
其实是这样弄出来的:
<FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginRight="10dp" android:layout_marginTop="5dp" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@null" android:src="@drawable/comment_box" /> <TextView android:id="@+id/zen_thread_replies" android:layout_width="24dp" android:layout_height="16dp" android:layout_gravity="top" android:background="@drawable/mid_green" android:gravity="center" android:textColor="#fff" android:textSize="11sp" /> </FrameLayout>
ZenThreadsAdapter.java:
public View getView(int position, View convertView, ViewGroup parent) { replies.setBackgroundResource(color); //... } private int colorForReplies(int replyNum) { int index = replyNum % colors.length; return colors[index]; } private int colors [] = { R.drawable.orange, R.drawable.navy, R.drawable.real_blue, R.drawable.purple, R.drawable.green };
原来是覆盖一个大小合适的textview在图片上面,用textView底色变色
pulltorefresh控件的源码分析及使用
[TODO]编码问题
win7下默认unicode编码.返回的数据如下(example)\u672a\u77e5\u9519\u8bef解决:使用fidder设置header
相关文章推荐
- 开源中国android客户端源码分析-学习笔记1
- 开源中国android客户端源码分析-学习笔记2
- mysql 源码学习笔记:客户端连接处理流程
- Asp.Net Ajax 学习笔记23 利用Microsoft AJAX Library开发客户端组件(上)
- Asp.Net Ajax 学习笔记25 利用Microsoft AJAX Library开发客户端组件(下)
- SmartClient(智能客户端)学习笔记之——Smart Client基本学习资源
- (转)SmartClient(智能客户端)学习笔记之——Microsoft Updater Application Block ApplicationUpdater assembly设计
- 学习altas笔记[客户端JS和Altas环境初始化关系和DataTable返回数据的客户端处理]
- LDD3源码学习笔记之scull_pipe转
- 学习altas笔记[客户端JS和Altas环境初始化关系和DataTable返回数据的客户端处理]
- Asp.Net Ajax 学习笔记24 利用Microsoft AJAX Library开发客户端组件(中)
- 扬扬的J2EE学习笔记(二)J2EE的客户端/服务器
- 铁血联盟2源码学习笔记--Makefile边看边学3
- Smart Client学习笔记(7) 使用多线程创建高响应智能客户端应用程序
- MS Ajax 客户端编程 学习笔记 (1)
- 学习笔记(1)在 ASP.NET 网页中不经过回发而实现客户端回调
- 学习altas笔记[客户端JS和Altas环境初始化关系和DataTable返回数据的客户端处理]
- JAVA虚拟机源码学习笔记之二
- .Net学习笔记 - 客户端访问服务器端的基本使用
- Asp.Net Ajax 学习笔记12 基于Microsoft AJAX Library扩展客户端组件