Material-Design-组件之-CollapsingToolbarLayout

CollapsingToolbarLayout 主要用于实现一个可折叠的标题栏,一般作为 AppBarLayout 的子 View 来使用,下面总结一下 CollapsingToolbarLayout 的使用。 Material Design 系列文章: Material Design组件之FloatingActionButtonMaterial Design组件之AppBarLayout常用属性//是否显示标题app:titleEnabled="true"//标题内容app:title="CollapsingToolbarLayout"//扩展后Title的位置app:expandedTitleGravity="left|bottom"//收缩后Title的位置app:collapsedTitleGravity="left"//CollapsingToolbarLayout收缩后Toolbar的背景颜色app:contentScrim ="@color/colorPrimary"//CollapsingToolbarLayout收缩时颜色变化的持续时间app:scrimAnimationDuration="1200"//颜色从可见高度的什么位置开始变化app:scrimVisibleHeightTrigger="150dp"//状态颜色变化(Android 5.0)app:statusBarScrim="@color/colorAccent"//设置滑动组件与手势之间的关系app:layout_scrollFlags="scroll|exitUntilCollapsed"对于 Title 当折叠布局完全可见时 Title 变大,可折叠布局随着向上滑动可见范围变小 Title 也变小,可以通过如下方式设置 Title 的颜色,具体如下: //设置标题ctlCollapsingLayout.setTitle("CollapsingToolbarLayout");//设置CollapsingToolbarLayout扩展时的颜色ctlCollapsingLayout.setExpandedTitleColor(Color.parseColor("#ffffff"));//设置CollapsingToolbarLayout收缩时的颜色ctlCollapsingLayout.setCollapsedTitleTextColor(Color.parseColor("#46eada"));这个 Title 的颜色渐变过程由 CollapsingToolbarLayout 完成,当然其他部分属性也可以在代码中设置。 两个标志位单独在说一下两个重要属性,可以作为一个标志位来记: layout_scrollFlagslayout_collapseModelayout_scrollFlags:一般使用 CoordinatorLayout、AppBarLayout等这些组件构成的界面,一般都有一个滚动视图,如 NestedScrollView,滚动视图一般设置了系统默认的 Behavior,具体如下: //设置layout_behavior属性<android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> ...</android.support.v4.widget.NestedScrollView>要随着滚动视图移动的组件,如 ToolBar、CollapsingToolbarLayout 等需要设置 layout_scrollFlags 属性来指定不同的动作,关于 layout_scrollFlags 值的具体含义请参考之前的一篇文章: Material Design 组件之 AppBarLayout 。 layout_collapseMode:layout_collapseMode 有两个值可以选择,如果设置了 pin 的 View 会随着页面向上滚动固定到顶部,如果设置了 parallax 的 View 会随着页面的滚动而滚动,此时可以结合另一个属性 layout_collapseParallaxMultiplier 形成视差滚动的效果。 CollapsingToolbarLayout 的介绍就到此为止,没有案例当然是不可以,下面是一个简单的使用案列,布局如下: <?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.manu.materialdesignsamples.samples.SampleCollapsingToolbarLayoutActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="200dp"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/ctlCollapsingLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:titleEnabled="true" app:title="CollapsingToolbarLayout" app:expandedTitleGravity="left|bottom" app:collapsedTitleGravity="left" app:contentScrim ="@color/colorPrimary" app:scrimAnimationDuration="1200" app:scrimVisibleHeightTrigger="150dp" app:statusBarScrim="@color/colorAccent" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="0.6" android:background="@drawable/ic_collapsing_title"/> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="70dp" android:minHeight="?attr/actionBarSize" app:layout_collapseMode="pin"> </android.support.v7.widget.Toolbar> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v7.widget.RecyclerView android:id="@+id/rvCollapsing" android:layout_width="match_parent" android:layout_height="match_parent"/> </android.support.v4.widget.NestedScrollView></android.support.design.widget.CoordinatorLayout>显示效果下面是显示效果,具体如下: ...

May 14, 2019 · 1 min · jiezi

Binder机制情景分析之C服务应用

