動機:
首先自己不是做應用的,一直以來從事的都是網絡游戲客戶端的開發。一是想了解一下應用開發的流程與技術點,二是看到園子里有人做的app,賺錢了,看的有點手癢癢,呵呵,三是Android應用開發,工資待遇比游戲開發要好,自己也好好學習下,為自己找條后路吧。
說明:
因為是從零做起,所以好多問題都是第一次遇到。主要寫一些碰到的一些設計和技術難題,主要以客戶端為主,因為是首次接觸。可能對高手來說,是小菜了。 但都有個過程吧。慢慢也就懂了。希望園子里面的朋友多多指教,多拍磚,找到最優的方案。希望自己能夠堅持寫完,自己的app能夠早日上架。
主要內容:
- 服務端客戶端框架的設計及通訊方式
- 網絡請求數據,動態加載listview中的數據項
- 服務端客戶端關於會話session的狀態保存
- ActivityGroup 對子Activity的管理 還有 子Activity之間的切換
- 內存管理方式的選擇
- ListView 中ArrayAdapter中刪除Item
- 持續更新中……目前已完成60%
--------------------------------------------------------------------------------------------
--------------------------服務端客戶端框架的設計及通訊方式------------------
--------------------------------------------------------------------------------------------
服務端:
服務端使用MVC的框架,dao,manager,servlet 三層。感覺不像嚴格意義上的MVC,有個型就好了。能夠分層次,處理不同水平方向上的東西。有一個handler處理接口。根據請求使用不同的handler來進行處理。相當於一個算法簇。用了策略模式。 Servlet分兩種,一種是處理服務端后台的管理,一種是處理客戶端請求的。 客戶端請求的servlet只需要一個就可以了。根據不同的請求,分別使用不同的handler來進行處理。
客戶端:
客戶端由activity,adapter,bean,handler,net,util這幾部分進行組成。 Adapter相當於activity和請求數據之間的橋梁,使用了適配器模式,handler和服務端的功能差不多。在這一片文章中也分析過。記一電子商城android客戶端初探解析Net包為網絡包,網絡分為兩部分,一部分是當進入一個界面時,頁面的請求,頁面的異步加載,第二部分是當有圖片的時候,圖片的異步加載。
--------------------------------------------------------------------------------------------
--------------------------網絡請求數據,動態加載listview中的數據項-------
--------------------------------------------------------------------------------------------
關於這個網上的做法有很多。但都是本地數據定死的。相應都是及時的。當有網絡請求數據的時候,就需要多處理一些東西。例如網絡請求期間,onScroll方法可能會持續的執行等等。
這里需要幾個變量參數來進行協作。
private int curPage;
private static int maxCount;
private boolean havaFooterView;//是否有腳view
在剛開始的時候,需要
// 添加到腳頁顯示
list.addFooterView(loadingLayout);
havaFooterView = true;
然后系統會執行onScroll方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
{
//當加載的時候,不在請求網絡
if (isLoading())
{
return;
}
if (firstVisibleItem + visibleItemCount == totalItemCount
&& havaFooterView)
{
Log.i("test", "Scroll>>>first: " + firstVisibleItem + ", visible: "
+ visibleItemCount + ", total: " + totalItemCount);
lastItem = firstVisibleItem + visibleItemCount - 1;
Log.i("test", "Scroll>>>lastItem:" + lastItem);
Log.i("test", "Scroll>>>adapter count:" + adapter.getCount());
requestNet();
}
}
執行查詢的條件是,不在加載中,並且拉到了底部,而且還有腳view的情況下。一定要切記了。條件判定不充分,可能會有多余的報文產生。
那么怎么解析數據呢?看下面的代碼
@Override
protected void processNetData(Object obj)
{
HashMap data = (HashMap)obj;
maxCount = Integer.parseInt(data.get("maxCount").toString());
ArrayList<CaipuDetail> tempCaipuDetails = (ArrayList) data.get("caipuList");
caipuDetails.addAll(tempCaipuDetails);
//如果適配器里面的數據數少於最大數就增加適配器數量,
//當這一次增加的數量是最大數量的時候,就刪除腳view
//同時設置havaFooterView為假
adapter.setCount(caipuDetails.size());
if (adapter.getCount() == maxCount)
{
list.removeFooterView(loadingLayout);
havaFooterView = false;
}
//重新刷新Listview的adapter里面數據
adapter.notifyDataSetChanged();
setLoading(false);
}
注意什么時候去除腳view,設置havaFooterView為false,讓進度條的狀態為false。
--------------------------------------------------------------------------------------------
--------------------------服務端客戶端關於會話session的狀態保存-----------
--------------------------------------------------------------------------------------------
一般的做法是這樣的:當客戶端登錄的時候,服務端生成一個session回話,並保存登錄Account賬號信息,並且返回一個sessionId給客戶端,客戶端解析后保存這個sessionId,並且每次發送報文的時候,附帶着sessionId在頭域中。這樣就可以保持回話狀態了。如果一段時間不請求服務器,回話可能會失效。這時候就需要重新登陸了。
我遇到的問題是,服務端的我是用session是否為null來判斷,回話是否過期的。但是會有這種情況,當session不為null,但是里面的值取不到,為空值,也就是上面保存的Account賬號信息,有一種解釋是session保存在散列表中,回話過期,應該是session中的屬性發生變化,但是並不為空。所以,我使用一個折中的辦法來判斷。就是也取得賬號信息,如果為空,就判斷過期。具體做法如下:
服務端:
public JSONObject prepareData(HttpServletRequest request) {
HttpSession session = request.getSession(false);
Object accountObj =null;
if(session!=null){
accountObj= session.getAttribute("account");
}
if(null == session || null == accountObj){
//這里就轉向為登錄提示
return formatJSONObject();
}
Account account =(Account)accountObj;
System.out.println("最大不活動時間:"+session.getMaxInactiveInterval());
System.out.println("sessionId:"+session.getId());
int page = Integer.parseInt(request.getParameter("page"));
Favorites t = new Favorites();
t.setAccountId(account.getId());
List<Favorites> favorites = fm.selectByAccountId(t,page);
int maxCount = favorites.size();
return formatJSONObject(favorites,maxCount);
}
客戶端:
httpGet.setHeader("Cookie", "JSESSIONID=" + Logic.instance().getSessionId());
--------------------------------------------------------------------------------------------
--- ActivityGroup 對子Activity的管理 還有 子Activity之間的切換-----
--------------------------------------------------------------------------------------------
ActivityGroup這個控件經常被使用。 如果學過html的同學,應該知道iframe框架,我覺得他們兩個挺像的。個人理解。呵呵。 其實剛開始我對這個ActivityGroup是很陌生的,都沒用過,當時反編譯了一個不錯的應用的xml。發覺里面使用到了這個控件。 覺得不錯。不錯的原因是 如果不用這個控件,我們的做法一般是寫一個baseActivity,在它中進行處理,想想就覺得不合理吧。 基類中點擊某一個搜索,首頁或收藏的選項,在基類中調用。 覺得這樣的設計很不好。覺得違反了設計的原則。 這個控件就解決了這個問題。 呵呵。好像扯遠了。。。
ActivityGroup對子Activity有啟動,還可以切換自己的內容view來包含子activity中的view。但是發現子Activity不能finish。如果執行了finish就推出應用了。ActivityGroup執行了finish就是推出應用。 不知道什么原因。 還請高手指教。 子Activity之間的切換,也分兩種 ,一種是啟動原有的activity,一種是刪除原有的,啟動一個新的。
可以使用下面通用的方法來進行啟動子Activity,代碼如下:
/**
* 加載子界面
* @param id ActivityId
* @param class1 類
*/
public void loadSubActivity(String id, Class class1,Bundle bundle,int flags)
{
contextView.removeAllViews();
Class subActivityClass = class1;
if (subActivityClass != null)
{
Intent in = new Intent(this,
subActivityClass);
//FLAG_ACTIVITY_SINGLE_TOP 如果沒有,添加一個。如果有顯示以前的
//Intent.FLAG_ACTIVITY_CLEAR_TOP 如果沒有添加一個,如果有刪除以前的,重新添加一個
in.addFlags(flags);
// in.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
// in.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if(bundle!=null){
in.putExtras(bundle);
}
Window mWindow = getLocalActivityManager().startActivity(id, in);
contextView.addView(mWindow.getDecorView());
}
}
--------------------------------------------------------------------------------------------
--------------------------內存管理方式的選擇---------------------------------------
--------------------------------------------------------------------------------------------
據我所知的內存管理方式有兩種:一種是軟引用,一種是記錄資源所用的次數,當為0時,及時釋放掉。
在應用中一般使用軟引用。
private HashMap<String, SoftReference<Drawable>> imageCache;
這個網上有很多例子,可以查找下。做法就是在當加進來一張圖片時,根據它的鏈接地址作為鍵值,如果這張圖片在這個哈希表中,就返回它的軟引用,然后產生對對象,如果不存在,然后看本地緩存中有沒有,有的話就返回對象,沒有的話 ,就保存圖片到本地。為以后再用。
在游戲中一般使用后一種做法,及時的消除資源。
原理就是,當增加一個界面時,計算界面中有多少圖片,然后有個圖片管理計數器,計算相同的圖片被加進去多少次,當關閉這個界面的時候,圖片被使用的數目減1,當為0的時候,就釋放掉這種圖片。
--------------------------------------------------------------------------------------------
-------------------------- ListView 中ArrayAdapter中刪除Item-----------------
--------------------------------------------------------------------------------------------
看到網上最簡單的回答是這樣的:
// adapter.remove(favorite);
// adapter.notifyDataSetChanged();
我信以為真。結果。你懂得。出錯了。呵呵。
問題出在哪里。一般ArrayAdapter中有下面兩個變量:
private List<Favorites> favorites;
private int count;
是這兩個變量出了問題?是的。 只更改上面的還不夠, 還需要更改適配器的管理數據的數量和數據。代碼如下:
maxCount = Integer.parseInt(data.get("maxCount").toString());
ArrayList<Favorites> tempFavorites = (ArrayList) data.get("favoritesList");
favoritesList = tempFavorites;
//如果適配器里面的數據數少於最大數就增加適配器數量,
//當這一次增加的數量是最大數量的時候,就刪除腳view
//同時設置havaFooterView為假
adapter.setCount(favoritesList.size());
adapter.setFavorites(favoritesList);
if (adapter.getCount() == maxCount && havaFooterView)
{
list.removeFooterView(loadingLayout);
havaFooterView = false;
}
//重新刷新Listview的adapter里面數據
adapter.notifyDataSetChanged();
當我們這樣寫的時候。特別要注意adapter.setFavorites(favoritesList);這句話。剛開始我的做法是這樣的,favoritesList = tempFavorites,后面沒有其他操作了。這樣就是兩個引用了。 數據還是沒有更新。 當時很煩躁,沒太注意這個東西。如果單獨的改變里面的值倒是可以的。還是一個引用。如果直接指到另一個引用。就有問題了。 呵呵。 感覺各位應該都不會犯這種低級問題吧。呵呵。
