动机:
首先自己不是做应用的,一直以来从事的都是网络游戏客户端的开发。一是想了解一下应用开发的流程与技术点,二是看到园子里有人做的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,后面没有其他操作了。这样就是两个引用了。 数据还是没有更新。 当时很烦躁,没太注意这个东西。如果单独的改变里面的值倒是可以的。还是一个引用。如果直接指到另一个引用。就有问题了。 呵呵。 感觉各位应该都不会犯这种低级问题吧。呵呵。