SOURCE
==================
ZenLogin登錄
ZenLoginActivity
動態註冊Boardcast
protected 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.java
mBoards.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