WOTPlus(2) - 开发_0

假如我要写什么,我根本就不管他格调不格调,正如谈恋爱时我绝不从爱祖国开始谈起。

写在前面

以下计划有几篇开发相关的文章,我并不是想做一个教程,而只是想记录下我开发 WOTPlus 时所一步步走过的路;
文章的大体走向按照时间线,开发的顺序除数据获取外基本上是按照功能模块次第进行的;
文章参照,我的印象笔记 - 坦克世界战绩查询WOTPlus开发日志


Let's Rock

首先还是 数据 的问题,毕竟坦克世界官网不是个开放平台,也没有给开发者使用的数据接口,所以只好自己来找了;
抽象数据之前,先把工程建立出来,实现类似 知乎日报 的放大图片动画的启动页:

启动页

放大动画动画效果代码:

iv_begin.setImageResource(R.mipmap.begin);

final ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 1.2f, 1.0f, 1.2f,
        Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
        0.5f);
scaleAnim.setFillAfter(true);
scaleAnim.setDuration(3000);
scaleAnim.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        startActivity();
    }

    @Override
    public void onAnimationRepeat(Animation animation) {

    }
});
iv_begin.startAnimation(scaleAnim);

实现的效果图(我先学习下怎么制作gif):

弄了好一阵子,命令行、录屏软件之类的都试了,但是在我的MX2上就是不好使,统统闪退,所以先来个静态图先看着,等我有空了再弄动态演示图

启动图

启动图片的尺寸:1080x1920

查询页与主页

这里的主页架构参照:Chris Banes 的轮子 cheesesquare
使用侧滑菜单罗列功能模块;

侧滑菜单图

** Navigation的图标问题 ** :
全部取自 Google 的 Material icans,免去了自己找或者自己画的痛苦,当然我也不会画,,,

然后开始写首页的UI,首页是一个Card的RecyclerView,所以先写了几个静态的CardView:

两个Card

数据的获取

空中网并没有对外暴露玩家信息的json的接口,我用chrome抓了无数遍的包,分析了好久,始终没找到相应的接口, 所以只好从html中获取想要的数据了,这与json接口相比显然是慢了好多,但是这也是没办法的事情。

大体的思路是:

  • 获取玩家信息html字符串;
  • 使用jsoup处理字符串并将相应信息对应到实体类 Woter
  • 使用Woter来进行赋值操作;

首先获取html:
一开始使用的是httpUrlconnection,但是返回的却是:getErrorStream ---错误 403: 权限不足
应该是header之类的需要设置,但是我比较不习惯使用这个,于是我开始寻找网络框架来做请求;

试用了Volley、 android-async-http,最后选择了Volley,毕竟是Google的官方产品,莫名有种靠谱感;

Volley的使用教程,来自郭神:Android Volley完全解析(一),初识Volley的基本用法

获取到源码时候使用jsoup进行分析:
添加jsoup的依赖: compile 'org.jsoup:jsoup:1.8.3'

jsoup的官方文档:jsoup: Java HTML Parser
使用jsoup解析HTML之获取html源码的几种方法

2016年5月5日22:47:20 今天先写到这里;

概要信息抽取

2016年5月14日20:07:34 继续;
创建了一个工具类 JsoupHtmlUtil 来解析html为 Woter ;
其中 handleWotPage 方法:

public static Woter handleWotPage(Document doc,String region) {
        Woter woter = new Woter();
        Elements elements;
        Element element;

        try {

            /**
             * (1)概要信息
             */
            element = doc.getElementById("js-profile-name");
            woter.setWoterName(element.text());

            // 创建时间戳
            element = doc.select(".js-date-format").first();
            woter.setTimeStamp(element.attr("data-timestamp"));

            // 主要信息栏
            elements = doc.select(".t-personal-data_value");
            woter.setPersonRanking(elements.get(0).text());
            woter.setPersonWin(elements.get(1).text());
            woter.setPersonFight(elements.get(2).text());
            woter.setPersonExp(elements.get(3).text());
            woter.setPersonDmg(elements.get(4).text());

            // 击杀/死亡率 伤害原因/收到
            elements = doc.select(".b-speedometer-weight");
            woter.setKillDeathRate(elements.get(0).text());
            woter.setDmgRecRate(elements.get(1).text());

            elements = doc.select(".b-speedometer-ratio");
            woter.setKillDeathNum(elements.get(0).text());
            woter.setDmgRecNum(elements.get(1).text());
      }
}

其实就是在html中找到需要的数据,并根据ID、class等将数据提取出来放置到自己构建的Woter类中;
麻烦就麻烦在数据要一个一个的找,并且要找出html的规律以便更好的提取数据;

军团信息获取

军团信息是临时获取的,并没有固定的显示,因为有的人没有加军团,没法找到其url;

因此,调用获取军团信息的网络请求之前需要进行一个判断:

String clanUrl = userInfo.getResponse().get(0).getClan_url();

