Android开发之千古、现在和明天

即如今,拥有着 80% 的市场份额的 Android
是不过主流的手机操作系统。它运行在群的智能手机、平板和任何各种各样的设备及。仅凭这一点,我们是不是可看
 Android 编程是略而轻松的也罢?

多年来及时几乎龙在扶助柠檬圈她底APM系统如哪些收集.Net运行时之各种风波,
这些事件包括线程开始, JIT执行, GC触发等等.
.Net于windows上(NetFramework, CoreCLR)通过ETW(Event Tracing for
Windows), 在linux上(CoreCLR)是透过LTTng跟踪事件.

几年前,Miley Cyrus 还以歌唱着乡村音乐,Justin Bieber
还梳理着他那红的“Bieber”发型,Malcolm 还当 AC/DC 乐队,而而 Android
开发还一对一复杂。Android 开发者对于Android
系统出极简便易行的使还起一样分外堆问题。

ETW的API设计既为过多人指责,
微软推出的类库krabsetw中直指ETW是极差之API而把操作ETW的公文命名为噩梦.hpp.
而且立马首文章遭受, Casey
Muratori解释了为何ETW是不过差之API, 原盖包括:

干什么?嗯,亲爱的读者,问题发出以各种地方:

  • 事件类应用了各项标志(最多只能发出32个), 没有设想到未来底场面
  • 差的接口共用一个坏之构造体, 接口的输入和输出不显眼
  • 于调用者写无论怎么看都是剩下的代码
  • 深受调用者使用魔法数字(而无是供一个朵举值)
  • 命名带有误导性
  • 返回值的义不合并
  • 运用过度复杂, 没有先想吓用例
  • 文档中没完好的以身作则代码, 只能打零碎的代码拼凑

漏洞层出的IDE:你发出没有出尝试过因此同样拿铲子去修复你的汽车?或者你起来着您爷爷的40年前之
Yugo 汽车去管妹?在Android世界面临,对于 Android 开发,我们发一个官
IDE——Eclipse,它产生雷同万分堆问题,在10分钟内保证吃您捉狂。Eclipse ADT
插件对再次多之错综复杂工程以来吧是满漏洞、缓慢而无和谐之。我们针对是颇恶心,祈祷能发奇迹来改进这通。

系统崩溃:Gingerbread (2.3.7)在 Android
系统版本被占据着一定深的市场份额(至少15-20%)。正使您曾知道的,Android
正通过4.0本子(Ice Cream
Sandwich)经历在千丝万缕的翻盖过程。系统出矣初的用户界面元素、新的配备硬件API、新的屏幕密度等等,这就造成了我们须小心地优化以及编制我们的施用来叫在新版本Android和原本子
Android
都能够运行良好。所有的就所有还极大地震慑了俺们的开支过程和导致了重新多之
bug 和 crash,以至于延长了开时间。

暂缓的仿真器:我们要以不同的 Android
系统版本及屏幕尺寸测试我们的用,所以我们须进至少20栽 Android
设备。听起是勿是挺疯狂?好吧,我们会应用仿真器来解决。但是你早就产生没有产生试过因此默认的
Android
仿真器?它的冉冉让人痛心,当您的运用在为部署到你的仿真器的早晚,你会给你自己去数办公楼前停的切削之数来打发时光。

用户界面(UI):Android 应用无聊死了。如果您亵渎看同样目 iOS
应用,你晤面相这些下充满了生活气息而且色彩缤纷。所有的物都是确凿的,动作变,从左到右、从右到左……而我辈的使用是殊的,如果我们想如果加强我们的用户体验,老旧的Gingerbread
会很快抹杀我们的期望以及憧憬。

然而Casey Muratori的篇章对本身扶特别挺,
我光所以了1天时间就描写起了下ETW收集.Net运行时事件之演示代码.
其后我起看什么行使LTTng收集这些事件,
按照我往底经验linux上之类库api通常会比windows的好用, 但LTTng是只章外.

只是这些还是2013之从了。

自第一桩做的事务是去寻觅怎样在c程序里面LTTng的接口,
我打开了他们的文档下一场开始浏览.
敏捷自己意识了她们之文档只说了何等下代码发送事件,
却无另外说明如何用代码接收事件, 我发觉及自身该去押源代码.

一个新开始

主人共都于上年有了移,改变有的这样之快,以至于你老易地去对其的尾随脚步,然后问自己“这还是呀时有的?”更要的凡漫天
Android
生态系统提高了成百上千——我们有了新的硬件(智能手表),新的软件(Gradle,Android
Studio),新的网(Android 5.0 Lollipop)。

