作为一个学生,完全一个人写程序是一件非常苦逼的事情,没有设计天赋,要写界面,用ps,做出来还巨丑,但是好在google 推出了md 设计规范,勉强可以写出看的过去的程序,但是android毕竟还是属于前端,没有后台服务器支持的话,有很多东西实现不了,自己学android也不久,没那么多精力在入后台的坑,于是打算为一个网站写个第三方客户端练练手(好吧,其实豆瓣之类的网站是开放了api的,单纯只是因为我以前经常用这个网站刷算法题而已,当时觉得有点麻烦,正好在学android,想着能不能做出一个app出来,巩固下自己的android学习,
学android 的时间不算多,所以不敢说是教程,怕坑了新人,这里就是自己开发过程中的一个分享而已。
这里结合自己写好的 app 来说说一个第三方客户端项目的构建过程。
先放下源码地址吧:https://github.com/Thereisnospon/AcClient
构思
因为没有api,所以只能通过爬虫的方式,通过获得网页的html,再从中解析出数据。开始我是打算使用 正则表达式帮我进行解析的,然而这个方法非常蛋疼,因为一个html文件非常复杂,各种标签乱飞,解析几种页面之后,就了解到了一个神器 Jsoup 可以很方便的从 html 中 获取想要的信息。比如这里就是项目中一个代码片段,用来获取一个标签的内容:
public class CodeBuilder {
public static String parse(String html){
Document document= Jsoup.parse(html);
Element element=getTexrs(document);
if(element!=null)
return element.text();
else return "";
}
private static Element getTexrs(Document document){
Elements elements=document.getElementsByTag("textarea");
if(elements==null||elements.size()==0)
return null;
else return elements.first();
}
}
然后就是如何获取网页的内容,进行模拟登陆,模拟注册了。这个通过抓取 http 包的信息,可以查看 post 提交信息
之后通过 给出 cookie 值,就可以实现模拟登陆了。
网络请求的话,使用 OkHttp 比较方便,但是还是封装了一层,进行更好的网络请求操作,管理cookie 等信息。
实现了 网络请求和 数据解析之后,差不多就可以做一个项目出来了,但是网络请求要处理线程的问题,AsynaCTask 不靠谱,自己写线程各种 handle ,message,或者用 EventBus 感觉还是很乱,于是使用了 RxJava 这个神器,结合MVP模式可以写出很优雅的代码。就像这样。
public class RankPresenter implements RankContact.Presenter {
RankContact.View view;
RankContact.Model model;
public RankPresenter(RankContact.View view) {
this.view = view;
model=new RankModel();
}
@Override
public void loadRankItems() {
Observable.just(1)
.observeOn(Schedulers.io())
.map(new Func1<Integer, List<RankItem>>() {
@Override
public List<RankItem> call(Integer integer) {
return model.loadRankItems();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<RankItem>>() {
@Override
public void call(List<RankItem> list) {
if (list !=null &&list.size()!=0)
view.onRefreshRankSuccess(list);
else view.onRankFailure("load null");
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
view.onRankFailure(throwable.getMessage());
}
});
}
@Override
public void moreRankItems() {
Observable.just(1)
.observeOn(Schedulers.io())
.map(new Func1<Integer, List<RankItem>>() {
@Override
public List<RankItem> call(Integer integer) {
return model.moreRankItems();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<RankItem>>() {
@Override
public void call(List<RankItem> list) {
if (list !=null &&list.size()!=0)
view.onMoreRanks(list);
else view.onRankFailure("load null");
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
view.onRankFailure(throwable.getMessage());
}
});
}
}
MVP 模式 也就是分 Model,View,Contact 层,把 Activity 非常复杂的功能分解出来,这样既可以降低耦合,又可以让代码逻辑更加清晰。
例如,要实现一个获取排名信息的界面,先定义一个接口
public interface RankContact {
interface View{
void onRefreshRankSuccess(List<RankItem> list);
void onMoreRanks(List<RankItem>list);
void onRankFailure(String msg);
}
interface Model{
List<RankItem>loadRankItems();
List<RankItem>moreRankItems();
}
interface Presenter{
void loadRankItems();
void moreRankItems();
}
}
然后分别实现它们:
Model
public class RankModel implements RankContact.Model {
private int currentPage=1;
public RankModel() {
currentPage=1;
}
@Override
public List<RankItem> loadRankItems() {
currentPage=1;
return getRanks(currentPage);
}
@Override
public List<RankItem> moreRankItems() {
return getRanks(currentPage);
}
private List<RankItem>getRanks(int page){
String html=getHtml(page);
if(html==null)
return null;
List<RankItem>rankItems=RankItem.Builder.parse(html);
if(rankItems!=null&&rankItems.size()!=0){
currentPage=page+1;
}
return rankItems;
}
private String getHtml(int page){
int from=(page-1)*25+1;
IRequest request=HttpUtil.getInstance()
.get(HdojApi.RANK)
.addParameter("from",""+from);
try{
Response response=request.execute();
String html=new String(response.body().bytes(),"gb2312");
return html;
}catch (IOException e){
e.printStackTrace();
}
return null;
}
}
Presenter
public class RankPresenter implements RankContact.Presenter {
RankContact.View view;
RankContact.Model model;
public RankPresenter(RankContact.View view) {
this.view = view;
model=new RankModel();
}
@Override
public void loadRankItems() {
Observable.just(1)
.observeOn(Schedulers.io())
.map(new Func1<Integer, List<RankItem>>() {
@Override
public List<RankItem> call(Integer integer) {
return model.loadRankItems();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<RankItem>>() {
@Override
public void call(List<RankItem> list) {
if (list !=null &&list.size()!=0)
view.onRefreshRankSuccess(list);
else view.onRankFailure("load null");
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
view.onRankFailure(throwable.getMessage());
}
});
}
@Override
public void moreRankItems() {
Observable.just(1)
.observeOn(Schedulers.io())
.map(new Func1<Integer, List<RankItem>>() {
@Override
public List<RankItem> call(Integer integer) {
return model.moreRankItems();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<RankItem>>() {
@Override
public void call(List<RankItem> list) {
if (list !=null &&list.size()!=0)
view.onMoreRanks(list);
else view.onRankFailure("load null");
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
view.onRankFailure(throwable.getMessage());
}
});
}
}
View
public class RankFragment extends NormalSwipeFragment implements RankContact.View{
RankContact.Presenter presenter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view=normalView(inflater,container,savedInstanceState);
presenter=new RankPresenter(this);
Logger.d("create viwe");
return view;
}
@Override
public BaseSwipeAdapter createItemAdapter(List list) {
return new RankItemAdapter(list);
}
@Override
public void loadMore() {
Logger.d("laod more");
presenter.moreRankItems();
}
@Override
public void refresh() {
Logger.d("refresh");
presenter.loadRankItems();
}
@Override
public void onRefreshRankSuccess(List<RankItem> list) {
Logger.d("refresh success");
onRefreshData(list);
}
@Override
public void onMoreRanks(List<RankItem> list) {
Logger.d("more ranks");
onMoreData(list);
}
@Override
public void onRankFailure(String msg) {
Logger.e(msg);
}
}
这样的话,作为view的 Fragment 只需要做界面显示的功能就可以了,而不用管数据怎么获取,而 Presenter 就做好 Model 和 View 的桥梁,把Model 查找的数据给View 显示就可以了。
这样 一个 Activity 就会非常简单
public class RankActivity extends SearchActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rank);
Logger.d("create");
initDrawer();
changeFragment(new RankFragment());
}
@Override
public boolean onQueryTextSubmit(String query) {
Fragment fragment= SearchPeopleFragment.newInstance(query);
changeFragment(fragment);
return true;
}
}
更详细的mvp教程 :
http://rocko.xyz/2015/02/06/Android%E4%B8%AD%E7%9A%84MVP/
于是整个项目的结构就成了这样:
- api 放请求的url。。好吧并不能算api,因为内容都是自己抓的orz
- base 定义了一些基本的Activity,Fragment,
- data 定义了数据的Bean 以及创建它们的Builder
- event 放了关于Intent ,EventBus 相关的消息标识
- modules 重要的部分,放置各个功能模块
- ui 这里放了些 adapter
- utils 放置工具类,例如网络请求
- widget 放置自定义的组件等
大致就想到了这么些。mark,慢慢填坑.ORZ
最后还是给下地址吧:
项目的代码地址为:https://github.com/Thereisnospon/AcClient
应用的下载地址:http://www.coolapk.com/apk/thereisnospon.acclient