if (TextUtils.isEmpty(clanUrl)) {
    woter.setEnterClanFlag("0");
    // 没有clanUrl则跳出
    handler.sendEmptyMessage(1);
} else {
    woter.setEnterClanFlag("1");
    // 调用获取军团信息的请求
    mQueue.add(jsonObjectRequest);
}

从html中找到动态获取军团信息的js:

军团信息获取

这个 url 返回的是 json ,但是,这个json里的数据也是html代码,用于直接在html页面上显示军团块的,因此还需要用 jsoup 对里面的信息进行提取;
实现方式:
使用Gson将获取到的json数据转换成对应的类-ClanInfo;
然后使用 jsoupClanInfo方法来处理 clanInfo.getData().getClan_block().toString()

/**
     * 使用jsoup处理军团信息
     * @param s
     */
    private void jsoupClanInfo(String s) {
        Document doc = Jsoup.parse(s);
        Element link = doc.select("img").first();

        Element clanPosition = doc.select(".number").first();
        Element clanDays = doc.select(".number").last();

        // 赋值给woter,但是这个地方要考虑赋值的顺序,应该是在获取解析的主页面信息之后,再设置这几个军团信息;
        woter.setClanDescription(link.attr("alt"));
        woter.setClanImgSrc(link.attr("src"));
        woter.setClanPosition(clanPosition.text());
        woter.setClanDays(clanDays.text());

    }

至此,军团信息获取完毕;

抽取成就信息

成就信息比较复杂,有相当长的html代码块是用来展示成就信息的;
首先,抽象成就信息为model - Achievements,作为Woter的一个属性;
然后是 Achievements 将成就信息分为7个list及其相对应的数量,以及一个总数量:

public class Achievements {

    // 总成就数量 获取/总数
    public String totalNum;

    /**
     * 战斗英雄
     */
    public List<Achieve> warheroList;
    /**
     * 荣誉排行
     */
    public List<Achieve> honorList;
    /**
     * 史诗成就
     */
    public List<Achieve> epicList;

    public List<Achieve> teamList;
    public List<Achieve> commemorateList;
    public List<Achieve> stageList;
    public List<Achieve> otherList;

    // 获取与总数数量  例如 9/14
    public String warHeroNum;
    public String honorNum;
    public String epicNum;
    public String teamNum;
    public String commemorateNum;
    public String stageNum;
    public String otherNum;
}

然后是 jsoup 提取成就信息的代码(你可能不想看,因为太多了):

/**
 * (2)成就信息
 */

// 提取成就息
// 总数
Element total = doc.select(".js-achievements-header").first();
// totalNum,在下面赋值
// System.out.println(total.text());

// 最外层div js-full-achievements
Element root = doc.select(".js-full-achievements").first();

// 成就分类数量
Elements subroots = root.getElementsByTag("h4");

// (1)七个分类的名称及数量,在下面赋值
//            String subNum1 = subroots.get(0).text();
// ...

// (2)七个分类的内容
Elements subrootContents = root.select(".b-achivements"); // 7个内容

Achievements achievements = new Achievements();
for (int i = 0; i < subrootContents.size(); i++) {

    List<Achieve> tempList = new ArrayList<Achieve>();

    Elements contentLis = subrootContents.get(i).select(".b-achivements_item");
    Elements contentDivs = subrootContents.get(i).select(".b-tooltip-main");

    for (int j = 0; j < contentLis.size(); j++) {

        Achieve achieve = new Achieve();

        Element contentLi = contentLis.get(j);
        Element contentDiv = contentDivs.get(j);

        // (1)from contentLi
        // ID和图片地址
        String img;
        Element imgContent = contentLi.getElementsByTag("img").first();
        if (QueryActivity.REGION_NORTH.equals(region)) {
            img = "http:" + imgContent.attr("src"); // //ncw.worldoftanks.cn/static/3.34.7/encyclopedia/tankopedia/achievement/geniusforwarmedal.png
        } else {
            img = "http://scw.worldoftanks.cn" + imgContent.attr("src"); // /static/3.35.7/encyclopedia/tankopedia/achievement/victorymarch.png
        }

        String id = imgContent.attr("alt");
        // 获得数量
        Element numContent = contentLi.select(".b-achivements_num").first();
        // 数量存在有或者没有两种情况,需要进行判断
        String num = "0";
        if (numContent != null) {
            num = numContent.text();
        }

        // (2)from contentDiv
        // 成就名称
        Element nameContent = contentDiv.select(".b-bold").first();
        String name = nameContent.text();
        // 成就描述
        String discription = "";
        Element contentDis = contentDiv.getElementsByTag("p").last();
        // 描述(1)
        discription = contentDis.text();
        // 附加描述 可能为空
        Element contentDisAdd = contentDiv.getElementsByTag("li").first();
        if (contentDisAdd != null) {
            discription += contentDisAdd.text();
        }
        // 另:几个特殊的情况
        // 巴顿猎手
        if ("pattonvalley".equals(id)) {
            Elements contentDiss = contentDiv.getElementsByTag("p");
            discription = "";
            for (Element p : contentDiss) {
                discription += p.text();
            }
        }
        // 猎人
        if ("beasthunter".equals(id)) {
            Elements contentDiss = contentDiv.getElementsByTag("p");
            discription = "";
            for (Element p : contentDiss) {
                discription += p.text();
            }
        }
        // 西奈雄狮
        if ("sinai".equals(id)) {
            Elements contentDiss = contentDiv.getElementsByTag("p");
            discription = "";
            for (Element p : contentDiss) {
                discription += p.text();
            }
        }

        achieve.setAchivementId(id);
        achieve.setAchivementImg(img);
        achieve.setAchivementNum(num);
        achieve.setAchivementName(name);
        achieve.setAchivementDes(discription);

        tempList.add(achieve);
    }

    achievements.setTotalNum(total.text());
    if (i == 0) {
        achievements.setWarheroList(tempList);
        achievements.setWarHeroNum(subroots.get(i).text());
    }
    if (i == 1) {
        achievements.setHonorList(tempList);
        achievements.setHonorNum(subroots.get(i).text());
    }
    if (i == 2) {
        achievements.setEpicList(tempList);
        achievements.setEpicNum(subroots.get(i).text());
    }
    if (i == 3) {
        achievements.setTeamList(tempList);
        achievements.setTeamNum(subroots.get(i).text());
    }
    if (i == 4) {
        achievements.setCommemorateList(tempList);
        achievements.setCommemorateNum(subroots.get(i).text());
    }
    if (i == 5) {
        achievements.setStageList(tempList);
        achievements.setStageNum(subroots.get(i).text());
    }
    if (i == 6) {
        achievements.setOtherList(tempList);
        achievements.setOtherNum(subroots.get(i).text());
    }

}