一. 概述这里只讲下binder的实现原理,不牵扯到android的java层是如何调用; 涉及到的会有ServiceManager,led_control_server和test_client的代码,这些都是用c写的.其中led_control_server和test_client是 仿照bctest.c写的; 在linux平台下运行binder更容易分析binder机制实现的原理(可以增加大量的log,进行分析);在Linux运行时.先运行ServiceManager,再运行led_control_server最后运行test_client;1.1 Binder通信模型Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及binder驱动,其中ServiceManager用于管理系统中的各种服务。1.2 运行环境本文中的代码运行环境是在imx6ul上跑的,运行的是linux系统,内核版本4.10(非android环境分析);1.3 文章代码文章所有代码已上传https://github.com/SourceLink…二. ServiceManager涉及到的源码地址:frameworks/native/cmds/servicemanager/sevice_manager.c frameworks/native/cmds/servicemanager/binder.c frameworks/native/cmds/servicemanager/bctest.cServiceManager相当于binder通信过程中的守护进程,本身也是个binder服务、好比一个root管理员一样; 主要功能是查询和注册服务;接下来结合代码从main开始分析下serviceManager的服务过程;2.1 main源码中的sevice_manager.c中主函数中使用了selinux,为了在我板子的linux环境中运行,把这些代码屏蔽,删减后如下:int main(int argc, char **argv){ struct binder_state bs; bs = binder_open(1281024); ① if (!bs) { ALOGE(“failed to open binder driver\n”); return -1; } if (binder_become_context_manager(bs)) { ② ALOGE(“cannot become context manager (%s)\n”, strerror(errno)); return -1; } svcmgr_handle = BINDER_SERVICE_MANAGER; binder_loop(bs, svcmgr_handler); ③ return 0;}①: 打开binder驱动(详见2.2.1) ②: 注册为管理员(详见2.2.2) ③: 进入循环,处理消息(详见2.2.3)从主函数的启动流程就能看出sevice_manager的工作流程并不是特别复杂; 其实client和server的启动流程和manager的启动类似,后面再详细分析;2.2 binder_openstruct binder_state *binder_open(size_t mapsize){ struct binder_state *bs; struct binder_version vers; bs = malloc(sizeof(*bs)); if (!bs) { errno = ENOMEM; return NULL; } bs->fd = open("/dev/binder", O_RDWR); ① if (bs->fd < 0) { fprintf(stderr,“binder: cannot open device (%s)\n”, strerror(errno)); goto fail_open; } if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) || ② (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) { fprintf(stderr, “binder: driver version differs from user space\n”); goto fail_open; } bs->mapsize = mapsize; bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); ③ if (bs->mapped == MAP_FAILED) { fprintf(stderr,“binder: cannot map device (%s)\n”, strerror(errno)); goto fail_map; } return bs;fail_map: close(bs->fd);fail_open: free(bs); return NULL;}①: 打开binder设备 ②: 通过ioctl获取binder版本号 ③: mmp内存映射这里说明下为什么binder驱动是用ioctl来操作,是因为ioctl可以同时进行读和写操作;2.2 binder_become_context_managerint binder_become_context_manager(struct binder_state *bs){ return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);}还是通过ioctl请求类型BINDER_SET_CONTEXT_MGR注册成manager;2.3 binder_loopvoid binder_loop(struct binder_state *bs, binder_handler func){ int res; struct binder_write_read bwr; uint32_t readbuf[32]; bwr.write_size = 0; bwr.write_consumed = 0; bwr.write_buffer = 0; readbuf[0] = BC_ENTER_LOOPER; binder_write(bs, readbuf, sizeof(uint32_t)); ① for (;;) { bwr.read_size = sizeof(readbuf); bwr.read_consumed = 0; bwr.read_buffer = (uintptr_t) readbuf; res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); ② if (res < 0) { ALOGE(“binder_loop: ioctl failed (%s)\n”, strerror(errno)); break; } res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func); ③ if (res == 0) { ALOGE(“binder_loop: unexpected reply?!\n”); break; } if (res < 0) { ALOGE(“binder_loop: io error %d %s\n”, res, strerror(errno)); break; } }}①: 写入命令BC_ENTER_LOOPER通知驱动该线程已经进入主循环,可以接收数据; ②: 先读一次数据,因为刚才写过一次; ③: 然后解析读出来的数据(详见2.2.4);binder_loop函数的主要流程如下: 2.4 binder_parseint binder_parse(struct binder_state *bs, struct binder_io *bio, uintptr_t ptr, size_t size, binder_handler func){ int r = 1; uintptr_t end = ptr + (uintptr_t) size; while (ptr < end) { uint32_t cmd = *(uint32_t ) ptr; ptr += sizeof(uint32_t);#if TRACE fprintf(stderr,"%s:\n", cmd_name(cmd));#endif switch(cmd) { case BR_NOOP: break; case BR_TRANSACTION_COMPLETE: / check服务 */ break; case BR_INCREFS: case BR_ACQUIRE: case BR_RELEASE: case BR_DECREFS:#if TRACE fprintf(stderr," %p, %p\n", (void *)ptr, (void *)(ptr + sizeof(void )));#endif ptr += sizeof(struct binder_ptr_cookie); break; case BR_SPAWN_LOOPER: { / create new thread / //if (fork() == 0) { //} pthread_t thread; struct binder_thread_desc btd; btd.bs = bs; btd.func = func; pthread_create(&thread, NULL, binder_thread_routine, &btd); / in new thread: ioctl(BC_ENTER_LOOPER), enter binder_looper */ break; } case BR_TRANSACTION: { struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr; if ((end - ptr) < sizeof(*txn)) { ALOGE(“parse: txn too small!\n”); return -1; } if (func) { unsigned rdata[256/4]; struct binder_io msg; struct binder_io reply; int res; bio_init(&reply, rdata, sizeof(rdata), 4); ① bio_init_from_txn(&msg, txn); res = func(bs, txn, &msg, &reply); ② binder_send_reply(bs, &reply, txn->data.ptr.buffer, res); ③ } ptr += sizeof(*txn); break; } case BR_REPLY: { struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr; if ((end - ptr) < sizeof(txn)) { ALOGE(“parse: reply too small!\n”); return -1; } binder_dump_txn(txn); if (bio) { bio_init_from_txn(bio, txn); bio = 0; } else { / todo FREE BUFFER */ } ptr += sizeof(*txn); r = 0; break; } case BR_DEAD_BINDER: { struct binder_death *death = (struct binder_death *)(uintptr_t) *(binder_uintptr_t *)ptr; ptr += sizeof(binder_uintptr_t); death->func(bs, death->ptr); break; } case BR_FAILED_REPLY: r = -1; break; case BR_DEAD_REPLY: r = -1; break; default: ALOGE(“parse: OOPS %d\n”, cmd); return -1; } } return r;}①: 按照一定的格式初始化rdata数据,请注意这里rdata是在用户空间创建的buf; ②: 调用设置进来的处理函数svcmgr_handler(详见2.2.5); ③: 发送回复信息;这个函数我们只重点关注下BR_TRANSACTION其他的命令含义可以参考表格A;2.5 svcmgr_handlerint svcmgr_handler(struct binder_state *bs, struct binder_transaction_data *txn, struct binder_io *msg, struct binder_io *reply){ struct svcinfo *si; uint16_t *s; size_t len; uint32_t handle; uint32_t strict_policy; int allow_isolated; //ALOGI(“target=%x code=%d pid=%d uid=%d\n”, // txn->target.handle, txn->code, txn->sender_pid, txn->sender_euid); if (txn->target.handle != svcmgr_handle) return -1; if (txn->code == PING_TRANSACTION) return 0; // Equivalent to Parcel::enforceInterface(), reading the RPC // header with the strict mode policy mask and the interface name. // Note that we ignore the strict_policy and don’t propagate it // further (since we do no outbound RPCs anyway). strict_policy = bio_get_uint32(msg); ① s = bio_get_string16(msg, &len); if (s == NULL) { return -1; } if ((len != (sizeof(svcmgr_id) / 2)) || ② memcmp(svcmgr_id, s, sizeof(svcmgr_id))) { fprintf(stderr,“invalid id %s\n”, str8(s, len)); return -1; } switch(txn->code) { ③ case SVC_MGR_GET_SERVICE: case SVC_MGR_CHECK_SERVICE: s = bio_get_string16(msg, &len); if (s == NULL) { return -1; } handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid); ④ if (!handle) break; bio_put_ref(reply, handle); return 0; case SVC_MGR_ADD_SERVICE: s = bio_get_string16(msg, &len); if (s == NULL) { return -1; } handle = bio_get_ref(msg); allow_isolated = bio_get_uint32(msg) ? 1 : 0; if (do_add_service(bs, s, len, handle, txn->sender_euid, ⑤ allow_isolated, txn->sender_pid)) return -1; break; case SVC_MGR_LIST_SERVICES: { uint32_t n = bio_get_uint32(msg); if (!svc_can_list(txn->sender_pid)) { ALOGE(“list_service() uid=%d - PERMISSION DENIED\n”, txn->sender_euid); return -1; } si = svclist; while ((n– > 0) && si) ⑥ si = si->next; if (si) { bio_put_string16(reply, si->name); return 0; } return -1; } default: ALOGE(“unknown code %d\n”, txn->code); return -1; } bio_put_uint32(reply, 0); return 0;}①: 获取帧头数据,一般为0,因为发送方发送数据时都会在数据最前方填充4个字节0数据(分配数据空间的最小单位4字节); ②: 对比svcmgr_id是否和我们原来定义相同#define SVC_MGR_NAME “linux.os.ServiceManager”(我改写了); ③: 根据code 做对应的事情,就想到与根据编码去执行对应的fun(client请求服务后去执行服务,service也是根据不同的code来执行。接下来会举例说明);、④: 从服务名在server链表中查找对应的服务,并返回handle(详见2.2.6); ⑤: 添加服务,一般都是service发起的请求。将handle和服务名添加到服务链表中(这里的handle是由binder驱动分配); ⑥: 查找server_manager中链表中第n个服务的名字(该数值由查询端决定);2.6 do_find_serviceuint32_t do_find_service(struct binder_state *bs, const uint16_t *s, size_t len, uid_t uid, pid_t spid){ struct svcinfo *si; if (!svc_can_find(s, len, spid)) { ① ALOGE(“find_service(’%s’) uid=%d - PERMISSION DENIED\n”, str8(s, len), uid); return 0; } si = find_svc(s, len); ② //ALOGI(“check_service(’%s’) handle = %x\n”, str8(s, len), si ? si->handle : 0); if (si && si->handle) { if (!si->allow_isolated) { ③ // If this service doesn’t allow access from isolated processes, // then check the uid to see if it is isolated. uid_t appid = uid % AID_USER; if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) { return 0; } } return si->handle; ④ } else { return 0; }}①: 检测调用进程是否有权限请求服务(这里用selinux管理权限,为了让代码可以方便允许,这里面的代码有做删减); ②: 遍历server_manager服务链表; ③: 如果binder服务不允许服务从沙箱中访问,则执行下面检查; ④: 返回查询到handle;do_find_service函数主要工作是搜索服务链表,返回查找到的服务2.7 do_add_serviceint do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle, uid_t uid, int allow_isolated, pid_t spid){ struct svcinfo *si; //ALOGI(“add_service(’%s’,%x,%s) uid=%d\n”, str8(s, len), handle, // allow_isolated ? “allow_isolated” : “!allow_isolated”, uid); if (!handle || (len == 0) || (len > 127)) return -1; if (!svc_can_register(s, len, spid)) { ① ALOGE(“add_service(’%s’,%x) uid=%d - PERMISSION DENIED\n”, str8(s, len), handle, uid); return -1; } si = find_svc(s, len); ② if (si) { if (si->handle) { ALOGE(“add_service(’%s’,%x) uid=%d - ALREADY REGISTERED, OVERRIDE\n”, str8(s, len), handle, uid); svcinfo_death(bs, si); } si->handle = handle; } else { ③ si = malloc(sizeof(si) + (len + 1) * sizeof(uint16_t)); if (!si) { ALOGE(“add_service(’%s’,%x) uid=%d - OUT OF MEMORY\n”, str8(s, len), handle, uid); return -1; } si->handle = handle; si->len = len; memcpy(si->name, s, (len + 1) * sizeof(uint16_t)); si->name[len] = ‘\0’; si->death.func = (void) svcinfo_death; si->death.ptr = si; si->allow_isolated = allow_isolated; si->next = svclist; svclist = si; } ALOGI(“add_service(’%s’), handle = %d\n”, str8(s, len), handle); binder_acquire(bs, handle); ④ binder_link_to_death(bs, handle, &si->death); ⑤ return 0;}①: 判断请求进程是否有权限注册服务; ②: 查找ServiceManager的服务链表中是否已经注册了该服务,如果有则通知驱动杀死原先的binder服务,然后更新最新的binder服务; ③: 如果原来没有创建该binder服务,则进行一系列的赋值,再插入到服务链表的表头; ④: 增加binder服务的引用计数; ⑤: 告诉驱动接收服务的死亡通知;2.8 调用时序图从上面分析,可以知道ServiceManager的主要工作流程如下: 三. led_control_server3.1 mainint main(int argc, char **argv) { int fd; struct binder_state bs; uint32_t svcmgr = BINDER_SERVICE_MANAGER; uint32_t handle; int ret; struct register_server led_control[3] = { ① [0] = { .code = 1, .fun = led_on } , [1] = { .code = 2, .fun = led_off } }; bs = binder_open(1281024); ② if (!bs) { ALOGE(“failed to open binder driver\n”); return -1; } ret = svcmgr_publish(bs, svcmgr, LED_CONTROL_SERVER_NAME, led_control); ③ if (ret) { ALOGE(“failed to publish %s service\n”, LED_CONTROL_SERVER_NAME); return -1; } binder_set_maxthreads(bs, 10); ④ binder_loop(bs, led_control_server_handler); ⑤ return 0;}①: led_control_server提供的服务函数; ②: 初始化binder组件( 详见2.2); ③: 注册服务,svcmgr是发送的目标, LED_CONTROL_SERVER_NAME注册的服务名, led_control注册的binder实体; ④: 设置创建线程最大数(详见3.5); ⑤: 进入线程循环(详见2.3);3.2 svcmgr_publishint svcmgr_publish(struct binder_state *bs, uint32_t target, const char *name, void *ptr){ int status; unsigned iodata[512/4]; struct binder_io msg, reply; bio_init(&msg, iodata, sizeof(iodata), 4); ① bio_put_uint32(&msg, 0); // strict mode header bio_put_string16_x(&msg, SVC_MGR_NAME); bio_put_string16_x(&msg, name); bio_put_obj(&msg, ptr); if (binder_call(bs, &msg, &reply, target, SVC_MGR_ADD_SERVICE)) ② return -1; status = bio_get_uint32(&reply); ③ binder_done(bs, &msg, &reply); ④ return status;}①: 初始化用户空间的数据iodata,设置了四个字节的offs,接着按一定格式往buf里面填充数据; ②: 调用ServiceManager服务的SVC_MGR_ADD_SERVICE功能; ③: 获取ServiceManager回复数据,成功返回0; ④: 结束注册过程,释放内核中刚才交互分配的buf;3.2.1 bio_initvoid bio_init(struct binder_io *bio, void *data, size_t maxdata, size_t maxoffs){ size_t n = maxoffs * sizeof(size_t); if (n > maxdata) { bio->flags = BIO_F_OVERFLOW; bio->data_avail = 0; bio->offs_avail = 0; return; } bio->data = bio->data0 = (char *) data + n; ① bio->offs = bio->offs0 = data; ② bio->data_avail = maxdata - n; ③ bio->offs_avail = maxoffs; ④ bio->flags = 0; ⑤}①: 根据传进来的参数,留下一定长度的offs数据空间, data指针则从 data + n开始; ②: offs指针则从 data开始,则offs可使用的数据空间只有n个字节; ③: 可使用的data空间计数; ④: 可使用的offs空间计数; ⑤: 清除buf的flag;init后此时buf空间的分配情况如下图:3.2.2 bio_put_uint32void bio_put_uint32(struct binder_io *bio, uint32_t n){ uint32_t *ptr = bio_alloc(bio, sizeof(n)); if (ptr) *ptr = n;}这个函数往buf里面填充一个uint32的数据,这个数据的最小单位为4个字节; 前面svcmgr_publish调用bio_put_uint32(&msg, 0);,实质buf中的数据是00 00 00 00 ;3.2.3 bio_allocstatic void *bio_alloc(struct binder_io *bio, size_t size){ size = (size + 3) & (~3); if (size > bio->data_avail) { bio->flags |= BIO_F_OVERFLOW; return NULL; } else { void *ptr = bio->data; bio->data += size; bio->data_avail -= size; return ptr; }}这个函数分配的数据宽度为4的倍数,先判断当前可使用的数据宽度是否小于待分配的宽度; 如果小于则置标志BIO_F_OVERFLOW否则分配数据,并对data往后偏移size个字节,可使用数据宽度data_avail减去size个字节;3.2.4 bio_put_string16_xvoid bio_put_string16_x(struct binder_io *bio, const char *_str){ unsigned char str = (unsigned char) _str; size_t len; uint16_t ptr; if (!str) { ① bio_put_uint32(bio, 0xffffffff); return; } len = strlen(_str); if (len >= (MAX_BIO_SIZE / sizeof(uint16_t))) { bio_put_uint32(bio, 0xffffffff); return; } / Note: The payload will carry 32bit size instead of size_t */ bio_put_uint32(bio, len); ptr = bio_alloc(bio, (len + 1) * sizeof(uint16_t)); if (!ptr) return; while (*str) ② *ptr++ = *str++; *ptr++ = 0;}①: 这里到bio_alloc前都是为了计算和判断自己串的长度再填充到buf中; ②: 填充字符串到buf中,一个字符占两个字节,注意 uint16_t *ptr;;3.2.5 bio_put_objvoid bio_put_obj(struct binder_io *bio, void *ptr){ struct flat_binder_object obj; obj = bio_alloc_obj(bio); ① if (!obj) return; obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; obj->type = BINDER_TYPE_BINDER; ② obj->binder = (uintptr_t)ptr; ③ obj->cookie = 0;}struct flat_binder_object {/ WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS / __u32 type; __u32 flags; union { binder_uintptr_t binder;/ WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ __u32 handle; }; binder_uintptr_t cookie;};①: 分配一个flat_binder_object大小的空间(详见3.2.6); ②: type的类型为BINDER_TYPE_BINDER时则type传入的是binder实体,一般是服务端注册服务时传入; type的类型为BINDER_TYPE_HANDLE时则type传入的为handle,一般由客户端请求服务时; ③: obj->binder值,跟随type改变;3.2.6 bio_alloc_objstatic struct flat_binder_object *bio_alloc_obj(struct binder_io *bio){ struct flat_binder_object *obj; obj = bio_alloc(bio, sizeof(obj)); ① if (obj && bio->offs_avail) { bio->offs_avail–; bio->offs++ = ((char) obj) - ((char) bio->data0); ② return obj; } bio->flags |= BIO_F_OVERFLOW; return NULL;}①: 在data后分配struct flat_binder_object长度的空间; ②: bio->offs空间记下此时插入obj,相对于data0的偏移值;看到这终于知道offs是干嘛的了,原来是用来记录数据中是否有obj类型的数据;3.2.7 完整数据格式图综上分析,传输一次完整的数据的格式如下:3.3 binder_callint binder_call(struct binder_state *bs, struct binder_io msg, struct binder_io reply, uint32_t target, uint32_t code){ int res; struct binder_write_read bwr; struct { uint32_t cmd; struct binder_transaction_data txn; } attribute((packed)) writebuf; unsigned readbuf[32]; if (msg->flags & BIO_F_OVERFLOW) { fprintf(stderr,“binder: txn buffer overflow\n”); goto fail; } writebuf.cmd = BC_TRANSACTION; // binder call transaction writebuf.txn.target.handle = target; ① writebuf.txn.code = code; ② writebuf.txn.flags = 0; writebuf.txn.data_size = msg->data - msg->data0; ③ writebuf.txn.offsets_size = ((char) msg->offs) - ((char) msg->offs0); writebuf.txn.data.ptr.buffer = (uintptr_t)msg->data0; writebuf.txn.data.ptr.offsets = (uintptr_t)msg->offs0; bwr.write_size = sizeof(writebuf); ④ bwr.write_consumed = 0; bwr.write_buffer = (uintptr_t) &writebuf; for (;;) { bwr.read_size = sizeof(readbuf); bwr.read_consumed = 0; bwr.read_buffer = (uintptr_t) readbuf; res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr); ⑤ if (res < 0) { fprintf(stderr,“binder: ioctl failed (%s)\n”, strerror(errno)); goto fail; } res = binder_parse(bs, reply, (uintptr_t) readbuf, bwr.read_consumed, 0); ⑥ if (res == 0) return 0; if (res < 0) goto fail; }fail: memset(reply, 0, sizeof(*reply)); reply->flags |= BIO_F_IOERROR; return -1;}①: 这个target就是我们这次请求服务的目标,即ServiceManager; ②: code是我们请求服务的功能码,由服务端提供; ③: 把binder_io数据转化成binder_transaction_data数据; ④: 驱动进行读写是根据这个size来的,分析驱动的时候再详细分析; ⑤: 进行一次读写; ⑥: 解析发送的后返回的数据,判断是否注册成功;3.4 binder_donevoid binder_done(struct binder_state *bs, struct binder_io *msg, struct binder_io *reply){ struct { uint32_t cmd; uintptr_t buffer; } attribute((packed)) data; if (reply->flags & BIO_F_SHARED) { data.cmd = BC_FREE_BUFFER; data.buffer = (uintptr_t) reply->data0; binder_write(bs, &data, sizeof(data)); reply->flags = 0; }}这个函数比较简单发送BC_FREE_BUFFER命令给驱动,让驱动释放内核态由刚才交互分配的buf;3.5 binder_set_maxthreadsvoid binder_set_maxthreads(struct binder_state *bs, int threads){ ioctl(bs->fd, BINDER_SET_MAX_THREADS, &threads);}这里主要调用ioctl函数写入命令BINDER_SET_MAX_THREADS进行设置最大线程数;3.6 调用时序图led_control_server主要提供led的控制服务,具体的流程如下: 四. test_client4.1 mainint main(int argc, char **argv){ struct binder_state bs; uint32_t svcmgr = BINDER_SERVICE_MANAGER; unsigned int g_led_control_handle; if (argc < 3) { ALOGE(“Usage:\n”); ALOGE("%s led <on|off>\n", argv[0]); return -1; } bs = binder_open(1281024); ① if (!bs) { ALOGE(“failed to open binder driver\n”); return -1; } g_led_control_handle = svcmgr_lookup(bs, svcmgr, LED_CONTROL_SERVER_NAME); ② if (!g_led_control_handle) { ALOGE( “failed to get led control service\n”); return -1; } ALOGI(“Handle for led control service = %d\n”, g_led_control_handle); if (!strcmp(argv[1], “led”)) { if (!strcmp(argv[2], “on”)) { if (interface_led_on(bs, g_led_control_handle, 2) == 0) { ③ ALOGI(“led was on\n”); } } else if (!strcmp(argv[2], “off”)) { if (interface_led_off(bs, g_led_control_handle, 2) == 0) { ALOGI(“led was off\n”); } } } binder_release(bs, g_led_control_handle); ④ return 0;}①: 打开binder设备(详见2.2); ②: 根据名字获取led控制服务; ③: 根据获取到的handle,调用led控制服务(详见4.3); ④: 释放服务;client的流程也很简单,按步骤1.2.3.4读下来就是了;4.2 svcmgr_lookupuint32_t svcmgr_lookup(struct binder_state *bs, uint32_t target, const char *name){ uint32_t handle; unsigned iodata[512/4]; struct binder_io msg, reply; bio_init(&msg, iodata, sizeof(iodata), 4); ① bio_put_uint32(&msg, 0); // strict mode header bio_put_string16_x(&msg, SVC_MGR_NAME); bio_put_string16_x(&msg, name); if (binder_call(bs, &msg, &reply, target, SVC_MGR_GET_SERVICE)) ② return 0; handle = bio_get_ref(&reply); ③ if (handle) binder_acquire(bs, handle); ④ binder_done(bs, &msg, &reply); ⑤ return handle;}①: 因为是请求服务,所以这里不用添加binder实体数据,具体的参考3.2,这里就不重复解释了; ②: 向target进程(ServiceManager)请求获取led_control服务(详细参考3.3); ③: 从ServiceManager返回的数据buf中获取led_control服务的handle; ④: 增加该handle的引用计数; ⑤: 释放内核空间buf(详3.4);4.2.1 bio_get_refuint32_t bio_get_ref(struct binder_io *bio){ struct flat_binder_object *obj; obj = _bio_get_obj(bio); ① if (!obj) return 0; if (obj->type == BINDER_TYPE_HANDLE) ② return obj->handle; return 0;}①: 把bio的数据转化成flat_binder_object格式; ②: 判断binder数据类型是否为引用,是则返回获取到的handle;4.2.2 _bio_get_objstatic struct flat_binder_object *_bio_get_obj(struct binder_io bio){ size_t n; size_t off = bio->data - bio->data0; ① / TODO: be smarter about this? */ for (n = 0; n < bio->offs_avail; n++) { if (bio->offs[n] == off) return bio_get(bio, sizeof(struct flat_binder_object)); ② } bio->data_avail = 0; bio->flags |= BIO_F_OVERFLOW; return NULL;}①: 一般情况下该值都为0,因为在reply时获取ServiceManager传来的数据,bio->data和bio->data都指向同一个地址; ②: 获取到struct flat_binder_object数据的头指针;从ServiceManager传来的数据是struct flat_binder_object的数据,格式如下: 4.3 interface_led_onint interface_led_on(struct binder_state *bs, unsigned int handle, unsigned char led_enum){ unsigned iodata[512/4]; struct binder_io msg, reply; int ret = -1; int exception; bio_init(&msg, iodata, sizeof(iodata), 4); bio_put_uint32(&msg, 0); // strict mode header bio_put_uint32(&msg, led_enum); if (binder_call(bs, &msg, &reply, handle, LED_CONTROL_ON)) return ret; exception = bio_get_uint32(&reply); if (exception == 0) ret = bio_get_uint32(&reply); binder_done(bs, &msg, &reply); return ret;}这个流程和前面svcmgr_lookup的请求服务差不多,只是最后是获取led_control_server的返回值. 注意这里为什么获取了两次uint32类型的数据,这是因为服务方在回复数据的时候添加了头帧,这个是可以调节的,非规则;4.4 binder_releasevoid binder_release(struct binder_state *bs, uint32_t target){ uint32_t cmd[2]; cmd[0] = BC_RELEASE; cmd[1] = target; binder_write(bs, cmd, sizeof(cmd));}通知驱动层减小对target进程的引用,结合驱动讲解就更能明白了;4.5 调用时序图test_client的调用时序如下,过程和led_control_server的调用过程相识: A: 表BR_含义BR个人理解是缩写为binder reply消息含义参数BR_ERROR发生内部错误(如内存分配失败)—BR_OK BR_NOOP操作完成—BR_SPAWN_LOOPER该消息用于接收方线程池管理。当驱动发现接收方所有线程都处于忙碌状态且线程池里的线程总数没有超过BINDER_SET_MAX_THREADS设置的最大线程数时,向接收方发送该命令要求创建更多线程以备接收数据。—BR_TRANSACTION对应发送方的BC_TRANSACTIONbinder_transaction_dataBR_REPLY对应发送方BC_REPLY的回复binder_transaction_dataBR_ACQUIRE_RESULT BR_FINISHED未使用—BR_DEAD_REPLY交互时向驱动发送binder调用,如果对方已经死亡,则驱动回应此命令—BR_TRANSACTION_COMPLETE发送方通过BC_TRANSACTION或BC_REPLY发送完一个数据包后,都能收到该消息做为成功发送的反馈。这和BR_REPLY不一样,是驱动告知发送方已经发送成功,而不是Server端返回请求数据。所以不管同步还是异步交互接收方都能获得本消息。—BR_INCREFS BR_ACQUIRE BR_RELEASE BR_DECREFS这一组消息用于管理强/弱指针的引用计数。只有提供Binder实体的进程才能收到这组消息。binder_uintptr_t binder:Binder实体在用户空间中的指针 binder_uintptr_t cookie:与该实体相关的附加数据BR_DEAD_BINDER 向获得Binder引用的进程发送Binder实体死亡通知书;收到死亡通知书的进程接下来会返回BC_DEAD_BINDER_DONE做确认。—BR_CLEAR_DEATH_NOTIFICATION_DONE回应命令BC_REQUEST_DEATH_NOTIFICATION—BR_FAILED_REPLY如果发送非法引用号则返回该消息—B: 表BC_含义BC个人理解是缩写为binder call or cmd消息含义参数BC_TRANSACTION BC_REPLYBC_TRANSACTION用于Client向Server发送请求数据;BC_REPLY用于Server向Client发送回复(应答)数据。其后面紧接着一个binder_transaction_data结构体表明要写入的数据。struct binder_transaction_dataBC_ACQUIRE_RESULT BC_ATTEMPT_ACQUIRE未使用—BC_FREE_BUFFER请求驱动释放调刚在内核空间创建用来保存用户空间数据的内存块—BC_INCREFS BC_ACQUIRE BC_RELEASE BC_DECREFS这组命令增加或减少Binder的引用计数,用以实现强指针或弱指针的功能。—BC_INCREFS_DONE BC_ACQUIRE_DONE第一次增加Binder实体引用计数时,驱动向Binder实体所在的进程发送BR_INCREFS, BR_ACQUIRE消息;Binder实体所在的进程处理完毕回馈BC_INCREFS_DONE, BC_ACQUIRE_DONE—BC_REGISTER_LOOPER BC_ENTER_LOOPER BC_EXIT_LOOPER这组命令同BINDER_SET_MAX_THREADS一道实现Binder驱 动对接收方线程池管理。BC_REGISTER_LOOPER通知驱动线程池中一个线程已经创建了;BC_ENTER_LOOPER通知驱动该线程已经进入主循环,可以接收数据;BC_EXIT_LOOPER通知驱动该线程退出主循环,不再接收数据。—BC_REQUEST_DEATH_NOTIFICATION获得Binder引用的进程通过该命令要求驱动在Binder实体销毁得到通知。虽说强指针可以确保只要有引用就不会销毁实体,但这毕竟是个跨进程的引用,谁也无法保证实体由于所在的Server关闭Binder驱动或异常退出而消失,引用者能做的是要求Server在此刻给出通知。—BC_DEAD_BINDER_DONE收到实体死亡通知书的进程在删除引用后用本命令告知驱动。—参考表格参考博客:https://blog.csdn.net/univers… ...

