12306 半自动刷票

*** 说明***:
这不是黑科技,并不是自动购票,请根据自己的需求使用,建议使用抢票 APP 靠谱一些,360 抢票也比这个好,至少它能智能识图!!!
只是自动查询你想要的车次,并自动点击预订,自动填写用户名和密码,但是图片验证码还需要自己点击(这个无解)。
本文先说使用方法,再讲解。

使用方法

  • 首先,打开 12306 的 车票预定页面

  • 摁 F12 键打开浏览器控制台(Chrome 浏览为例),选择 console,如下图所示:

    console.png

  • 将配置好的代码粘贴到图中区域,按 Enter 键回车就行。可以在 Network 里面看到已经在刷了:

log.png
  • 建议运行时关闭控制台 或者 将控制台放到右侧之类,不然小屏幕下登陆框会看不全。点击控制台右侧的三个竖点选择:
    setRight.png

可能的结果

第一种情况是需要重新登录一次,这种很常见。用户名密码都自动帮你填好了,然后自己再填这个坑爹的验证码吧(ˉ▽ˉ;)...,示意图如下:

needLogin.png

另一种情况是直接跳到购买页面,你需要自己勾选乘客和点击提交订单即可:

noLogin.png

无论是哪种结果,都需要重新运行代码。在控制台按向上箭头,再按回车键就行(当然重新粘贴也行)。

<br />


配置代码

先粘代码:

var WISH = {
        train_date: '2017-01-24', // 乘车日期
        from_station_telecode: 'HGH', // HGH - 杭州东
        to_station_telecode: 'NXG', // NXG - 南昌西
        purpose_codes: 'ADULT', // ADULT - 成年人,0X00 - 学生

        station_train_code: [ // 想买的车次,排前面的优先
            'G2365',
            'G1417',
            'G1341'
        ],
        setType: [ // 座位类型,不填 - 不限制; yz_num - 硬座; rz_num - 软座; yw_num - 硬卧; rw_num - 软卧; gr_num - 高级软卧; zy_num - 一等座; ze_num - 二等座; tz_num - 特等座; wz_num - 无座; qt_num - 其它; swz_num - 商务座
            "zy_num", // 一等座
            "ze_num", // 二等座
            "wz_num" // 无座
        ]
        
    },
    USER = {
        name: 'xxx@163.com', // 用户名称
        password: 'xxx' // 用户密码
    },
    SEARCH_RATE = 5000, // 刷新频率,5000 毫秒

    timer = null,
    matchTicket = {},
    availableTicketsMap = {};

/**
 * Ajax
 * @param  {Object}   data     搜索参数
 * @param  {Function} callback 回调函数,用于处理返回的数据
 */
function queryAjax(data, callback) {
    var ajaxData = {
        'leftTicketDTO.train_date': data.train_date,
        'leftTicketDTO.from_station': data.from_station_telecode,
        'leftTicketDTO.to_station': data.to_station_telecode,
        'purpose_codes': data.purpose_codes
    };

    // log, it's no use for me
    $.ajax({
        type: "GET",
        isTakeParam: false,
        beforeSend: function(xhr) {
            xhr.setRequestHeader("If-Modified-Since", "0");
            xhr.setRequestHeader("Cache-Control", "no-cache");
        },
        url: "/otn/leftTicket/log",
        data: ajaxData,
        timeout: 15000,
        success: function(res) {}
    });

    // query
    $.ajax({
        type: 'GET',
        isTakeParam: false,
        beforeSend: function(xhr) {
            xhr.setRequestHeader('If-Modified-Since', '0');
            xhr.setRequestHeader('Cache-Control', 'no-cache');
        },
        url: '/otn/leftTicket/queryA',
        data: ajaxData,
        timeout: 10000,
        success: function(res) {
            if (res.status) {
                callback(res.data);
            }
        }
    });
}

/**
 * 处理返回的数据
 * @param  {Array} data  返回的所有车次信息
 * @return {Object}      可购买的车次 map
 */
