如何在没有官方API的情况下写一个第三方客户端

作为一个学生,完全一个人写程序是一件非常苦逼的事情,没有设计天赋,要写界面,用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 提交信息

4344FDD1-BB2B-4AA8-A8E6-809810B1E112.png

之后通过 给出 cookie 值,就可以实现模拟登陆了。

网络请求的话,使用 OkHttp 比较方便,但是还是封装了一层,进行更好的网络请求操作,管理cookie 等信息。

9E6C5408-A2EB-46F5-9F9B-026F2F4CAB42.png

实现了 网络请求和 数据解析之后,差不多就可以做一个项目出来了,但是网络请求要处理线程的问题,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/

于是整个项目的结构就成了这样:

B52C0E65-93AB-488F-BA5A-47B6B7266CAD.png

  • 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

猜你喜欢

转载自blog.csdn.net/yzr1183739890/article/details/53036696