woter.setAchievements(achievements);
解析完的成就类

这个地方是比较复杂的,因为要根据html的结构一层层的抽丝剥茧,到最后才能将所有的数据一个个的抽离出来,这里使用了嵌套循环,使之最后直接构建出一个 **Achievements ** ,并set到Woter中去;

做完这一块之后,我的信息大增,毕竟页面要使用的数据源有了将近一半了!
于是我就拿着这些能提取出来使用的数据去写页面了,,,

延时加载

首先阐明下输入玩家昵称和大区之后,点击查询之后:

  1. 根据昵称和大区获取玩家的基本信息,其中最重要的就是:account_id;
  2. 获取到玩家ID后,用其ID和NAME做参数,获取玩家战绩的html源码String;
  3. 将2中获取的String交给 JsoupHtmlUtil.handleWotPage 方法处理,返回缺少军团信息的Woter对象;
  4. 判断是否有军团信息,若有:获取军团信息,并处理返回的json,将军团信息set到3中的Woter,获取数据完毕,开始将Woter交给 WoterAdapter 绘制首页列表;若无:则获取数据完毕,开始将Woter交给 WoterAdapter 绘制首页列表;

以上这个流程,刨去4先不说,123是肯定要执行的,123中有两个嵌套的网络请求,并且第二个网络请求返回的是500k左右的String,而且需要将这个String用jsoup进行解析为Woter,这是需要耗费相当长的时间的,当然这个东西不能在主线程中进行了,我这里使用一个 Handler 来进行延时加载的控制:

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {

                case 1:
                    try {
                        woterAdapter = new WoterAdapter(getActivity(), woter);
                        // 添加载入动画
                        AlphaAnimatorAdapter animatorAdapter = new AlphaAnimatorAdapter(woterAdapter, mRecyclerView);

                        mRecyclerView.setAdapter(animatorAdapter);
                        deathWheelProgressDialog.dismiss();

                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    break;
            }
        }
    };

当执行完耗时操作后,这个操作可能是在第二个网络请求后(无军团信息的情况),也有可能是在第三个网络请求之后(有军团信息的情况),调用 handler.sendEmptyMessage(1); 方法;
一开始使用的时候我还犯了个错误,就是在网络请求之后直接调用了 sendEmptyMessage 方法,而解析html的耗时方法还没有执行完,这样就起不到控制异步任务的作用了,因此,应该在所有的耗时方法都执行完毕之后再调用 sendEmptyMessage;

第一篇暂时写到这里,篇幅太长不宜阅读,下一篇继续;
2016年5月14日21:24:07 by zhang.xx

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,652评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,085评论 25 707
  • 做一件事,无论大小,倘无恒心,是很不好的。 承上启下 上篇博文记录了 WOTPlus 的启动页、查询页、侧滑菜单与...
    康恩饭阅读 535评论 0 0
  • 终于躺床上了,却习惯性睡不着,思绪满天飞,总是为女儿担心:担心她一个人回去的安全性,担心她分数太低选不了好学校,担...
    springwen阅读 153评论 0 0
  • 2017年9月16日 周一 晴天 今天一整天都在忙碌的搬家搬东西,因为是一个人所以开会跑了三趟,才把东西搬的差不多...
    娟姐66阅读 110评论 0 0