function getAvailableTicketsMap(data) {
    var i = 0,
        ticket = {},
        result = {};

    for (i = 0; i < data.length; i++) {
        ticket = {
            secretStr: data[i].secretStr,
            train_no: data[i].queryLeftNewDTO.train_no,
            start_time: data[i].queryLeftNewDTO.start_time,
            station_train_code: data[i].queryLeftNewDTO.station_train_code,
            to_station_telecode: data[i].queryLeftNewDTO.to_station_telecode,
            from_station_telecode: data[i].queryLeftNewDTO.from_station_telecode
        };

        if (ticket.secretStr !== '' && // or data[i].queryLeftNewDTO.canWebBuy === 'Y'
            ticket.to_station_telecode === WISH.to_station_telecode &&
            ticket.from_station_telecode === WISH.from_station_telecode &&
            hasSiteType(data[i].queryLeftNewDTO) ) {

            result[ticket.station_train_code] = {
                train_no: ticket.train_no,
                secretStr: ticket.secretStr,
                start_time: ticket.start_time,
                station_train_code: ticket.station_train_code,
                to_station_telecode: ticket.to_station_telecode,
                from_station_telecode: ticket.from_station_telecode
            };
        }
    }

    return result;
}

/**
 * 是否有想要的座位
 * @param  {[type]}  queryLeftNewDTO [description]
 * @return {Boolean}                 true-有,false-无
 */
function hasSiteType( queryLeftNewDTO ) {
    var i = 0,
        wantSetTypeArr = WISH.setType;

    if ( wantSetTypeArr.length === 0 ) {
        return true;
    }

    for (i = 0; i < wantSetTypeArr.length; i++) {
        if ( /有|[1-9]/.test(queryLeftNewDTO[ wantSetTypeArr[i] ]) ) {
            return true;
        }
    }

    return false;
}

/**
 * 匹配想要购买的车次
 * @param  {Object} ticketsMap 可购买的所有车次
 * @return {Boolean}           true-匹配到,false-没匹配到
 */
function matchYourTickets(ticketsMap) {
    var i = 0,
        ticket = {},
        wantTrains = WISH.station_train_code;

    console.log(ticketsMap);

    for (i = 0; i < wantTrains.length; i++) {
        ticket = ticketsMap[wantTrains[i]];

        if (typeof ticket !== 'undefined') {
            clearInterval(timer); // 清除定时器
            reserveTicket(ticket); // 预订车票
            setFormData(USER); // 填写用户信息

            return true;
        }
    }

    return false;
}

/**
 * 预订车票
 * @param  {Object} ticket 改车次信息
 */
function reserveTicket(ticket) {
    checkG1234(ticket.secretStr, ticket.start_time, ticket.train_no, ticket.from_station_telecode, ticket.to_station_telecode);
}

/**
 * 填写表单信息
 * @param {Object} user 用户信息对象
 */
function setFormData(user) {
    $('#username').val(user.name);
    $('#password').val(user.password);
}

/**
 * 初始化
 */
function init() {
    // 一开始执行就查询匹配一次
    queryAjax(WISH, function(data) {
        availableTicketsMap = getAvailableTicketsMap(data);
        matchYourTickets(availableTicketsMap);
    });

    // 定时器
    timer = setInterval(function() {
        queryAjax(WISH, function(data) {
            availableTicketsMap = getAvailableTicketsMap(data);
            matchYourTickets(availableTicketsMap);
        });
    }, SEARCH_RATE);
}

init();

// 用于下一个页面
// function buy() {
//  $( '#normalPassenger_0' ).click();
//  $( '#submitOrder_id' ).click();
// }

// buy();

需要配置的信息就是代码开头的 WISH 部分。

<br />


城市 code 查询

至于城市 code 怎么查询,请在官方代码中查找,代码很长很长...链接地址
使用浏览器的 Ctrl + F 查找你所想查找的城市,比如 “杭州东”(注意:杭州和杭州东的 code 不一样),紧接着杭州东后面的字母就是对应的 code :

stationCode.png

建议使用可搜索的城市区间,在官网测试过的;注意大小写是区分的

<br />


思路分析——搜索功能

好了,接下来是思路分析。

先填写搜索条件,点击搜索,查看控制台 Network 里面的 XHR 记录,也就是发送的 Ajax 了:

searchLog.png

可以发现,每次点击搜索,都会发送两个 Ajax 请求:

  • /otn/leftTicket/log:这个看名字像是记录搜索日志,不是很清楚
  • /otn/leftTicket/queryA: 这个就是查询了

两个 Ajax 的参数都是一致的:

queryString.png

就是我们所填写的搜索参数,格式化后如下:

{
    'leftTicketDTO.train_date': '2017-01-24', // 出发日
    'leftTicketDTO.from_station': 'HGH', // 出发地
    'leftTicketDTO.to_station': 'NXG', // 目的地
    'purpose_codes': 'ADULT' // 普通(成年人)
}

所以我们可以使用这些参数来模拟搜索。

Tips:
并且发现查询结果只和 出发日期出发地目的地乘客类型(普通、学生) 有关,和车次的筛选条件无关:

不填写筛选条件(返回 92 条数据)

noFilter.png

填写筛选条件(返回 92 条数据)
hasFilter.png

所以车次的过滤,是在浏览器端完成的

<br />


思路分析——预订

找个可以预订的车次,查看 预定 按钮信息(就是控制台左上角的箭头,先点击它,再去点按钮):

viewEle.png

可以看到使用的是 onClick 事件,执行函数是 checkG1234(顺带多观察了几个可预定的车次,发现预订按钮点击执行的函数名都叫做 checkG1234):

clickHandleFunction.png

checkG1234 格式化:

checkG1234(
    'c7JA6pPfpAnWCp5RcvN8Ks8WYk68Wv0ZxdytFe8c8vmN64VfPKCIdxXSLTCBd1Gc%2F9zXT5i7gaZy%0ATr4SWbEOeZwttRjRaGMfOHoKAnxoDJwtiEm8Ittvm7BZZu1qaJcZqFqg2EbdT%2FyissJoFkqEzenT%0AA%2F1UTQV%2BHLjKKEM6MT9SsmljBbjFH3SAhSavWoWUzjQlLsWofENBOoRg2Hu%2F%2FxSqKmWoLwQ%2F4Ku%2B%0AOZNWfWel2HJQ1Oqn5obNR5mTv8sNkJGaSCO459N4zSjH5Fnv29Nbw207GxdKQGFCVQ%3D%3D',
    '00:35',
    '56000K429760',
    'HZH',
    'NCG'
)

一共有四个参数,前三个不知道是啥,后两个是始发地和目的地。先不管,先去扒扒搜索返回的数据。

<br />


思路分析——返回数据分析

假设 K4297 在查询结果中对应的数据为 data,则将可以将预订函数参数分解:
找两条数据对比一下:

{
    "queryLeftNewDTO": {
        "train_no": "56000K429760",
        "station_train_code": "K4297", // 车次
        "start_station_telecode": "HZH",
        "start_station_name": "杭州",
        "end_station_telecode": "NCG",
        "end_station_name": "南昌",
        "from_station_telecode": "HZH",
        "from_station_name": "杭州",
        "to_station_telecode": "NCG",
        "to_station_name": "南昌",
        "start_time": "00:35",
        "arrive_time": "10:12",
        "day_difference": "0",
        "train_class_name": "",
        "lishi": "09:37",
        "canWebBuy": "Y",
        "lishiValue": "577",
        "yp_info": "0%2BhvfBij8EeRbc3N5OhdLWdJS%2F%2FutFvI",
        "control_train_day": "20300303",
        "start_train_date": "20170124",
        "seat_feature": "W010",
        "yp_ex": "1010",
        "train_seat_feature": "0",
        "train_type_code": "4",
        "start_province_code": "08",
        "start_city_code": "0904",
        "end_province_code": "11",
        "end_city_code": "1104",
        "seat_types": "11",
        "location_code": "H1",
        "from_station_no": "01",
        "to_station_no": "07",
        "control_day": 29,
        "sale_time": "1030",
        "is_support_card": "0",
        "controlled_train_flag": "0",
        "controlled_train_message": "正常车次,不受控",
        "yz_num": "--", // 硬座
        "rz_num": "--", // 软座
        "yw_num": "--", // 硬卧
        "rw_num": "--", // 软卧
        "gr_num": "--", // 高级软卧?
        "zy_num": "有", // 一等座
        "ze_num": "有", // 二等座
        "tz_num": "--", // 特等座
        "gg_num": "--", // ?
        "yb_num": "--", // ?
        "wz_num": "--", // 无座
        "qt_num": "--", // 其它?
        "swz_num": "11" // 商务座
    },
    "secretStr": "'c7JA6pPfpAnWCp5RcvN8Ks8WYk68Wv0ZxdytFe8c8vmN64VfPKCIdxXSLTCBd1Gc%2F9zXT5i7gaZy%0ATr4SWbEOeZwttRjRaGMfOHoKAnxoDJwtiEm8Ittvm7BZZu1qaJcZqFqg2EbdT%2FyissJoFkqEzenT%0AA%2F1UTQV%2BHLjKKEM6MT9SsmljBbjFH3SAhSavWoWUzjQlLsWofENBOoRg2Hu%2F%2FxSqKmWoLwQ%2F4Ku%2B%0AOZNWfWel2HJQ1Oqn5obNR5mTv8sNkJGaSCO459N4zSjH5Fnv29Nbw207GxdKQGFCVQ%3D%3D",
    "buttonTextInfo": "预订"
}