每个人犹对这负有贡献——Google、设备制造商、开发者。每个人还来同等之目标。问他俩一如既往的此题材:“OK。现在咱们来平安的系统,十亿划算之以及十亿计的用户——我们怎么才会益简化和加强
Android?我们怎么才会叫开发过程再好?”这虽是 open access和 open source
原则展现的她们之潜力——每个人犹足以做出改变、产生提高、创造新的物的四方。

老大不便列有所有之变化,但本身做了一个列表来排有里面(在我看来)最要紧之变动:

初始化LTTng

应用LTTng跟踪事件首先得创造一个对话, 启用事件和上加上下文参数,
然后启用跟踪, 在命令行里面凡是如此的调用:

lttng create --live
lttng enable-event --userspace --tracepoint DotNETRuntime:GCStart_V2
lttng add-context --userspace --type vpid
lttng add-context --userspace --type vtid
lttng start

lttng这个命令的源代码在github齐,
通过几分钟之觅自身发觉lttng的依次命令的兑现都是保存在本条文件夹下的.
打开create.c继以发现了创建会话调用的是lttng_create_session函数,
lttng_create_session函数可以通过引用lttng.h调用.
重新过了几分钟我写有了第一实施代码

int ret = lttng_create_session_live("example-session", "net://127.0.0.1", 1000000);

运转后就就报错了, 错误是”No session daemon is available”.
原因是lttng-sessiond是程序没有启动,
lttng是经过一个独自服务来保管会话的, 而这个服务得手动启动.

行使独立服务本身没有错, 但是lttng-sessiond此序提供了重重参数,
只要一个独自想跟用户事件的先后启动了是服务并点名了忽略内核事件之参数,
然后另外一个跟踪内核事件之顺序将无克正常运作.
毋庸置疑的做法是使systemd来启动这个服务, 让系统管理员决定就此啊参数,
而休是叫调用者去启动它.

化解这题目无非待简单粗暴的点滴行, 启动时若已经起步过新历程会砸,
没有其他影响:

system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));

现在lttng_create_session_live会回到成功了, 但是还要发现了初的题目,
创的对话是由一个单独的劳务管理的, 即使当前进程退出会话也会见有,
第二潮创的当儿会回到一个业已在的错误.
其一题材跟ETW的题目同样模型一样, 解决方式也同样,
在创立会话前关闭它就足以了.

于是代码变成了这么:

system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));
lttng_destroy_session(SessionName);
int ret = lttng_create_session_live("example-session", "net://127.0.0.1", 1000000);

由此一段时间后, 我为此代码实现了和下令执行一样的效果:

// start processes, won't replace exists
system("lttng-sessiond --daemonize");
std::this_thread::sleep_for(std::chrono::seconds(1));