November 20, 2018 · 11 min · jiezi

2018年Android的保活方案效果统计

一、常见保活方案1、监听广播:监听全局的静态广播,比如时间更新的广播、开机广播、解锁屏、网络状态、解锁加锁亮屏暗屏(3.1版本),高版本需要应用开机后运行一次才能监听这些系统广播,目前此方案失效。可以更换思路,做APP启动后的保活(监听广播启动保活的前台服务)2、定时器、JobScheduler:假如应用被系统杀死,那么定时器则失效,此方案失效。JobService在5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权)3、双进程(NDK方式Fork子进程)、双Service守护:高版本已失效,5.0起系统回收策略改成进程组。双Service方案也改成了应用被杀,任何后台Service无法正常状态运行4、提高Service优先级:只能一定程度上缓解Service被立马回收二、保活1、AIDL方式单进程、双进程方式保活Service2、降低oom_adj的值:常驻通知栏(可通过启动另外一个服务关闭Notification,不对oom_adj值有影响)、使用”1像素“的Activity覆盖在getWindow()的view上、循环播放无声音频(黑科技,7.0下杀不掉)3、监听锁屏广播:使Activity始终保持前台4、使用自定义锁屏界面:覆盖了系统锁屏界面。5、通过android:process属性来为Service创建一个进程6、跳转到系统白名单界面让用户自己添加app进入白名单三、复活1、JobScheduler:原理类似定时器,5.0,5.1,6.0作用很大,7.0时候有一定影响(可以在电源管理中给APP授权)2、推送互相唤醒复活:极光、友盟、以及各大厂商的推送3、同派系APP广播互相唤醒:比如今日头条系、阿里系方案实现效果统计1、双进程守护方案(基于onStartCommand() return START_STICKY)1、原生5.0、5.1:原生任务栏滑动清理app,Service会被杀掉,然后被拉起,接着一直存活2、金立F100(5.1):一键清理直接杀掉整个app,包括双守护进程。不手动清理情况下,经测试能锁屏存活至少40分钟3、华为畅享5x(6.0):一键清理直接杀掉整个app,包括双守护进程。不手动清理下,锁屏只存活10s。结论:双进程守护方案失效。4、美图m8s(7.1.1):一键清理直接杀掉整个app,包括双守护进程。不清理情况下,锁屏会有被杀过程(9分钟左右被杀),之后重新复活,之后不断被干掉然后又重新复活。结论:双守护进程可在后台不断拉起Service。5、原生7.0:任务栏清除APP后,Service存活。使用此方案后Service照样存活。6、LG V30+(7.1.2):不加双进程守护的时候,一键清理无法杀掉服务。加了此方案之后也不能杀掉服务,锁屏存活(测试观察大于50分钟)7、小米8(8.1):一键清理直接干掉app并且包括双守护进程。不清理情况下,不加守护进程方案与加守护进程方案Service会一直存活,12分钟左右closed。结论:此方案没有起作用结论:除了华为此方案无效以及未更改底层的厂商不起作用外(START_STICKY字段就可以保持Service不被杀)。此方案可以与其他方案混合使用2、监听锁屏广播打开1像素Activity(基于onStartCommand() return START_STICKY)1、原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用)2、华为畅享5x(6.0):锁屏只存活4s。结论:方案失效。3、美图m8s(7.1.1):同原生5.04、原生7.0:同美图m8s。5、LG V30+(7.1.2):锁屏后情况跟不加情况一致,服务一致保持运行,结论:此方案不起作用6、小米8(8.1):关屏过2s之后app全部被干掉。结论:此方案没有起作用结论:此方案无效果3、故意在后台播放无声的音乐(基于onStartCommand() return START_STICKY)1、原生5.0、5.1:锁屏后3s服务被干掉然后重启(START_STICKY字段起作用)2、华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务,锁屏8分钟后依然存活。结论:此方案适用3、美图m8s(7.1.1):同5.04、原生7.0:任务管理器中关闭APP后服务被干掉,大概过3s会重新复活(同仅START_STICKY字段模式)。结论:看不出此方案有没有其作用5、LG V30+(7.1.2):使用此方案前后效果一致。结论:此方案不起作用6、小米8(8.1):一键清理可以杀掉服务。锁屏后保活超过20分钟结论:成功对华为手机保活。小米8下也成功突破20分钟4、使用JobScheduler唤醒Service(基于onStartCommand() return START_STICKY)1、原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。结论:此方案起作用2、华为畅享5x(6.0):一键清理直接杀掉APP,过12s左右会自动重启服务,JobScheduler起作用3、美图m8s(7.1.1):一键清理直接杀掉APP,无法自动重启4、原生7.0:同美图m8s(7.1.1)5、小米8(8.1):同美图m8s(7.1.1)结论:只对5.0,5.1、6.0起作用5、混合使用的效果,并且在通知栏弹出通知1、原生5.0、5.1:任务管理器中干掉APP,服务会在周期时间后重新启动。锁屏超过11分钟存活2、华为畅享5x(6.0):一键清理后服务依然存活,需要单独清理才可杀掉服务。结论:方案适用。3、美图m8s(7.1.1):一键清理APP会被杀掉。正常情况下锁屏后服务依然存活。4、原生7.0:任务管理器中关闭APP后服务被干掉,过2s会重新复活5、小米8(8.1):一键清理可以杀掉服务,锁屏下后台保活时间超过38分钟6、荣耀10(8.0):一键清理杀掉服务,锁屏下后台保活时间超过23分钟结论:高版本情况下可以使用弹出通知栏、双进程、无声音乐提高后台服务的保活概率实现具体过程一、双进程实现方案使用AIDL绑定方式新建2个Service优先级(防止服务同时被系统杀死)不一样的守护进程互相拉起对方,并在每一个守护进程的ServiceConnection的绑定回调里判断保活Service是否需要重新拉起和对守护线程进行重新绑定。1、新建一个AIDL文件KeepAliveConnectioninterface KeepAliveConnection {}2、新建一个服务类StepService,onBind()方法返回new KeepAliveConnection.Stub()对象,并在ServiceConnection的绑定回调中对守护进程服务类GuardService的启动和绑定。/** * 主进程 双进程通讯 * * @author LiGuangMin * @time Created by 2018/8/17 11:26 /public class StepService extends Service { private final static String TAG = StepService.class.getSimpleName(); private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { Logger.d(TAG, “StepService:建立链接”); boolean isServiceRunning = ServiceAliveUtils.isServiceAlice(); if (!isServiceRunning) { Intent i = new Intent(StepService.this, DownloadService.class); startService(i); } } @Override public void onServiceDisconnected(ComponentName componentName) { // 断开链接 startService(new Intent(StepService.this, GuardService.class)); // 重新绑定 bindService(new Intent(StepService.this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT); } }; @Nullable @Override public IBinder onBind(Intent intent) { return new KeepAliveConnection.Stub() { }; } @Override public int onStartCommand(Intent intent, int flags, int startId) { startForeground(1, new Notification()); // 绑定建立链接 bindService(new Intent(this, GuardService.class), mServiceConnection, Context.BIND_IMPORTANT); return START_STICKY; }}3、对守护进程GuardService进行和2一样的处理/* * 守护进程 双进程通讯 * * @author LiGuangMin * @time Created by 2018/8/17 11:27 /public class GuardService extends Service { private final static String TAG = GuardService.class.getSimpleName(); private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { Logger.d(TAG, “GuardService:建立链接”); boolean isServiceRunning = ServiceAliveUtils.isServiceAlice(); if (!isServiceRunning) { Intent i = new Intent(GuardService.this, DownloadService.class); startService(i); } } @Override public void onServiceDisconnected(ComponentName componentName) { // 断开链接 startService(new Intent(GuardService.this, StepService.class)); // 重新绑定 bindService(new Intent(GuardService.this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT); } }; @Nullable @Override public IBinder onBind(Intent intent) { return new KeepAliveConnection.Stub() { }; } @Override public int onStartCommand(Intent intent, int flags, int startId) { startForeground(1, new Notification()); // 绑定建立链接 bindService(new Intent(this, StepService.class), mServiceConnection, Context.BIND_IMPORTANT); return START_STICKY; }}4、在Activity中在启动需要保活的DownloadService服务后然后启动保活的双进程public class MainActivity extends AppCompatActivity { private TextView mShowTimeTv; private DownloadService.DownloadBinder mDownloadBinder; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mDownloadBinder = (DownloadService.DownloadBinder) service; mDownloadBinder.setOnTimeChangeListener(new DownloadService.OnTimeChangeListener() { @Override public void showTime(final String time) { runOnUiThread(new Runnable() { @Override public void run() { mShowTimeTv.setText(time); } }); } }); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this, DownloadService.class); startService(intent); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); //双守护线程,优先级不一样 startAllServices(); } @Override public void onContentChanged() { super.onContentChanged(); mShowTimeTv = findViewById(R.id.tv_show_time); } @Override protected void onDestroy() { super.onDestroy(); unbindService(mServiceConnection); } /* * 开启所有守护Service / private void startAllServices() { startService(new Intent(this, StepService.class)); startService(new Intent(this, GuardService.class)); }}二、监听到锁屏广播后使用“1”像素Activity提升优先级1、该Activity的View只要设置为1像素然后设置在Window对象上即可。在Activity的onDestroy周期中进行保活服务的存活判断从而唤醒服务。“1像素"Activity如下public class SinglePixelActivity extends AppCompatActivity { private static final String TAG = SinglePixelActivity.class.getSimpleName(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Window mWindow = getWindow(); mWindow.setGravity(Gravity.LEFT | Gravity.TOP); WindowManager.LayoutParams attrParams = mWindow.getAttributes(); attrParams.x = 0; attrParams.y = 0; attrParams.height = 1; attrParams.width = 1; mWindow.setAttributes(attrParams); ScreenManager.getInstance(this).setSingleActivity(this); } @Override protected void onDestroy() { if (!SystemUtils.isAppAlive(this, Constant.PACKAGE_NAME)) { Intent intentAlive = new Intent(this, DownloadService.class); startService(intentAlive); } super.onDestroy(); }}2、对广播进行监听,封装为一个ScreenReceiverUtil类,进行锁屏解锁的广播动态注册监听public class ScreenReceiverUtil { private Context mContext; private SreenBroadcastReceiver mScreenReceiver; private SreenStateListener mStateReceiverListener; public ScreenReceiverUtil(Context mContext) { this.mContext = mContext; } public void setScreenReceiverListener(SreenStateListener mStateReceiverListener) { this.mStateReceiverListener = mStateReceiverListener; // 动态启动广播接收器 this.mScreenReceiver = new SreenBroadcastReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_USER_PRESENT); mContext.registerReceiver(mScreenReceiver, filter); } public void stopScreenReceiverListener() { mContext.unregisterReceiver(mScreenReceiver); } /* * 监听sreen状态对外回调接口 / public interface SreenStateListener { void onSreenOn(); void onSreenOff(); void onUserPresent(); } public class SreenBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (mStateReceiverListener == null) { return; } if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏 mStateReceiverListener.onSreenOn(); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏 mStateReceiverListener.onSreenOff(); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁 mStateReceiverListener.onUserPresent(); } } }}3、对1像素Activity进行防止内存泄露的处理,新建一个ScreenManager类public class ScreenManager { private static final String TAG = ScreenManager.class.getSimpleName(); private static ScreenManager sInstance; private Context mContext; private WeakReference<Activity> mActivity; private ScreenManager(Context mContext) { this.mContext = mContext; } public static ScreenManager getInstance(Context context) { if (sInstance == null) { sInstance = new ScreenManager(context); } return sInstance; } /* 获得SinglePixelActivity的引用 * @param activity / public void setSingleActivity(Activity activity) { mActivity = new WeakReference<>(activity); } /* * 启动SinglePixelActivity / public void startActivity() { Intent intent = new Intent(mContext, SinglePixelActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } /* * 结束SinglePixelActivity / public void finishActivity() { if (mActivity != null) { Activity activity = mActivity.get(); if (activity != null) { activity.finish(); } } }}4、对1像素的Style进行特殊处理,在style文件中新建一个SingleActivityStyle<style name=“SingleActivityStyle” parent=“android:Theme.Holo.Light.NoActionBar”> <item name=“android:windowBackground”>@android:color/transparent</item> <item name=“android:windowFrame”>@null</item> <item name=“android:windowNoTitle”>true</item> <item name=“android:windowIsFloating”>true</item> <item name=“android:windowContentOverlay”>@null</item> <item name=“android:backgroundDimEnabled”>false</item> <item name=“android:windowAnimationStyle”>@null</item> <item name=“android:windowDisablePreview”>true</item> <item name=“android:windowNoDisplay”>false</item>5、让SinglePixelActivity使用singleInstance启动模式,在manifest文件中 <activity android:name=".activity.SinglePixelActivity” android:configChanges=“keyboardHidden|orientation|screenSize|navigation|keyboard” android:excludeFromRecents=“true” android:finishOnTaskLaunch=“false” android:launchMode=“singleInstance” android:theme="@style/SingleActivityStyle" />6、在保活服务类DownloadService中对监听的广播进行注册和对SinglePixelActivity进行控制。public class DownloadService extends Service { public static final int NOTICE_ID = 100; private static final String TAG = DownloadService.class.getSimpleName(); private DownloadBinder mDownloadBinder; private NotificationCompat.Builder mBuilderProgress; private NotificationManager mNotificationManager; private ScreenReceiverUtil mScreenListener; private ScreenManager mScreenManager; private Timer mRunTimer; private int mTimeSec; private int mTimeMin; private int mTimeHour; private ScreenReceiverUtil.SreenStateListener mScreenListenerer = new ScreenReceiverUtil.SreenStateListener() { @Override public void onSreenOn() { mScreenManager.finishActivity(); Logger.d(TAG, “关闭了1像素Activity”); } @Override public void onSreenOff() { mScreenManager.startActivity(); Logger.d(TAG, “打开了1像素Activity”); } @Override public void onUserPresent() { } }; private OnTimeChangeListener mOnTimeChangeListener; @Override public void onCreate() { super.onCreate();// 注册锁屏广播监听器 mScreenListener = new ScreenReceiverUtil(this); mScreenManager = ScreenManager.getInstance(this); mScreenListener.setScreenReceiverListener(mScreenListenerer); mDownloadBinder = new DownloadBinder(); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Logger.d(TAG, “onStartCommand”); startRunTimer(); return START_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return mDownloadBinder; } @Override public boolean onUnbind(Intent intent) { Logger.d(TAG, “onUnbind”); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); NotificationManager mManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); if (mManager == null) { return; } mManager.cancel(NOTICE_ID); stopRunTimer();// mScreenListener.stopScreenReceiverListener(); } private void startRunTimer() { TimerTask mTask = new TimerTask() { @Override public void run() { mTimeSec++; if (mTimeSec == 60) { mTimeSec = 0; mTimeMin++; } if (mTimeMin == 60) { mTimeMin = 0; mTimeHour++; } if (mTimeHour == 24) { mTimeSec = 0; mTimeMin = 0; mTimeHour = 0; } String time = “时间为:” + mTimeHour + " : " + mTimeMin + " : " + mTimeSec; if (mOnTimeChangeListener != null) { mOnTimeChangeListener.showTime(time); } Logger.d(TAG, time); } }; mRunTimer = new Timer(); // 每隔1s更新一下时间 mRunTimer.schedule(mTask, 1000, 1000); } private void stopRunTimer() { if (mRunTimer != null) { mRunTimer.cancel(); mRunTimer = null; } mTimeSec = 0; mTimeMin = 0; mTimeHour = 0; Logger.d(TAG, “时间为:” + mTimeHour + " : " + mTimeMin + " : " + mTimeSec); } public interface OnTimeChangeListener { void showTime(String time); } public class DownloadBinder extends Binder { public void setOnTimeChangeListener(OnTimeChangeListener onTimeChangeListener) { mOnTimeChangeListener = onTimeChangeListener; } }}3、在后台播放音乐1、准备一段无声的音频,新建一个播放音乐的Service类,将播放模式改为无限循环播放。在其onDestroy方法中对自己重新启动。public class PlayerMusicService extends Service { private final static String TAG = PlayerMusicService.class.getSimpleName(); private MediaPlayer mMediaPlayer; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); Logger.d(TAG, TAG + “—->onCreate,启动服务”); mMediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.silent); mMediaPlayer.setLooping(true); } @Override public int onStartCommand(Intent intent, int flags, int startId) { new Thread(new Runnable() { @Override public void run() { startPlayMusic(); } }).start(); return START_STICKY; } private void startPlayMusic() { if (mMediaPlayer != null) { Logger.d(TAG, “启动后台播放音乐”); mMediaPlayer.start(); } } private void stopPlayMusic() { if (mMediaPlayer != null) { Logger.d(TAG, “关闭后台播放音乐”); mMediaPlayer.stop(); } } @Override public void onDestroy() { super.onDestroy(); stopPlayMusic(); Logger.d(TAG, TAG + “—->onCreate,停止服务”); // 重启自己 Intent intent = new Intent(getApplicationContext(), PlayerMusicService.class); startService(intent); }}2、 在保活的DownloadServie服务类的onCreate方法中对PlayerMusicService进行启动 Intent intent = new Intent(this, PlayerMusicService.class); startService(intent);3、在Manifest文件中进行注册 <service android:name=".service.PlayerMusicService" android:enabled=“true” android:exported=“true” android:process=":music_service" />4、使用JobScheduler唤醒Service1、新建一个继承自JobService的ScheduleService类,在其onStartJob回调中对DownloadService进行存活的判断来重启。public class ScheduleService extends JobService { private static final String TAG = ScheduleService.class.getSimpleName(); @Override public boolean onStartJob(JobParameters params) { boolean isServiceRunning = ServiceAliveUtils.isServiceAlice(); if (!isServiceRunning) { Intent i = new Intent(this, DownloadService.class); startService(i); Logger.d(TAG, “ScheduleService启动了DownloadService”); } jobFinished(params, false); return false; } @Override public boolean onStopJob(JobParameters params) { return false; }}2、 在DownloadService服务类中进行JobScheduler的注册和使用 /* * 使用JobScheduler进行保活 */ private void useJobServiceForKeepAlive() { JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); if (jobScheduler == null) { return; } jobScheduler.cancelAll(); JobInfo.Builder builder = new JobInfo.Builder(1024, new ComponentName(getPackageName(), ScheduleService.class.getName())); //周期设置为了2s builder.setPeriodic(1000 * 2); builder.setPersisted(true); builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); int schedule = jobScheduler.schedule(builder.build()); if (schedule <= 0) { Logger.w(TAG, “schedule error!”); } }3、在manifest文件中进行权限设置 <service android:name=".service.ScheduleService" android:enabled=“true” android:exported=“true” android:permission=“android.permission.BIND_JOB_SERVICE” />关于推送类拉活根据华为官方文档集成HUAWEI Push1、华为畅玩5X(6.0):APP全部进程被杀死时可以被拉起。2、华为nove 3e(8.0):APP全部进程被杀死时无法被拉起,能收到推送。3、华为荣耀10(8.1):同2结论:理论情况下,华为推送应该可以拉起华为机器才对,感觉是我没花钱的原因补充:ServiceAliveUtils 类如下public class ServiceAliveUtils { public static boolean isServiceAlice() { boolean isServiceRunning = false; ActivityManager manager = (ActivityManager) MyApplication.getMyApplication().getSystemService(Context.ACTIVITY_SERVICE); if (manager == null) { return true; } for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (“demo.lgm.com.keepalivedemo.service.DownloadService”.equals(service.service.getClassName())) { isServiceRunning = true; } } return isServiceRunning; }}作者:minminaya链接:https://www.jianshu.com/p/b53…阅读更多做后台是选择Java 、Go ,还是 PHP?NDK项目实战—高仿360手机助手之卸载监听AndroidUtils:Android开发不得不收藏的Utils(Android)面试题级答案(精选版)Google开发者大会:你不得不知的Tensorflow小技巧相信自己,没有做不到的,只有想不到的在这里获得的不仅仅是技术! ...

September 29, 2018 · 6 min · jiezi