作为一个学生,完全一个人写程序是一件非常苦逼的事情,没有设计天赋,要写界面,用ps,做出来还巨丑,但是好在google 推出了md 设计规范,勉强可以写出看的过去的程序,但是android毕竟还是属于前端,没有后台服务器支持的话,有很多东西实现不了,自己学android也不久,没那么多精力在入后台的坑,于是打算为一个网站写个第三方客户端练练手(好吧,其实豆瓣之类的网站是开放了api的,单纯只是因为我以前经常用这个网站刷算法题而已,当时觉得有点麻烦,正好在学android,想着能不能做出一个app出来,巩固下自己的android学习,
学android 的时间不算多,所以不敢说是教程,怕坑了新人,这里就是自己开发过程中的一个分享而已。
这里结合自己写好的 app 来说说一个第三方客户端项目的构建过程。
Image may be NSFW.
Clik here to view.
先放下源码地址吧: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 提交信息
Image may be NSFW.
Clik here to view.
之后通过 给出 cookie 值,就可以实现模拟登陆了。
网络请求的话,使用 OkHttp 比较方便,但是还是封装了一层,进行更好的网络请求操作,管理cookie 等信息。
Image may be NSFW.
Clik here to view.
实现了 网络请求和 数据解析之后,差不多就可以做一个项目出来了,但是网络请求要处理线程的问题,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/
于是整个项目的结构就成了这样:
Image may be NSFW.
Clik here to view.
- 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