假设这条数据叫做 data,可以发现如下对应关系:

'data.secretStr'  =>  'c7JA6pPfpAnWCp5RcvN8Ks8WYk68Wv0ZxdytFe8c8vmN64VfPKCIdxXSLTCBd1Gc%2F9zXT5i7gaZy%0ATr4SWbEOeZwttRjRaGMfOHoKAnxoDJwtiEm8Ittvm7BZZu1qaJcZqFqg2EbdT%2FyissJoFkqEzenT%0AA%2F1UTQV%2BHLjKKEM6MT9SsmljBbjFH3SAhSavWoWUzjQlLsWofENBOoRg2Hu%2F%2FxSqKmWoLwQ%2F4Ku%2B%0AOZNWfWel2HJQ1Oqn5obNR5mTv8sNkJGaSCO459N4zSjH5Fnv29Nbw207GxdKQGFCVQ%3D%3D',
'data.queryLeftNewDTO.start_time'  =>  '00:35',
'data.queryLeftNewDTO.train_no'  =>  '56000K429760',
'data.queryLeftNewDTO.from_station_telecode'  =>  'HZH',
'data.queryLeftNewDTO.to_station_telecode'  =>  'NCG'

好了,点击预订需要的参数都找全了。

<br />


模拟搜索

这个很简单,定义一个 queryAjax 的方法,传入搜索参数 data 和回调函数 callback:

/**
 * Ajax
 * @param  {Object}   data     搜索参数
 * @param  {Function} callback 回调函数,用于处理返回的数据
 */
function queryAjax( data, callback ) {
    var ajaxData = {
        'leftTicketDTO.train_date': data.train_date,
        'leftTicketDTO.from_station': data.from_station_telecode,
        'leftTicketDTO.to_station': data.to_station_telecode,
        'purpose_codes': data.purpose_codes
    };

    // log, it's no use for me
    $.ajax({
        type: "GET",
        isTakeParam: false,
        beforeSend: function( xhr ) {
            xhr.setRequestHeader("If-Modified-Since", "0");
            xhr.setRequestHeader("Cache-Control", "no-cache");
        },
        url: "/otn/leftTicket/log",
        data: ajaxData,
        timeout: 15000,
        success: function( res ) {}
    });

    // query
    $.ajax({
        type: 'GET',
        isTakeParam: false,
        beforeSend: function( xhr ) {
            xhr.setRequestHeader('If-Modified-Since', '0');
            xhr.setRequestHeader('Cache-Control', 'no-cache');
        },
        url: '/otn/leftTicket/queryA',
        data: ajaxData,
        timeout: 10000,
        success: function( res ) {
            if ( res.status ) {
                callback( res.data );
            }
        }
    });
}

<br />


处理返回的数据

为了便于后期查找出我们需要的,并且可购买的车次,这里使用键值对保存可购买的车次信息:

/**
 * 处理返回的数据
 * @param  {Array} data  返回的所有车次信息
 * @return {Object}      可购买的车次 map
 */
function getAvailableTicketsMap(data) {
    var i = 0,
        ticket = {},
        result = {};

    for (i = 0; i < data.length; i++) {
        ticket = {
            secretStr: data[i].secretStr,
            train_no: data[i].queryLeftNewDTO.train_no,
            start_time: data[i].queryLeftNewDTO.start_time,
            station_train_code: data[i].queryLeftNewDTO.station_train_code,
            to_station_telecode: data[i].queryLeftNewDTO.to_station_telecode,
            from_station_telecode: data[i].queryLeftNewDTO.from_station_telecode
        };

        if (ticket.secretStr !== '' && // or data[i].queryLeftNewDTO.canWebBuy === 'Y'
            ticket.to_station_telecode === WISH.to_station_telecode &&
            ticket.from_station_telecode === WISH.from_station_telecode &&
            hasSiteType(data[i].queryLeftNewDTO) ) {

            result[ticket.station_train_code] = {
                train_no: ticket.train_no,
                secretStr: ticket.secretStr,
                start_time: ticket.start_time,
                station_train_code: ticket.station_train_code,
                to_station_telecode: ticket.to_station_telecode,
                from_station_telecode: ticket.from_station_telecode
            };
        }
    }

    return result;
}