// create new session
lttng_destroy_session(SessionName);
int ret = lttng_create_session_live(SessionName, SessionUrl, LiveSessionInterval);
if (ret != 0) {
    std::cerr << "lttng_create_session: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// create handle from session
lttng_domain domain = {};
domain.type = LTTNG_DOMAIN_UST;
lttng_handle* handle = lttng_create_handle(SessionName, &domain);
if (handle == nullptr) {
    std::cerr << "lttng_create_handle: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// enable event
lttng_event event = {};
event.type = LTTNG_EVENT_TRACEPOINT;
memcpy(event.name, EventName.c_str(), EventName.size());
event.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL;
event.loglevel = -1;
ret = lttng_enable_event_with_exclusions(handle, &event, nullptr, nullptr, 0, nullptr);
if (ret < 0) {
    std::cerr << "lttng_enable_event_with_exclusions: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// add context
lttng_event_context contextPid = {};
contextPid.ctx = LTTNG_EVENT_CONTEXT_VPID;
ret = lttng_add_context(handle, &contextPid, nullptr, nullptr);
if (ret < 0) {
    std::cerr << "lttng_add_context: " << lttng_strerror(ret) << std::endl;
    return -1;
}

// start tracing
ret = lttng_start_tracing(SessionName);
if (ret < 0) {
    std::cerr << "lttng_start_tracing: " << lttng_strerror(ret) << std::endl;
    return -1;
}

暨此结束是勿是杀粗略? 尽管尚无文档, 但是这些api都是非常简单的api,
看源代码就可测算如何调用.

1.ANDROID STUDIO

俺们最为欢喜的Andorid 开发的 IDE
终于成了安居乐业之1.0版了。我弗见面谈谈太多关于 AS
为什么对开发过程来说是无与伦比好之有关细节,因为我们已经来个别篇登出之博客覆盖了当下无异于主题。我会说
Eclipse ADT 插件都休叫官方赞成采用,我吧强烈建议你管具有的采取迁移到
 Android Studio。向 Google 致敬!

 新Android Studio Logo

收获事件

当报LTTng启用跟踪后, 我还需得到发送至LTTng的波,
在ETW中收获事件是经注册回调获取之:

EVENT_TRACE_LOGFILE trace = { };
trace.LoggerName = (char*)mySessionName.c_str();
trace.EventRecordCallback = (PEVENT_RECORD_CALLBACK)(StaticRecordEventCallback);
trace.BufferCallback = (PEVENT_TRACE_BUFFER_CALLBACK)(StaticBufferEventCallback);
trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME;
TRACEHANDLE sessionHandle = ::OpenTrace(&trace);
if (sessionHandle == INVALID_PROCESSTRACE_HANDLE) {
    // ...
}
ULONG processStatus = ::ProcessTrace(&sessionHandle, 1, nullptr, nullptr);

我寻思lttng有无来这样的机制,
首先自己见状底是lttng.h中的lttng_register_consumer函数,
这个函数的笺注如下:

This call registers an "outside consumer" for a session and an lttng domain.
No consumer will be spawned and all fds/commands will go through the socket path given (socket_path).

翻译出就为会话注册一个外部的买主, 听上去与自之求大像吧?
此函数的亚个参数是一个字符串, 我想见是unix socket, lttng会通过unix
socket发送事件过来.
遂自己形容了这般的代码:

ret = lttng_register_consumer(handle, "/tmp/custom-consumer");

平执行及时报错, 错误是Command undefined, 也不怕是令未定义,
服务端不支持此命令.
透过查找发现lttng的源代码中没外调用这个函数的地方,
也就是说这个函数是独装饰.
看起是办法实行不通.


通过一番招来,
我发现了live-reading-howto夫文档,
里面的内容非常少但可见见使用lttng-relayd本条服务可读取事件.
读取事件时单支持TCP, 使用TCP传输事件数量不仅复杂而效率很没有,
相对ETW直接通过内存传递数据这无疑是个愚蠢的办法.
虽然笨拙但是还是若延续写, 我开看就TCP传输用之凡啊协议.

本着传输协议的诠释文档在live-reading-protocol.txt,
这首文档写的老大不好, 但总比没有好.
lttng-relayd进行互动使用的是一个lttng自己创立的半双工二进制协议,
设计如下:

客户端发送命令于lttng-relayd欲遵守以下的格式

[data_size: unsigned 64 bit big endian int, 命令体大小]
[cmd: unsigned 32 bit big endian int, 命令类型]
[cmd_version: unsigned 32 bit big endian int, 命令版本]
[命令体, 大小是data_size]

出殡命令的宏图无问题, 大部分次之上前制协议都是这么设计的,
问题在于接收命令的设计.
吸收命令的格式完全靠让发送命令的型,
例如LTTNG_VIEWER_CONNECT这命令发送过去会晤接到以下的数:

[viewer_session_id: unsigned 64 bit big endian int, 服务端指定的会话ID]
[major: unsigned 32 bit big endian int, 大版本]
[minor: unsigned 32 bit big endian int, 中版本]
[type: 客户端的类型]

可以看接收的数码未曾数据头, 没有数据头如何决定接受多少多少也?
这就算要求客户端定义之对大小要与服务端完全一致, 一个字段都未能够漏.
服务端在之后的翻新受未能够被返回数据随意添加字段,
返回多少字段需要在发送过来的cmd_version,
保持api的兼容性将会晤十分之麻烦.
目前在lttng中cmd_version举凡一个雁过拔毛字段,
也就是是他俩从未仔细的想过api的更新问题.
没错的做法应该是回数据为该提供一个数据头,
然后许客户端忽略多出来的数据.


圈罢协议后, 我于惦记既用了第二进制协议,
应该也会供一个sdk来减少解析的工作量吧?
透过一番查找找到了一个头文件lttng-viewer-abi.h,
包含了跟lttng-relayd相使用的数据结构体定义.
此腔文件于源代码里面有, 但是却无以LTTng发布之软件包中,
这代表使用它们用复制它到项目里面.
复制别人的源代码到花色中匪可知那么不论,
看了瞬间LTTng的开源协议,
include/lttng/*src/lib/lttng-ctl/*生的文书是LGPL,
其余文件是GPL,
呢便是上面比方将这个腔文件复制到温馨的类中,
自己之型必须使GPL协议开始源
,
不思就此GPL的语只能把其中的情节好一行行重新勾, 还无能够写的太像.

既是是测试就随便这样多矣, 把此腔文件的代码复制过来就从头持续写,
首先是并接受lttng-relayd:

int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
    perror("socket");
    return -1;
}
sockaddr_in address = {};
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_family = AF_INET;
address.sin_port = htons(5344);
ret = connect(fd, (sockaddr*)&address, sizeof(address));
if (ret < 0) {
    perror("connect");
    return -1;
}

连接成后的互动流程在读书者的协议文档以后可以整理如下:

初始化
    客户端发送命令 LTTNG_VIEWER_CLIENT_COMMAND + 构造体 lttng_viewer_connect
    服务端返回构造体 lttng_viewer_connect
    客户端发送命令 LTTNG_VIEWER_CREATE_SESSION + 构造体 lttng_viewer_create_session_response
    服务端返回构造体 lttng_viewer_create_session_response
列出会话
    客户端发送命令 LTTNG_VIEWER_LIST_SESSIONS, 不带构造体
    服务端返回构造体 lttng_viewer_list_sessions + 指定长度的 lttng_viewer_session
附加到会话
    客户端发送命令 LTTNG_VIEWER_ATTACH_SESSION + 构造体 lttng_viewer_attach_session_request
    服务端返回构造体 lttng_viewer_attach_session_response + 指定长度的 lttng_viewer_stream
循环 {
    如果需要获取新的流 {
        客户端发送命令 LTTNG_VIEWER_GET_NEW_STREAMS + 构造体 lttng_viewer_new_streams_request
        服务端返回构造体 lttng_viewer_new_streams_response + 指定长度的 lttng_viewer_stream
    }
    如果需要获取新的元数据(metadata) {
        枚举现存的metadata流列表 {
            客户端发送命令 LTTNG_VIEWER_GET_METADATA + 构造体 lttng_viewer_get_metadata
            服务端返回构造体 lttng_viewer_metadata_packet + 指定长度的payload
        }
    }
    枚举现存的trace流列表 {
        客户端发送命令 LTTNG_VIEWER_GET_NEXT_INDEX + 构造体 lttng_viewer_get_next_index
        服务端返回构造体 lttng_viewer_index
        检查返回的 index.flags, 如果服务端出现了新的流或者元数据, 需要先获取新的流和元数据才可以继续
        客户端发送命令 LTTNG_VIEWER_GET_PACKET + 构造体 lttng_viewer_trace_packet
        服务端返回构造体 lttng_viewer_trace_packet + 指定长度的payload
        根据metadata packet和trace packet分析事件的内容然后记录事件
    }
}

是休是看大复杂?
因为商决定了服务端发给客户端的多寡没有数据头,
所以服务端不克积极推送数据到客户端, 客户端必须主动的失进行轮询.
一旦你放在心上到建筑造体的称呼,
会发现有些构造体后面有request和response而有没,
如果不看上下文只看构造体的名目特别麻烦猜到其的作用.
毋庸置疑的做法是颇具请求与归的结构体名称末尾都添加request和response,
不设错过大概这些字母而浪费思考的时间.


以发送命令和接收构造体我形容了片帮忙函数, 它们并无复杂,
使用TCP交互的次序还见面发生近似之代码:

int sendall(int fd, const void* buf, std::size_t size) {
    std::size_t pos = 0;
    while (pos < size) {
        auto ret = send(fd,
            reinterpret_cast<const char*>(buf) + pos, size - pos, 0);
        if (ret <= 0) {
            return -1;
        }
        pos += static_cast<std::size_t>(ret);
    }
    return 0;
}

int recvall(int fd, void* buf, std::size_t size) {
    std::size_t pos = 0;
    while (pos < size) {
        auto ret = recv(fd,
            reinterpret_cast<char*>(buf) + pos, size - pos, 0);
        if (ret <= 0) {
            return -1;
        }
        pos += static_cast<std::size_t>(ret);
    }
    return 0;
}

template <class T>
int sendcmd(int fd, std::uint32_t type, const T& body) {
    lttng_viewer_cmd cmd = {};
    cmd.data_size = htobe64(sizeof(T));
    cmd.cmd = htobe32(type);
    if (sendall(fd, &cmd, sizeof(cmd)) < 0) {
        return -1;
    }
    if (sendall(fd, &body, sizeof(body)) < 0) {
        return -1;
    }
    return 0;
}

初始化连接的代码如下:

lttng_viewer_connect body = {};
body.major = htobe32(2);
body.minor = htobe32(9);
body.type = htobe32(LTTNG_VIEWER_CLIENT_COMMAND);
if (sendcmd(fd, LTTNG_VIEWER_CONNECT, body) < 0) {
    return -1;
}
if (recvall(fd, &body, sizeof(body)) < 0) {
    return -1;
}
viewer_session_id = be64toh(body.viewer_session_id);

末尾的代码比较干燥我虽概括了,
想看完整代码的好关押这里.


进去循环后会见由lttng-relayd抱两种植中之数目:

  • 伯数据(metadata), 定义了跟踪数据的格式
  • 钉住数据(trace), 包含了风波信息例如GC开始同终止等

得元数据应用的是LTTNG_VIEWER_GET_METADATA命令,
获取到之老大数据内容如下:

Wu@"Jtf@oe/* CTF 1.8 */

typealias integer { size = 8; align = 8; signed = false; } := uint8_t;
typealias integer { size = 16; align = 8; signed = false; } := uint16_t;
typealias integer { size = 32; align = 8; signed = false; } := uint32_t;
typealias integer { size = 64; align = 8; signed = false; } := uint64_t;
typealias integer { size = 64; align = 8; signed = false; } := unsigned long;
typealias integer { size = 5; align = 1; signed = false; } := uint5_t;
typealias integer { size = 27; align = 1; signed = false; } := uint27_t;

trace {
    major = 1;
    minor = 8;
    uuid = "a3df4090-0722-4a74-97a4-81e066406f03";
    byte_order = le;
    packet.header := struct {
        uint32_t magic;
        uint8_t  uuid[16];
        uint32_t stream_id;
        uint64_t stream_instance_id;
    };
};

env {
    hostname = "ubuntu-virtual-machine";
    domain = "ust";
    tracer_name = "lttng-ust";
    tracer_major = 2;
    tracer_minor = 9;
};

clock {
    name = "monotonic";
    uuid = "f397e532-4837-402b-8cc9-700ed92a339d";
    description = "Monotonic Clock";
    freq = 1000000000; /* Frequency, in Hz */
    /* clock value offset from Epoch is: offset * (1/freq) */
    offset = 1514336042565610080;
};

typealias integer {
    size = 27; align = 1; signed = false;
    map = clock.monotonic.value;
} := uint27_clock_monotonic_t;

typealias integer {
    size = 32; align = 8; signed = false;
    map = clock.monotonic.value;
} := uint32_clock_monotonic_t;

typealias integer {
    size = 64; align = 8; signed = false;
    map = clock.monotonic.value;
} := uint64_clock_monotonic_t;

struct packet_context {
    uint64_clock_monotonic_t timestamp_begin;
    uint64_clock_monotonic_t timestamp_end;
    uint64_t content_size;
    uint64_t packet_size;
    uint64_t packet_seq_num;
    unsigned long events_discarded;
    uint32_t cpu_id;
};

struct event_header_compact {
    enum : uint5_t { compact = 0 ... 30, extended = 31 } id;
    variant <id> {
        struct {
            uint27_clock_monotonic_t timestamp;
        } compact;
        struct {
            uint32_t id;
            uint64_clock_monotonic_t timestamp;
        } extended;
    } v;
} align(8);

struct event_header_large {
    enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;
    variant <id> {
        struct {
            uint32_clock_monotonic_t timestamp;
        } compact;
        struct {
            uint32_t id;
            uint64_clock_monotonic_t timestamp;
        } extended;
    } v;
} align(8);

stream {
    id = 0;
    event.header := struct event_header_compact;
    packet.context := struct packet_context;
    event.context := struct {
        integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _vpid;
        integer { size = 32; align = 8; signed = 1; encoding = none; base = 10; } _vtid;
    };
};

event {
    name = "DotNETRuntime:GCStart_V2";
    id = 0;
    stream_id = 0;
    loglevel = 13;
    fields := struct {
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Count;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Depth;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Reason;
        integer { size = 32; align = 8; signed = 0; encoding = none; base = 10; } _Type;
        integer { size = 16; align = 8; signed = 0; encoding = none; base = 10; } _ClrInstanceID;
        integer { size = 64; align = 8; signed = 0; encoding = none; base = 10; } _ClientSequenceNumber;
    };
};

本条头条数据的格式是CTF Metadata,
这个格式看上去像json但是并无是, 是LTTng的店温馨创建的一个文本格式.
babeltrace遭到富含了分析这个文本格式的代码,
但是没有开放任何解析其的接口,
也就是设你想自己分析只好写一个词法分析器.
这些格式其实可以用json表示, 体积不见面多多少,
但是马上局就是发明了一个初的格式增加使用者的负担.
写一个词法分析器需要1天时间跟1000行代码, 这里自己便先行跳了了.


连接下去获取跟踪数据,
使用的凡LTTNG_VIEWER_GET_NEXT_INDEX和LTTNG_VIEWER_GET_PACKET命令.
LTTNG_VIEWER_GET_NEXT_INDEX返回了时流动的offset和可抱之content_size,
这里的content_size单位凡位(bit),
也就算是用除以8才方可算出可以获多少字节,
关于content_size的单位LTTng中无其他文档和注释说明它们是各项,
只发一个测试代码里面的某行写了/ CHAR_BIT.
使用LTTNG_VIEWER_GET_PACKET命令,
传入offset和content_size/8可以博得跟踪数据(如果不/8会晤得到到剩余的数目要返回ERR).
实际返回的跟踪数据如下:

000000: c1 1f fc c1 29 82 6b fe 24 10 4c 6b 97 91 4d c3  ....).k.$.Lk..M.
000010: ed d4 41 8f 00 00 00 00 03 00 00 00 00 00 00 00  ..A.............
000020: 92 91 49 96 08 0a 00 00 07 a0 58 b9 08 0a 00 00  ..I.......X.....
000030: 50 05 00 00 00 00 00 00 00 80 00 00 00 00 00 00  P...............
000040: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000050: 03 00 00 00 1f 00 00 00 00 92 91 49 96 08 0a 00  ...........I....
000060: 00 e1 1b 00 00 03 00 00 00 02 00 00 00 01 00 00  ................
000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1f  ................
000080: 00 00 00 00 4d ae a7 af 08 0a 00 00 e1 1b 00 00  ....M...........
000090: 04 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00  ................
0000a0: 00 00 00 00 00 00 00 00 00 00                    ..........

跟数据的格式是CTF Stream
Packet,
也是一个自定义的第二前行制格式, 需要般配元数据解析.
babeltrace着一律没开放解析其的接口(有python
binding但是从未解析数据的函数), 也便是要协调写二前进制数据解析器.

操作LTTng + 和relayd通讯 + 元数据词法分析器 +
跟踪数据解析器全部加以起来预计得2000行代码,
而这总体应用ETW只所以了100多行代码.
糟糕之设计, 复杂的运用, 落后的文档, 各种各样的自定义协议和多少格式,
不提供SDK把LTTng打招了一个比ETW更麻烦用之跟踪系统.
目前在github上LTTng只发生100多星而babeltrace只生20大抵,
也证实了没有多少人口在用它们.
自身不了解怎么CoreCLR要就此LTTng, 但欣慰的凡CoreCLR
2.1碰头出新的跟机制EventPipe,
到时候可重新简单的实现跨越平台捕获CoreCLR跟踪事件.

自身时勾勒的调用ETW的代码放在了这里,
调用LTTng的代码放在了这里,
有趣味的足去参考.

2.GRADLE

Gradle 是工程自动化工具,它曾代替 Apche Ant 成为 Android
应用关键的构建系统。它以 Android
开发者中杀流行。因为我们经过她几乎可自动化所有事务——从以我们的应用区分成不同风格、正确配置签名等等

因而,他成了同一系列之“管理”工具,我们之所以来定义及保障我们的工安装。Gradle
也是测试自动化库和电动构建服务器大量增长之严重性缘由。测试自动化库和自行构建服务器又吃
 Android
系统带来了不停集成(CI)开发进程。但是未是一切都是那么让人乐观——Gradle也在实行进度高达中批评。在纷繁工程方
Gradle 也的确很缓慢,但咱愿意此问题会见于连下的本子与批发中解决。

教训

顶差之API(ETW)和更不比之API(LTTng)都扣留了了, 那么该如何避免他们的错误,
编写一个好之API呢?

Casey
Muratori关系的训诫有:

3.LOLLIPOP

Google 说 Lollipop 是自人类诞生以来 Android 系统最可怜之晋级,Google
说的正确性。 Android
的每个片都发生相应的修改及升级换代,但是咱也远非看到开发者对这些改变有什么的感应。虽然用旧设备升级至
Lollipop 还有不少题材,但是咱盼望这会于联网下去的版中化解。

统筹API的第一漫漫以及第二修规则: “永远都自编写用例开始”

统筹一个API时, 首先使做的凡立在调用者的立足点, 想想调用者需要什么,
如何才能够无限简单易行的上这个需求.
编纂一个粗略的用例代码永远是规划API中务必的同一步.
不要了多的失去想内部贯彻, 如果内部贯彻机制让API变得复杂,
应该想办法去抽象它.

4.LOLLIPOP 的外在—— MATERIAL DESIGN

于此给作 Material Design 的金光闪闪的新 Android UI
有成千上万如描绘。这是近期几乎年Android
系统最根本创新点之一,它完全移了咱利用之观感。我最欣赏 Material
Design
的是它们彻底改变了用户体验条件——一切都紧要。即使是一线的细节也未可知为忽视。我们得对每个用户交互、点击、触摸等做出响应。因为,这恰恰使
Google
所说的,这些动作都是有意义的。我们要运用黑体、拥抱新的活的情调、每一样步用动画片、大书,简单地说,我们只要为我们的利用为命。Material
Design 同也完全符合 Android
生态系统,适应各种不同之屏幕尺寸。这为便是胡咱们的运是相似的,但是当不同之平台具有不等同的外观。

 Material Design 动画

考虑到未来之恢弘

因为需求会不断变化, 设计API的时节该也前途底变动预留空间,
保证为后相当性.
例如ETW中监听的风波类.aspx)使用了号标记,
也不怕是参数是32号时最为多只能发出32种植事件,
考虑到未来生更多事件应该把事件类型定义为连日来的数值并提供额外的API启用事件.
现时有发生众多接口在计划时会考虑到本, 例如用v1和v2区分,
这是一个颇好的策略.

5.LOLLIPOP 的内在—— ART

每个人都当座谈设计、UI、UI
元素、动画、色彩······,但是咱是开发者,我们感谢兴趣之是外表之下的物。而且,哇!!!这招擎真是美极了:ART,新的运行网。为了记录,ART
并无是什么新物—它被介绍也 Kitkat 上附有的运作系统。通过引入
Lollipop,它了代表了 Dalvik,成为主系统。由于众多原因 ART
是高大的,但我独自提及中有数接触:

同、它采用
AOT(ahead-of-time)编译,这象征她将高中级语言(Dalvik字节码)编译成体系二进制码。这就是造成我们采取还缺少的实践时、更少之
CPU 占用、更不见之电池组消耗。在单,安装过程吧就还丰富。

第二、他供 multidex 支持。Dalvik dex
文件发出只至关重要瑕疵—它们只能分包65,356种艺术。我们务必组织好我们的
Android
应用为要艺术毫无超过这界定。尽管此数字也许看起来十分死,但是只要你把
Google Play
服务(几乎每个应用还用)算在内,再添加一些标函数库,你不怕能自由超过这个范围。ART
为同等栽突破了字节码以多 dex 文件包到一个单独的 APK
的措施组织而的采用。

明白接口的输入和出口

不用为省代码去叫一个接口接收或者返回多余的信息.
以ETW中许多接口都共同用了一个大构造体EVENT_TRACE_PROPERTIES,
调用者很麻烦下手明白接口使用了建造造体里面的焉值, 又影响了怎么值.
筹API时应当明白接口的目的, 让接口接收及归必要且极其少的信息.

6.ANDROID 无处不以

咱们开被智能手表、电视、汽车开发以,为什么而以这个已呢?如果你因于你的房间,喝在了千篇一律海热咖啡,花一两分钟看看你的四周。在连下的即时几乎年而可能会见到至少五样运行在
Android
系统的设备—电视、笔记本、平板、相机、自行车、厨房电器、恒温器、汽车等等。Android
开始作为同栽试验,它叫证实能够运转在其余一个持有小型微处理器的东西上面。

提供整体的以身作则代码

针对调用者来说, 100实践的示范代码通常比较1000执之文档更有意义.
因接口的设计者和调用者拥有的知识量通常不对等,
调用者在未曾看出实际的例证之前, 很可能无法了解设计者编写的文档.

7.智能手机质量的滋长

智能手机还是Android
系统的核心装备。长期以来,智能手机的整品质有题目。老旧的Android
设备比较老旧的 iPhone 更臭更慢——iOS
通常感觉还通畅。对于那些被众中华制造商等养的跌价设备来说,这种感受越来越如此。

有幸地是,Android
智能手机的质地和速度稳步提升,所以今天咱们有过多副每个人之预算及需要的新装置。如果您想有所一致令手机,它装有充分高之照相机分辨率、优秀的宏图、强大的微处理器和电量,这不是个问题——我们且发出。

自我个人太喜爱的品牌是摩托罗拉,它的手机—Moto X、Moto G及Moto E
都抱有姣好的线,同时为确实有充分好的性价比。而当又,Google
的一个团伙正力于模块化手机的开销。Project Ara 目标在彻底动摇 Android
世界,如果周进行顺利,它发出或会见过来人们眼前。

Project Ara 部分

并非使魔法数字

旋即是众多接口都会犯的荒谬, 例如ETW中决定事件附加的音讯时, 1代表时间戳,
2表示系统时, 3代表CPU周期计数.
一经您得传递具有某种意义的数字被接口,
请务必于SDK中为该数字定义枚举类型.

自于LTTng中收到到之训诫有:

下同样步何去何从?

写文档

99%的调用者没有看源代码的兴或能力,
不写文档没有丁见面懂怎样去调动用而的接口.
现在时有发生诸多自动生成文档的工具, 用这些家伙得以抽过多的工作量,
但是公照样当手动去编写一个入门的文档.

远离JAVA

咱既解决了 IDE 和网版本的绝大多数题材,我们不怕可关注 Android
其他地方的问题。

恕我直言,在 Android 开发极核心之题目备受极度要之题材是 Java。对不起,Java
Harmony,基于 Java 7 或 Java6,但它们不是
Java。不要被自己放错——我坚信Java是一样门户好的编程语言,但是自耶看我们是上打破常规了。我们要开始找另外一宗编程语言来代表
Java 成为 Android 开发的核心语言。

瞧我们最为要害的竞争者—Apple。他们早就介绍了同流派新的语言,叫做
Swift,它结合了累单其他语言(如 Python、Ruby 或
C#)的尽优异特征。我们曾比 iOS
开发者开发同应用得还多的年华,而这会如我们再慢。

即时就算是怎咱们得新物的加盟了。我们已经生矣关于哪个语言会取代Java的有些想方设法。我道是
Groovy。它的语法与 Java 非常相似(实际上,它是冲 Java
的),我们呢有一对做事原型了。同时,也不用忘记了其是 Gradle
的主语言——所以,为什么未把它用来Android 开发也?或者可能是
Scala(它好快捷获得新用户),又或者是 Kotlin(Jake Wharton
最近写了同样首特别好之有关用于 Android 的 Kotlin 的概论)?

并非轻易之夺创造一个商量

创立一个新的合计表示要编制新的代码去分析其,
而且每个程序语言都使双重编辑一潮.
只有你不行有生气, 可以啊主流的程序语言都提供一个SDK, 否则不引进这样做.
博种还提供了REST API, 这是坏好的主旋律,
因为几每个语言都生现成的类库可以便宜地调用REST API.

数据库管理变得重复好

自而指出另一个问题—数据库管理 API。如果您重新同不行亵渎
Andoird,看一样肉眼我们的竞争对手—iOS(核心数据,将越加准确)——你晤面见到他俩的确具有美好之艺术以及创办数据库对象的GUI
和 CRUD 方法,数据库变化监听器。但是倘若你回头看下默认的 Android API
——我们还未曾离乡背井写那些大地震慑我们出过程的 SQL 命令。

调剂 SQL
错误不是一律项好之从事—它充分耗时间,我们为远非翻动数据库数据的GUI。尽管也闹一部分对的
ORM 库(如 GreenDAO、ActiveAndroid 或
SugarORM),但是它还生好之题目。我自从无针对其统统满意—他们只要无是采用十分复杂,要不就是丢一些东西(如数据库改变监听器)。我顾到了
Realm for Android 和
DBFlow,我望她们会解决自身有所的题目又减少执行时。

谨慎的去定义二进制协议

概念一个吓的二进制协议要分外特别的造诣, LTTng定义之协议明显考虑的最为少.
推介的做法是醒目区分请求与应, 请求和报还当发生一个含长度的头,
支持都双工通信.
比方你想设计一个二进制协议,
强烈建议参考Cassandra数据库的磋商文档,
这个协议无论是设计或文档都是五星级的水平.
但若您莫对传输性能有老严苛的求, 建议采取现成的协议加json或者xml.

结论

Android
以过去底几乎年发生了高大的转。它已经打一个简练的智能手机系统发展为一个支持各种装备的雄强系统。时间会见告诉我们
Android
将会见成什么样。谁知道呀天我们会无会见甚至可以用它来叫核聚变反应堆编程,或者为”终结者“编程。PS.
显然终结者更好玩。

立刻是本人课余时间的翻译,错误非常多,还请耐心指出,谢谢!

初稿链接:https://www.infinum.co/the-capsized-eight/articles/the-past-present-and-future-of-android-development

非苟错过创造一个DSL(Domain-specific language)

此处自己从未写轻易, 如果你有一个数据结构需要代表成文本,
请使用还通用的格式.
LTTng表示初数据常常采取了一个要好创立的DSL,
但里面的情节用json表示为无见面多多少体积,
也就是说创造一个DSL没有外好处.
浅析DSL需要自己编排词法分析器,
即使是更老到的程序员编写一个吧欲过多时(包含单元测试更多),
如果使用json等通用格式那么编写解析的代码只待几分钟.

形容以末

尽管如此这篇文章将LTTng批评了平等洋,
但这说不定是目前海内外唯一一篇涉嫌如何通过代码调用LTTng和收受事件数量的文章.
可望看了这首稿子的计划性API时大都为调用者着想,
你偷懒省下几分钟数会导致别人浪费几龙之时间.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图