从零快速搭建Android项目

好了,本文只作为Android开发工作的一个记录。记录项目从零开始要处理的都有哪些工作。废话不多说,直接进入正题

1.概要设计

1.1 模块划分

根据需求和原型设计,可能的模块划分如下:

*注册登录模块

*用户模块

*帖子模块

*媒体(图片+视频)模块

*相册模块

*……

1.2 数据交换和API接口

服务端与客户端使用JSON交换数据,使用自定义JSON格式,约定返回code、message,实体封装在result中,支持单个实体、实体列表、多个实体列表,定义如下:

{

"code":500,

"message":"系统异常,请稍后重试",

"result":""

}


{

"code":200,

"message":"登录成功",

"result":{

"user":{

"userId":1,

"nickName":"Leo",

"email":"xxx.com",

"gender":0

}

}

}

主要API接口设计如下:

http://api.xxx.com/service/v1.0/user/login

http://api.xxx.com/service/v1.0/user/third-login

http://api.xxx.com/service/v1.0/user/register

http://api.xxx.com/service/v1.0/user/logout

http://api.xxx.com/service/v1.0/user/info/update

http://api.xxx.com/service/v1.0/album/upload

http://api.xxx.com/service/v1.0/album/update

http://api.xxx.com/service/v1.0/album/delete

......

也许你看到了,API做了二级域名映射,同时为了服务端后期API版本的升级管理,在URL中加上了版本标识V1.0。对了,此处没有使用Https。为了解决数据传输的安全,我做了点特别的处理:对请求体和响应结果进行RSA加密(如果服务端返回的数据稍稍过大,这个RSA严重影响客户端解密,后来我换成了AES),所有请求为POST请求,所以API URL后面没有带参数,你也看不到任何请求相关的信息。

1.3 数据库设计

根据需求和原型设计,数据库的设计大概需要两周时间。其实一周基本搞定了,但为了考虑充分,留出一周时间来检验和调整。数据库E-R图略。

2 Android客户端

2.1 基本结构

Android本身就是MVC,所以我不打算引入MVP或MVVM。我的理念是职责分层,快速推出Android 1.0。主要的包结构如下:



工程的搭建和包的划分有各种各样的,适合自己的就行了。想讨论或想看别人怎么做的,点击这里:App工程结构搭建:几种常见Android代码架构分析

2.2 功能划分

注册登录,个人信息,相册管理,消息通知,系统设置等等。

2.3 引入的第三方技术

重复发明轮子是不可取的。有些模块根本没必要自己写。以下是引入的第三方库,以及优势说明。

2.3.1 网络请求库android-async-http

*在匿名回调中处理请求结果

*在UI线程外进行http请求

*文件断点上传

*智能重试

*默认gzip压缩

*支持解析成Json格式

*可将Cookies持久化到SharedPreferences

2.3.2 云巴推送

专注于为需要实时数据交换的产品提供完美解决方案

基于发布者/订阅者(publisher/subscriber)模式,集成简单

对比了百度云推送、腾讯信鸽推送,云巴效果更好

原极光推送CTO创办的

2.3.3 xUtils(只使用其中的DbUtils和ViewUtils)

*Android中的ORM框架,一行代码就可以进行增删改查

*支持事务,默认关闭