Tips
分析发现,如果车次可以购买,那么 secretStr 的值不为空,并且queryLeftNewDTO.canWebBuy = 'Y'

<br />


匹配座位类型

长度为 0 就是不限制,有卖就买,返回 true;不为 0 就是遍历期望的类型数组,找到有匹配的就返回 true,否则为 false:

/**
 * 是否有想要的座位
 * @param  {[type]}  queryLeftNewDTO [description]
 * @return {Boolean}                 true-有,false-无
 */
function hasSiteType( queryLeftNewDTO ) {
    var i = 0,
        wantSetTypeArr = WISH.setType;

    if ( wantSetTypeArr.length === 0 ) {
        return true;
    }

    for (i = 0; i < wantSetTypeArr.length; i++) {
        if ( /有|[1-9]/.test(queryLeftNewDTO[ wantSetTypeArr[i] ]) ) {
            return true;
        }
    }

    return false;
}

<br />


匹配需要的车次

接下来就是匹配我们需要的车次了,需要传入上述所有可购买的车次:

/**
 * 匹配想要购买的车次
 * @param  {Object} ticketsMap 可购买的所有车次
 * @return {Boolean}           true-匹配到,false-没匹配到
 */
function matchYourTickets( ticketsMap ) {
    var i          = 0,
        ticket     = {},
        wantTrains = WISH.station_train_code;

    console.log( ticketsMap );

    for (i = 0; i < wantTrains.length; i++) {
        ticket = ticketsMap[ wantTrains[i] ];

        if ( typeof ticket !== 'undefined' ) {
            clearInterval( timer ); // 清除定时器
            reserveTicket( ticket ); // 预订车票
            setFormData( USER ); // 填写用户信息

            return true;
        }
    }

    return false;
}

<br />


预订车票

就是调预订的那个方法,传入需要的参数而已:

/**
 * 预订车票
 * @param  {Object} ticket 改车次信息
 */
function reserveTicket( ticket ) {
    checkG1234(ticket.secretStr, ticket.start_time,ticket.train_no, ticket.from_station_telecode, ticket.to_station_telecode);
}

<br />


填写用户信息

这个就是查看登陆表单的结果了,选择器就直接写死了:

/**
 * 填写表单信息
 * @param {Object} user 用户信息对象
 */
function setFormData( user ) {
    $( '#username' ).val( user.name );
    $( '#password' ).val( user.password );
}

Tips:
点击查看元素可以看到表单的用户名、用户密码输入框的 id 名称分别为 'username'、'password'

form.png

图片验证码无解,不知道抢票软件咋弄的,有内部接口?貌似移动端有独立的接口,下次看看。

<br />


初始化

写个定时器自动查询匹配:

/**
 * 初始化
 */
function init() {
    // 一开始执行就查询匹配一次
    queryAjax(WISH, function( data ) {
        availableTicketsMap = getAvailableTicketsMap( data );
        matchYourTickets( availableTicketsMap );
    });

    // 定时器
    timer = setInterval(function() {
        queryAjax(WISH, function( data ) {
            availableTicketsMap = getAvailableTicketsMap( data );
            matchYourTickets( availableTicketsMap );
        });
    }, SEARCH_RATE);
}

<br />


总结

自己开双屏,一边写代码一边看着,挂着刷还行,当然手机 App 更好吧。春运的票好难抢,今天啥都没抢到,就看明天了。抢不到就拿这个挂个一天....

Good Night ~ ~ o(* ̄▽ ̄*)ブ
—— 2016/12/26 By Live

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,381评论 0 17
  • 《裕语言》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 10...
    叶染柒丶阅读 26,384评论 5 19
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,900评论 25 707
  • 坐在登机口处,听到广播里一遍又一遍的晚点通知,很是淡定地掏出手机打开APP开始写作。 恰逢高考遇上端午,我很偶然地...
    心之音阅读 524评论 5 1