*可通过注解自定义表名、列名、外键、唯一性约束、NOT NULL约束、CHECK约束等(需要混淆的时候请注解表名和列名

*Android中的IOC框架,完全注解方式就可以进行UI,资源和事件绑定

*新的事件绑定方式,使用混淆工具混淆后仍可正常工作

2.3.4 友盟统计

*国内专业的移动应用统计分析平台

*统计和分析流量来源、内容使用、用户属性和行为数据

*Crash log跟踪

2.3.5 云通讯验证码

*解决方案成熟,众多公司使用

2.3.6 高德地图定位

*GPS+基站+wifi的混合定位方式

*接入简单

2.3.7 异步图片加载库Android-Universal-Image-Loader

*多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等

*支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置

*支持图片的内存缓存,文件系统缓存或者SD卡缓存

*支持图片下载过程的监听

*根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存

*较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片

*提供在较慢的网络下对图片进行加载

2.3.8 阿里云OSS Android客户端SDK

*提供文件(图片、视频等等)上传

*大文件分块上传

*删除操作(不推荐在客户端使用)

2.3.9 组件内通讯EventBus

*基于发布者/订阅者(publisher/subscriber)模式

*简化了应用程序内各组件间、组件与后台线程间的通信

2.3.10 Android本地数据库加密库SQLCipher

*基于SQLite扩展的开源数据库,在SQLite的基础之上增加了数据加密功能

*SQLCipher对Android SDK中所有与数据库相关的API都制作了一份镜像,使得开发者可以像操作普遍的数据库文件一样来操作SQLCipher

2.4 基础组件封装

2.4.1 基础回调接口

public interface DataCallback{
void onSuccess(Object result);
void onFailure(Object result);
}

2.4.2 网络访问

先看一下登录的序列图:

HttpManager类负责调用AsyncHttpWrapper中的post方法,和对服务端返回的数据解密、JSON转对象、回调上层;AsyncHttpWrapper则负责请求体的封装加密和其它的校验参数封装。看一下HttpManager类的post方法:

public void post(Context context,String url,RequestParams params,final String modelName,
final DataCallback callback){
<span style="white-space:pre">	</span>AsyncHttpWrapper.post(context,url,params,new AsyncHttpResponseHandler(){
<span style="white-space:pre">	</span>@Override
<span style="white-space:pre">	</span>public void onSuccess(int statusCode,Header[] headers,byte[] responseBody){
<span style="white-space:pre">	</span>try{
<span style="white-space:pre">	</span>if(modelName!=null){
<span style="white-space:pre">		</span>handleResponse(responseBody,callback,modelName);
<span style="white-space:pre">	</span>}else{
<span style="white-space:pre">		</span>String response=new String(responseBody);
<span style="white-space:pre">		</span>// 解密
<span style="white-space:pre">		</span>response=AES128.getInstance().decrypt(AppUtil.decodeReplace(response));
<span style="white-space:pre">		</span>// JSON转对象
<span style="white-space:pre">		</span>BaseMessage message=AppUtil.getMessage(response);
<span style="white-space:pre">		</span>if(callback!=null){
<span style="white-space:pre">			</span>// 如果自定义code是200
<span style="white-space:pre">			</span>if(Coder.CODE_200.equals(message.getCode())){
<span style="white-space:pre">				</span>callback.onSuccess(message.getMessage());
<span style="white-space:pre">			</span>}else{
<span style="white-space:pre">				</span>callback.onFailure(newServerError(message.getCode()));
<span style="white-space:pre">			</span>}
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>}catch(JSONExceptione){
<span style="white-space:pre">		</span>LogUtil.e(e);
<span style="white-space:pre">		</span>callback.onFailure("服务端返回的数据不能解析成JSON");
<span style="white-space:pre">	</span>}catch(Exceptione){
<span style="white-space:pre">		</span>LogUtil.e(e);
<span style="white-space:pre">		</span>callback.onFailure(e);
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>@Override
<span style="white-space:pre">	</span>public void onFailure(int statusCode,Header[] headers,byte[] responseBody,Throwable error){
<span style="white-space:pre">	</span>if(callback!=null){
<span style="white-space:pre">		</span>callback.onFailure(error);
<span style="white-space:pre">		</span>if(responseBody!=null){
<span style="white-space:pre">			</span>String s=new String(responseBody);
<span style="white-space:pre">			</span>LogUtil.e(s);
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>}
});
}

AsyncHttpWrapper中的post方法

public static void post(Context context,String url,RequestParams params,AsyncHttpResponseHandler responseHandler){
<span style="white-space:pre">	</span>// 设置请求头部信息
<span style="white-space:pre">	</span>generateHeader(context);
<span style="white-space:pre">	</span>// 加密请求参数
<span style="white-space:pre">	</span>String encryParams=AES128.getInstance().encrypt(params.toString());
<span style="white-space:pre">	</span>RequestParams requestParams=new RequestParams();
<span style="white-space:pre">	</span>requestParams.put("param",AppUtil.encodeReplace(encryParams));
<span style="white-space:pre">	</span>client.post(context,url,requestParams,responseHandler);
}

private static AsyncHttpClient client=new AsyncHttpClient();

2.4.3 Adapter封装

为了加快开发速度,重用代码,Adapter的使用有技巧。每次在getView中查找控件id、利用ViewHolder、赋值,最后返回convertView,看着都是差不多的代码。是时候脱离这个苦海了。先看怎么解决共用的ViewHolder问题。

public static <T extends View> T get(View view,int id){
<span style="white-space:pre">	</span>SparseArrayCompat<View><span style="white-space:pre">	</span>viewHolder=(SparseArrayCompat<View>)view.getTag();
<span style="white-space:pre">	</span>if(viewHolder==null){
<span style="white-space:pre">		</span>viewHolder=newSparseArrayCompat<>();
<span style="white-space:pre">		</span>view.setTag(viewHolder);
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>View childView=viewHolder.get(id);
<span style="white-space:pre">	</span>if(childView==null){
<span style="white-space:pre">		</span>childView=view.findViewById(id);
<span style="white-space:pre">		</span>viewHolder.put(id,childView);
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>return(T)childView;
}

ViewHolder的作用,就是通过convertView.setTag与convertView进行绑定。当convertView复用时,直接从与之对应的ViewHolder(getTag)中拿到convertView布局中的控件,省去了findViewById的时间。上面的代码就是这样的原理。

然后就是CommonAdapter了。

public abstract class CommonAdapter<T> extends BaseAdapter{
<span style="white-space:pre">	</span>protected LayoutInflater inflater;
<span style="white-space:pre">	</span>protected Context context;
<span style="white-space:pre">	</span>protected List<T>datas;
<span style="white-space:pre">	</span>protected finalintitemLayoutId;
<span style="white-space:pre">	</span>publicCommonAdapter(Context context,List<T>datas,intitemLayoutId){
<span style="white-space:pre">		</span>this.context=context;
<span style="white-space:pre">		</span>this.inflater=LayoutInflater.from(context);
<span style="white-space:pre">		</span>this.datas=datas;
<span style="white-space:pre">		</span>this.itemLayoutId=itemLayoutId;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>@Override
<span style="white-space:pre">	</span>public int getCount(){
<span style="white-space:pre">		</span>returndatas!=null?datas.size():0;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>@Override
<span style="white-space:pre">	</span>public T getItem(intposition){
<span style="white-space:pre">		</span>return datas.get(position);
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>@Override
<span style="white-space:pre">	</span>public long getItemId(int position){
<span style="white-space:pre">		</span>return position;
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>@Override
<span style="white-space:pre">	</span>public View getView(int position,View convertView,View Groupparent){
<span style="white-space:pre">		</span>final CommonViewHolder viewHolder=getViewHolder(position,convertView,parent);
<span style="white-space:pre">		</span>convert(viewHolder,getItem(position),position);
<span style="white-space:pre">		</span>return viewHolder.getConvertView();
<span style="white-space:pre">	</span>}
<span style="white-space:pre">	</span>public abstract void convert(CommonViewHolder viewHolder,T item,int position);
}

2.4.5 其它Utils封装

如AES128加密类、BitmapUtils、SecurePreferences、StringUtil、ToastUtil、IOUtil等等。

2.5 接口测试

为了保证数据交换、加解密正常,首先对某一个接口进行测试,以验证API Service能正常跑通。比如可以先对登录进行模拟测试,看是否成功,同时包括异常的测试,服务端是不是处理了边界异常,返回给客户端的都是封装过的异常信息,而不是抛一个敏感信息给客户端。提前进行接口测试有助于我们的基础组件运行没问题,方便后期其它模块的快速集成。

2.6 快速开发

基础组件封装好后,除了少量的从网络获取数据逻辑和本地数据库的增删改查,客户端基本上就是界面的布局工作了。界面开发基本看熟练程度和自定义View的重用。

好了,基本就这些。两个Android开发人员两个月内完成肯定是可以的,前提是至少有一个熟手。后面再谈谈MVP,毕竟这个客户端设计没法进行单元测试,如果业务逻辑越来越复杂,Activity的职责会越来越重,问题多多,不利于后期维护。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值