![]() |
乐观的甘蔗 · 在Java中调用Python?· 1 年前 · |
![]() |
旅途中的硬盘 · Excel如何逐行快速合并-百度经验· 1 年前 · |
![]() |
温柔的槟榔 · ioexception ...· 1 年前 · |
![]() |
憨厚的烤红薯 · Mac xlwings ...· 1 年前 · |
4、设置手机端环境 如果是user build,手机中没有gdbserver,所以需要手动push一个,gdbserver可以去源码目录下的prebuilts目录中搜索一下,但是这里要区分一下gdbserver和gdbserver64 如果是要调试64位的进程就需要gdbserver64 通过以下命令push到手机中 adb root adb disable-verity adb reboot 等待重启完成 adb remount adb push prebuilts/misc/android-arm/gdbserver/gdbserver /system/bin/ Breakpoint 1, FileDescriptorInfo::Restat (this=0xb4cbd620) at frameworks/base/core/jni/fd_utils-inl.h:182 warning: Source file is more recent than executable. 182 ALOGE("Restat, st_nlink == 8, (%s, fd=%d) : f_stat.st_nlink=%llu,0x%llX file_stat.st_nlink=%llu,0x%llX", 8、查看变量对应的值 p f_stat.st_nlink $1 = 1 到这里对于刚接触gdb调试同学也有了入门的知识,对于gdb老手估计是要玩飞的节奏。。。 但是这里要说一下,如果要调试的是framework相关的进程的native代码,可能会受到system server的watchdog的影响,1分钟没有及时响应操作就会触发watchdog而kill到system server进程,zygote也会跟着挂掉,这里有个小技巧可以用一下,就是在调试的过程中,如果需要耗时查看一些运行时状态,可以先执行 adb shell am hang 防止超时重启,查看完毕想要继续执行,就Ctrl+c终止掉am hang即可继续执行,后面就重复这个过程即可。 另外还有一种方式就是用Android Studio在线调试,把断点加在watchdog里面,配置gdb native调试。
1、配置vm options 一个完成Android源码会非常大,所以为了避免导入和生成index时OOM,最好提前配置一下Android Studio的vm options,可以参考下图是我的配置 2、配置最大打开文件数 在导入过程中会同时打开非常多的文件,为了避免too many files open错误,提前在profile文件中配置一下打开文件的数量,我这里是配置的2048 三、创建Android Framework工程 整个Android源码全编成功之后,还需要编译idegen模块,用以生成Android studio的工程配置文件,对于高通有modem源码的项目这里有个坑,就是有一个hlos下的有一个android的软连接会链接到源码根目录,导致递归死循环,最终栈溢出而无法生成工程配置文件,所以需要删除这个软链接 1、单独编译idegen模块 Vincent$ mmm development/tools/idegen/ 编译成功之后就生成了idegen.jar host Java: idegen (out/host/common/obj/JAVA_LIBRARIES/idegen_intermediates/classes) Install: out/host/darwin-x86/framework/idegen.jar 按照如下设置把JDK的classpath和sourcepath下的内容都删掉,使其能在debug时定位到项目中的源码 然后把Android SDK的classpath和sourcepath也删除,同时将Java SDK选为刚刚配置JDK1.8 设置Modules的依赖,将依赖中除了下图所示的之外全部删除 然后点击左下角的+选择Jars or directories添加framework相关的源码文件夹 点击OK,等待其建立index完成后工具栏的Attach debugger to Android process就会变成可点击状态 五、在线调试Android Framework 上面的配置工作完成后就可以开始在线调试了,但是前提是手机中的ro.debuggable要为1,或者是userdebug,eng版本的系统软件 1、attach到system_process进程 2、设置断点 成功attach后就可以打开想要设置断点的源码文件,然后在行号右边的空白处左键点击设置断点 3、查看运行时状态 设置完成后就可以操作手机,当代码运行到断点处就可以停下来,这时就可以查看运行时的状态,包括变量的值,backtrace等 还可以在watches窗口添加想要查看的表达式的值 4、获取backtrace快照 另外attach后,在不设置断点的情况下也可以实时的获取所有线程的backtrace的快照,作用类似于kill -3,但是只有Java层的trace 六、在线调试其他APP进程 细心的同学可能会想,通过上面的设置我是不是可以attach到其他APP进程,然后把相关源码添加到依赖里面并设置断点debug,或者在framework的公共代码处设置断点debug?答案是可以的,有心的同学可以自己探索一下。
通过初步分析、深入分析(具体分析过程和关键代码及log在下面)我们知道了问题的原因: 1、monkey测试的过程中起了bugreport,并将bugreport输出到stdout的log通过同步pipe循环读取出来然后write到sdcard的文件中,但是由于sdcard空间满了,write的时候发生了IO error导致monkey异常跳出了读取同步pipe的流程,由于bugreport还没有将log输出完毕所以会一直等待,而monkey则继续运行。 2、bugreport起来之后通过set property的方式触发了dumpstate去获取log,然后通过同步local socket(以下为了描述方便简短起见简称socket)与dumpstate建立连接,将dumpstate通过socket输出的log读取出来写到stdout。 3、dumpstate起来之后会首先将与bugreport的socket通过dup2重定向到stdout,然后执行一系列的抓取log信息的动作,包括cat proc节点、执行dumpsys batterystats等,这些动作都是通过run command的方式起的即fork一个进程,然后执行execvp,fork会继承dumpstate的文件描述符,所以起dumpsys batterystats之后dumpsys中的stdout就是与bugreport通信的socket,dumpsys batterystats会将stdout 文件描述符通过binder传递到system server中。 4、binder driver负责在system server进程中重新分配一个文件描述符来指向与bugreport通信的socket,system server收到dump batterystats的请求后开始把信息输出到bugreport的socket,由于bugreport被write block,导致system server输出的log无法被读取,从而引起system server的dump thread拿着batterystats的一个lock而block了进程内多个其他thread。 5、monkey继续运行向system server发起了dumpsys meminfo的请求,由于dump batterystats的线程拿着一个关键的lock没有释放,导致dumpsys meminfo一直无法得到执行而block了monkey主线程。 6、watchdog线程检测到system server被block之后开始执行相关逻辑,其中就有调用IActivityController的systemNotResponding方法,这个IActivityController是monkey注册的,所有就会跨进程调用到了monkey端,monkey的binder thread收到这个请求之后开始执行,但是执行的过程中需要monkey主线dump完需要的信息后notify它,由于monkey主线程dumpsys meminfo的过程中block,所以导致watchdog线程也被block而无法kill system server。 针对问题的原因,给出以下解决方案: 解开死锁问题的根本死结,当monkey向sdcard写log的时候,如果发生IO error,我们捕获它并继续将monkey起的bugreport的输出读完,从而使system server的dump线程释放持有的lock,使其他线程继续正常工作,dumpsys、dumpstate、cat、bugreport等都能完成自己的同步输入输出,monkey主线程也会恢复,整个系统恢复正常。 patch已同步提交给AOSP review,https://android-review.googlesource.com/#/c/234141/ 三、初步分析 1、查看system server的traces发现多线程互锁,主线程需要的锁在thread 33手里 "main" prio=5 tid=1 Blocked | group="main" sCount=1 dsCount=0 obj=0x74869fb8 self=0x5588e4e100 | sysTid=3230 nice=-2 cgrp=default sched=0/0 handle=0x7fa92efe80 | state=S schedstat=( 153835271056 66149983041 217214 ) utm=9061 stm=6322 core=4 HZ=100 | stack=0x7ff873d000-0x7ff873f000 stackSize=8MB | held mutexes= at com.android.server.AlarmManagerService$ResultReceiver.onSendFinished(AlarmManagerService.java:2266) - waiting to lock <0x2310f49f> (a java.lang.Object) held by thread 33 at android.app.PendingIntent$FinishedDispatcher.run(PendingIntent.java:219) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:135) at com.android.server.SystemServer.run(SystemServer.java:296) at com.android.server.SystemServer.main(SystemServer.java:188) at java.lang.reflect.Method.invoke!(Native method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:908) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:703) 2、thread 33需要的锁在thread 86手里 "AlarmManager" prio=5 tid=33 Blocked | group="main" sCount=1 dsCount=0 obj=0x132f14c0 self=0x7f92814780 | sysTid=3773 nice=0 cgrp=default sched=0/0 handle=0x7f93cb7d50 | state=S schedstat=( 7677732166 3315508747 11830 ) utm=569 stm=198 core=5 HZ=100 | stack=0x7f9233f000-0x7f92341000 stackSize=1036KB | held mutexes= at com.android.server.power.PowerManagerService.acquireWakeLockInternal(PowerManagerService.java:753) - waiting to lock <0x012c60c0> (a java.lang.Object) held by thread 86 at com.android.server.power.PowerManagerService.access$3300(PowerManagerService.java:87) at com.android.server.power.PowerManagerService$BinderService.acquireWakeLock(PowerManagerService.java:2990) at android.os.PowerManager$WakeLock.acquireLocked(PowerManager.java:986) at android.os.PowerManager$WakeLock.acquire(PowerManager.java:954) - locked <@addr=0x1300b8a0> (a android.os.Binder) at com.android.server.AlarmManagerService.deliverAlarmsLocked(AlarmManagerService.java:1820) at com.android.server.AlarmManagerService$AlarmThread.run(AlarmManagerService.java:1981) - locked <0x2310f49f> (a java.lang.Object) 3、thread 86需要的锁在thread 84手里 "Binder_E" prio=5 tid=86 Blocked | group="main" sCount=1 dsCount=0 obj=0x141230a0 self=0x7f8b096630 | sysTid=5467 nice=0 cgrp=default sched=0/0 handle=0x7f8b0960e0 | state=S schedstat=( 169694491361 86744323304 358303 ) utm=11590 stm=5379 core=5 HZ=100 | stack=0x7f8ada5000-0x7f8ada7000 stackSize=1012KB | held mutexes= at com.android.server.am.BatteryStatsService.noteStartWakelock(BatteryStatsService.java:253) - waiting to lock <0x052efb00> (a com.android.internal.os.BatteryStatsImpl) held by thread 84 at com.android.server.power.Notifier.onWakeLockAcquired(Notifier.java:158) at com.android.server.power.PowerManagerService.notifyWakeLockAcquiredLocked(PowerManagerService.java:963) at com.android.server.power.PowerManagerService.acquireWakeLockInternal(PowerManagerService.java:805) - locked <0x012c60c0> (a java.lang.Object) at com.android.server.power.PowerManagerService.access$3300(PowerManagerService.java:87) at com.android.server.power.PowerManagerService$BinderService.acquireWakeLock(PowerManagerService.java:2990) at android.os.IPowerManager$Stub.onTransact(IPowerManager.java:66) at android.os.Binder.execTransact(Binder.java:446) 4、thread 84拿着锁在做IO操作,但是这个IO操作为什么会一直block? "Binder_C" prio=5 tid=84 Native | group="main" sCount=1 dsCount=0 obj=0x1411e0a0 self=0x7f8b093e10 | sysTid=5394 nice=-20 cgrp=default sched=0/0 handle=0x7f8b091de0 | state=S schedstat=( 164975127905 86670873575 354263 ) utm=11207 stm=5290 core=4 HZ=100 | stack=0x7f8aaa1000-0x7f8aaa3000 stackSize=1012KB | held mutexes= kernel: __switch_to+0x70/0x7c kernel: sock_alloc_send_pskb+0x25c/0x314 kernel: sock_alloc_send_skb+0x18/0x24 kernel: unix_stream_sendmsg+0x15c/0x340 kernel: sock_aio_write+0x124/0x140 kernel: do_sync_write+0x74/0xa0 kernel: vfs_write+0xd4/0x178 kernel: SyS_write+0x44/0x74 kernel: cpu_switch_to+0x48/0x4c native: #00 pc 0005fca4 /system/lib64/libc.so (write+4) native: #01 pc 0000136c /system/vendor/lib64/libNimsWrap.so (write+44) native: #02 pc 000368d4 /system/lib64/libjavacore.so (???) native: #03 pc 003c0300 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_libcore_io_Posix_writeBytes__Ljava_io_FileDescriptor_2Ljava_lang_Object_2II+212) at libcore.io.Posix.writeBytes(Native method) at libcore.io.Posix.write(Posix.java:258) at libcore.io.BlockGuardOs.write(BlockGuardOs.java:313) at libcore.io.IoBridge.write(IoBridge.java:497) at java.io.FileOutputStream.write(FileOutputStream.java:186) at com.android.internal.util.FastPrintWriter.flushBytesLocked(FastPrintWriter.java:334) at com.android.internal.util.FastPrintWriter.flushLocked(FastPrintWriter.java:355) at com.android.internal.util.FastPrintWriter.appendLocked(FastPrintWriter.java:303) at com.android.internal.util.FastPrintWriter.print(FastPrintWriter.java:466) - locked <@addr=0x13c93200> (a com.android.internal.util.FastPrintWriter$DummyWriter) at android.os.BatteryStats$HistoryPrinter.printNextItem(BatteryStats.java:3503) at android.os.BatteryStats.dumpHistoryLocked(BatteryStats.java:3915) at android.os.BatteryStats.dumpLocked(BatteryStats.java:3962) at com.android.internal.os.BatteryStatsImpl.dumpLocked(BatteryStatsImpl.java:8843) at com.android.server.am.BatteryStatsService.dump(BatteryStatsService.java:947) - locked <0x052efb00> (a com.android.internal.os.BatteryStatsImpl) at android.os.Binder.dump(Binder.java:319) at android.os.Binder.onTransact(Binder.java:285) at com.android.internal.app.IBatteryStats$Stub.onTransact(IBatteryStats.java:832) at android.os.Binder.execTransact(Binder.java:446) 带着问题与朴老师还有明浩兄沟通,明浩兄给出的结论是同步IO操作,如果读端没有将写端写入到buffer或者写端自带buffer中的数据读取完,一旦写端将buffer写满仍然需要继续写或者写端自带buffer没人读,写端就会block,明浩兄自己模拟出了类似的写端调用栈: kernel: __switch_to+0x70/0x7c kernel: sock_alloc_send_pskb+0x25c/0x314 kernel: sock_alloc_send_skb+0x18/0x24 kernel: unix_stream_sendmsg+0x15c/0x340 kernel: sock_aio_write+0x124/0x140 kernel: do_sync_write+0x74/0xa0 kernel: vfs_write+0xd4/0x178 kernel: SyS_write+0x44/0x74 kernel: cpu_switch_to+0x48/0x4c 读端的调用栈: [<0000000000000000>] __switch_to+0x70/0x7c [<0000000000000000>] __skb_recv_datagram+0x408/0x49c [<0000000000000000>] skb_recv_datagram+0x38/0x5c [<0000000000000000>] unix_accept+0x6c/0x154 [<0000000000000000>] SyS_accept4+0x104/0x1e0 [<0000000000000000>] el0_svc_naked+0x20/0x28 [<0000000000000000>] 0xffffffffffffffff 与此同时明浩兄还带来了两个好问题: 1.读端的线程是哪个?这时候在干什么?没有找到对应的bugreport. 2.watchdog为什么没有杀掉system_server呢? 当然还少不了建议的解决方案: 1.采用异步写. 2.读端要及时关闭socket. 问题就这样结束了吗?当然不能! 四、深入分析 首先解释明浩兄提出的第二个问题,watchdog为什么没有杀掉system_server呢? watchdog thread在调用android.app.IActivityControllerStubProxy.systemNotResponding时被block,导致不能及时顺序的执行kill system server的动作: "watchdog" prio=5 tid=71 Native group="main" sCount=1 dsCount=0 obj=0x12edbe00 self=0x7f90d1bdc0 sysTid=5070 nice=0 cgrp=default sched=0/0 handle=0x7f90d1c6b0 state=S schedstat=( 876766452 162402010 4730 ) utm=17 stm=70 core=1 HZ=100 stack=0x7f8c410000-0x7f8c412000 stackSize=1036KB held mutexes= kernel: __switch_to+0x70/0x7c kernel: binder_thread_read+0x464/0xe8c kernel: binder_ioctl+0x3f8/0x824 kernel: do_vfs_ioctl+0x4a4/0x578 kernel: SyS_ioctl+0x5c/0x88 kernel: cpu_switch_to+0x48/0x4c native: #00 pc 0005ec3c /system/lib64/libc.so (__ioctl+4) native: #01 pc 00068eb0 /system/lib64/libc.so (ioctl+100) native: #02 pc 000275f0 /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+164) native: #03 pc 00028050 /system/lib64/libbinder.so (android::IPCThreadState::waitForResponse(android::Parcel*, int*)+112) native: #04 pc 000282c4 /system/lib64/libbinder.so (android::IPCThreadState::transact(int, unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+176) native: #05 pc 0001ff38 /system/lib64/libbinder.so (android::BpBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+64) native: #06 pc 000dcd58 /system/lib64/libandroid_runtime.so (???) native: #07 pc 005fcfb0 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+212) at android.os.BinderProxy.transactNative(Native method) at android.os.BinderProxy.transact(Binder.java:496) at android.app.IActivityController$Stub$Proxy.systemNotResponding(IActivityController.java:293) at com.android.server.Watchdog.run(Watchdog.java:487) 执行android.app.IActivityControllerStubProxy.systemNotResponding调用到了何方神圣?为什么会被block? 通过搜索代码和trace定位到是调用到了monkey中,而且发现了条件循环和wait: "Binder_1" prio=5 tid=15 Waiting | group="main" sCount=1 dsCount=0 obj=0x12cbb0a0 self=0x55cc7ec1e0 | sysTid=12015 nice=0 cgrp=default sched=0/0 handle=0x55cc801c50 | state=S schedstat=( 959522781 386691057 2262 ) utm=41 stm=54 core=5 HZ=100 | stack=0x7f82c72000-0x7f82c74000 stackSize=1012KB | held mutexes= at java.lang.Object.wait!(Native method) - waiting on <0x121ce47e> (a com.android.commands.monkey.Monkey) at com.android.commands.monkey.Monkey$ActivityController.systemNotResponding(Monkey.java:432) - locked <0x121ce47e> (a com.android.commands.monkey.Monkey) at android.app.IActivityController$Stub.onTransact(IActivityController.java:130) at android.os.Binder.execTransact(Binder.java:446) mWatchdogWaiting置为false的条件是跑完runMonkeyCycles之后走到下面才设置false并notifyall,但是当前monkey的main thread也block了: monkey的main thread执行reportDumpsysMemInfo时被block的trace: "main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 obj=0x12c410a0 self=0x55cc7c7510 | sysTid=12001 nice=0 cgrp=default sched=0/0 handle=0x7f8cf59e80 | state=S schedstat=( 216727507148 81167424419 501554 ) utm=17390 stm=4282 core=4 HZ=100 | stack=0x7fcefa3000-0x7fcefa5000 stackSize=8MB | held mutexes= kernel: __switch_to+0x70/0x7c kernel: pipe_wait+0x60/0x88 kernel: pipe_read+0x34c/0x3cc kernel: do_sync_read+0x74/0xa0 kernel: vfs_read+0xa0/0x12c kernel: SyS_read+0x44/0x74 kernel: cpu_switch_to+0x48/0x4c native: #00 pc 0005f68c /system/lib64/libc.so (read+4) native: #01 pc 0003947c /system/lib64/libjavacore.so (???) native: #02 pc 003c0300 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_libcore_io_Posix_readBytes__Ljava_io_FileDescriptor_2Ljava_lang_Object_2II+212) at libcore.io.Posix.readBytes(Native method) at libcore.io.Posix.read(Posix.java:165) at libcore.io.BlockGuardOs.read(BlockGuardOs.java:230) at libcore.io.IoBridge.read(IoBridge.java:472) at java.io.FileInputStream.read(FileInputStream.java:177) at java.io.InputStreamReader.read(InputStreamReader.java:231) - locked <@addr=0x12cc22a0> (a java.lang.ProcessManager$ProcessInputStream) at java.io.BufferedReader.fillBuf(BufferedReader.java:145) at java.io.BufferedReader.readLine(BufferedReader.java:397) - locked <@addr=0x12cc2460> (a java.io.InputStreamReader) at com.android.commands.monkey.Monkey.commandLineReport(Monkey.java:502) at com.android.commands.monkey.Monkey.reportDumpsysMemInfo(Monkey.java:469) at com.android.commands.monkey.Monkey.runMonkeyCycles(Monkey.java:1192) at com.android.commands.monkey.Monkey.run(Monkey.java:700) at com.android.commands.monkey.Monkey.main(Monkey.java:553) at com.android.internal.os.RuntimeInit.nativeFinishInit(Native method) at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:251) 又看到了同步IO被block的调用栈,这次是读端block。那么问题就来了,读端是谁?为什么写端没有写? 接下来watchdog没有kill system server的问题就转变为monkey的主线程为什么会被block?导致不能及时的设置条件为false并notifyall从而使watchdog继续工作? 好了,再捋一捋,有点乱: watchdog thread等待monkey的binder thread执行完systemNotResponding,monkey的binder thread又等着monkey的main thread执行完reportDumpsysMemInfo去notify自己,而reportDumpsysMemInfo的执行需要拿到一个BatteryStatsImpl实例的锁,但是BatteryStatsImpl实例的锁又被tid=84的binder thread拿着,也就是上面最早执行同步socket操作的thread,绕了一大圈发现这是一个连环死锁,最终死锁的关键还是这个tid=84的thread. system server中dumpsys meminfo被block的trace: "Binder_1" prio=5 tid=16 Blocked | group="main" sCount=1 dsCount=0 obj=0x12ceb0a0 self=0x5589025e20 | sysTid=3256 nice=0 cgrp=default sched=0/0 handle=0x5588f5c810 | state=S schedstat=( 187370245727 86634518556 362219 ) utm=12721 stm=6016 core=6 HZ=100 | stack=0x7f93e8c000-0x7f93e8e000 stackSize=1012KB | held mutexes= at com.android.server.am.ActivityManagerService.updateCpuStatsNow(ActivityManagerService.java:2304) - waiting to lock <0x052efb00> (a com.android.internal.os.BatteryStatsImpl) held by thread 84 - locked <0x1a8a1243> (a com.android.internal.os.ProcessCpuTracker) at com.android.server.am.ActivityManagerService.dumpApplicationMemoryUsage(ActivityManagerService.java:14708) at com.android.server.am.ActivityManagerService$MemBinder.dump(ActivityManagerService.java:2002) at android.os.Binder.dump(Binder.java:319) at android.os.Binder.onTransact(Binder.java:285) at android.os.Binder.execTransact(Binder.java:446) 好你个thread 84,竟然这么多人被你block,你到底在干啥?我们再看看它的trace: "Binder_C" prio=5 tid=84 Native | group="main" sCount=1 dsCount=0 obj=0x1411e0a0 self=0x7f8b093e10 | sysTid=5394 nice=-20 cgrp=default sched=0/0 handle=0x7f8b091de0 | state=S schedstat=( 164975127905 86670873575 354263 ) utm=11207 stm=5290 core=4 HZ=100 | stack=0x7f8aaa1000-0x7f8aaa3000 stackSize=1012KB | held mutexes= kernel: __switch_to+0x70/0x7c kernel: sock_alloc_send_pskb+0x25c/0x314 kernel: sock_alloc_send_skb+0x18/0x24 kernel: unix_stream_sendmsg+0x15c/0x340 kernel: sock_aio_write+0x124/0x140 kernel: do_sync_write+0x74/0xa0 kernel: vfs_write+0xd4/0x178 kernel: SyS_write+0x44/0x74 kernel: cpu_switch_to+0x48/0x4c native: #00 pc 0005fca4 /system/lib64/libc.so (write+4) native: #01 pc 0000136c /system/vendor/lib64/libNimsWrap.so (write+44) native: #02 pc 000368d4 /system/lib64/libjavacore.so (???) native: #03 pc 003c0300 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_libcore_io_Posix_writeBytes__Ljava_io_FileDescriptor_2Ljava_lang_Object_2II+212) at libcore.io.Posix.writeBytes(Native method) at libcore.io.Posix.write(Posix.java:258) at libcore.io.BlockGuardOs.write(BlockGuardOs.java:313) at libcore.io.IoBridge.write(IoBridge.java:497) at java.io.FileOutputStream.write(FileOutputStream.java:186) at com.android.internal.util.FastPrintWriter.flushBytesLocked(FastPrintWriter.java:334) at com.android.internal.util.FastPrintWriter.flushLocked(FastPrintWriter.java:355) at com.android.internal.util.FastPrintWriter.appendLocked(FastPrintWriter.java:303) at com.android.internal.util.FastPrintWriter.print(FastPrintWriter.java:466) - locked <@addr=0x13c93200> (a com.android.internal.util.FastPrintWriter$DummyWriter) at android.os.BatteryStats$HistoryPrinter.printNextItem(BatteryStats.java:3503) at android.os.BatteryStats.dumpHistoryLocked(BatteryStats.java:3915) at android.os.BatteryStats.dumpLocked(BatteryStats.java:3962) at com.android.internal.os.BatteryStatsImpl.dumpLocked(BatteryStatsImpl.java:8843) at com.android.server.am.BatteryStatsService.dump(BatteryStatsService.java:947) - locked <0x052efb00> (a com.android.internal.os.BatteryStatsImpl) at android.os.Binder.dump(Binder.java:319) at android.os.Binder.onTransact(Binder.java:285) at com.android.internal.app.IBatteryStats$Stub.onTransact(IBatteryStats.java:832) at android.os.Binder.execTransact(Binder.java:446) 仔细看这个traces,再仔细看这个traces,原来它在做dumpsys batterystats的操作,这个操作是谁发起的? 赶紧ps查看一下所有的dumpsys,发现有两个,一个是Z状态,僵尸了,一个是S状态。 Z状态?发生了啥?赶紧看看父进程是谁,发现是dumpstate,为啥dumpstate没有帮它儿子收尸?赶紧看代码: 原来dumpstate起了dumpsys batterystats之后如果waitpid等待超时就会通过kill来发送SIGTERM优雅的结束dumpsys batterystats,但是貌似它忘记了一件事情,就是再次waitpid替儿子收尸。。。太大意了,导致儿子变成僵尸! 但是尽管如此,处于僵尸状态的进程已经把占用的资源已经释放掉了,其中就包括文件描述符,所以还是解释不了thread 84为啥被block? 接着看dumpsys batterystats是如何调用system server去做dump操作的,继续看代码: 原来dumpsys把STDOUT传递给了system server去dump batterystats,但是上面dumpsys已经释放了所有文件资源,并且传递的是STDOUT,不应该block system server的thread 84啊? 带着这个问题继续烧脑,看源码,发现dumpstate是bugreport通过set property起的,然后通过socket与dumpstate建立连接,bugreport读取这个socket中的数据并write到标准输出,而dumpstate又通过dup2把与bugreport的socket重定向到了stdout: run_command是通过先fork然后在子进程中用execvp的方式执行的dumpsys batterystats,这样dumpsys batterystats就继承了dumpstate的stdout,对应的就是与bugreport通信的socket,所以就算dumpsys进程的stdout被关闭也只会对这个socket的引用计数减1,现在这个socket对应的引用计数还有2,分别是dumpstate进程自己以及system server,因此dumpsys的Z状态不会中断system server的thread 84,所以问题的关键还是在于thread 84在向这个socket同步写数据,但是socket的另一端没有读。 我们知道socket的另一端是bugreport,但是它为什么没有及时的把socket中的数据读走呢?赶紧用debuggerd dump出来bugreport的调用栈,发现bugreport在往标准输出里面写数据的时候被block了: backtrace: #00 pc 000000000005fca4 /system/lib64/libc.so (write+4) #01 pc 00000000000524dc /system/lib64/libc.so (__swrite+76) #02 pc 00000000000509d8 /system/lib64/libc.so (__sfvwrite+564) #03 pc 0000000000050e30 /system/lib64/libc.so (fwrite+188) #04 pc 0000000000000960 /system/bin/bugreport (main+192) #05 pc 0000000000013504 /system/lib64/libc.so (__libc_init+100) #06 pc 0000000000000a18 /system/bin/bugreport bugreport往标准输出里面写数据时为什么会被block? 先通过ps看一下bugreport的父进程是谁,发现是monkey进程,那monkey进程为什么不把bugreport的输出读走?从上面我们知道monkey的主线程block在了dumpsys meminfo,看它的其他线程也没有调用bugreport的调用栈,那bugreport是怎么起来的?什么时候起来的?百思不得其解,通过一遍一遍的看代码以及出问题时sdcard没有空间的种种线索发现了以下可疑代码和一个推论: monkey起bugreport的方式就是调用上面的commandLineReport函数,这个函数起了bugreport之后会通过同步pipe的方式读取bugreport的标准输出,正常情况下会一直读完,mRequestBugreport为true就会把这些数据写到sdcard指定的文件里。如果在向sdcard写入这些log数据的时候存储空间满了,就会发生IO exception,那么就会跳出readline的while循环,从而继续执行其他操作,导致bugreport在write的时候block,进一步导致system server的thread 84在write的时候block,因为bugreport无法读取socket中的数据。 monkey主线程跳出readline的while循环之后继续执行,然后执行到了dumpsys meminfo的操作,上面ps出来的另外一个S状态的dumpsys的父进程就是monkey,由于system server的thread 84拿着dumpsys meminfo线程需要的锁,所以monkey主线程也被block了,monkey主线程block导致system server的watchdog调用systemNotResponding一直无法返回而无法继续kill system server。 好了,分析到这里就回答完明浩兄的两个问题了,system server不被watchdog kill的原因,以及thread 84的同步socket的读端是谁也找到了,为什么不读的原因也找到了。 五、后续延伸 1、在发生问题时,通过进入到bugreport进程的proc的fd目录下,cat 1(stdout)整个系统就全部恢复了,原理如上 2、通过dumpsys batterystats|more可以使system server被block而引发watchdog重启,原理就是上面的分析 3、有测试的同学发现它们通过java代码写的模拟shell执行的调用logcat的代码时而有效,时而logcat起来之后没有响应,调用进程一直waitfor无法返回,原因就是它们调用的logcat命令在执行时有时手机里面的crash信息比较多导致同步pipe的buffer被用完,而他们的调用进程没有将同步pipe中的数据读出,导致logcat进程在同步写pipe的时候block,而他的调用进程一直在空waitfor,时而有效是因为手机里面crash信息比较少甚至没有,执行他这行代码后logcat进程不会因为数据太多而在write的时候被block,他们的具体代码如下: String returnInfo = Utils.SHELL(new String[]{"sh", "-c", "logcat -d -s crashmonitor"}); if(returnInfo.contains("CRASH") == true){ Utils.LOG("Found crash."); Utils.SHELL(new String[]{"sh", "-c", "logcat -v time -d > " + Utils.WORK_PATH + "logcat_crash.txt"}); public static String SHELL(String[] command){ Process p = null; int status = 0; String text = null; BufferedInputStream in = null; BufferedReader resultReader = null; p = Runtime.getRuntime().exec(command); status = p.waitFor(); if(status != 0){ throw new RuntimeException(String.format("Run shell command: %s, status: %s", command, status)); 可以看到调用“Runtime.getRuntime().exec(command);”之后直接waitfor了,没有管p的输出,正确的用法应该是: public static String SHELL(String[] command){ Process p = null; int status = 0; String text = null; BufferedInputStream in = null; BufferedReader resultReader = null; p = Runtime.getRuntime().exec(command); in = new BufferedInputStream(p.getInputStream()); resultReader = new BufferedReader(new InputStreamReader(in, "UTF-8")); String line; while((line = resultReader.readLine()) != null){ text += line + "\n"; status = p.waitFor(); if(status != 0){ throw new RuntimeException(String.format("Run shell command: %s, status: %s", command, status)); 4、问题是在L版本上发生,通过搜索和查看AOSP的最新master分支发现他们也提交了一些patch 首先Google重构了bugreport的代码,https://android-review.googlesource.com/#/c/124474/ Refactor of the bugreport code. Implemented these changes: Make this code C++. - Avoid hangs by adding a timeout. - Add the necessary TEMP_FAILURE_RETRY calls. Restructure the code a bit. 为dumpstate的run command实现更好的超时和waitpid机制,解决上面没有收尸的问题,https://android-review.googlesource.com/#/c/128514/ Add better timeout mechanism for running commands. 移除无效dumpstate gzip压缩选项,https://android-review.googlesource.com/#/c/130384/ Remove -z option. This option doesn't work with the current selinux protections and doesn't serve much of a purpose. You can get the same results running this: dumpstate | gzip > /data/local/tmp/dump.txt.gz 为dumpstate的dump_file增加超时机制避免bugreport永远hang住,https://android-review.googlesource.com/#/c/134103/ Add timeout for dump_file. It turns out dump_file is used on a number of /proc and system files. In one case, the read of a file stalled and caused a bugreport to hang forever. It's still possible if there is a kernel bug that this could stall forever, but less likely. Also, change the return type of nanotime to uint64_t. Testing: Created a named fifo and verified that dump_file fails with a timeout. Created a large /data/anr/traces.txt to verify that large files still dump properly and that the additional NONBLOCK parameter doesn't cause a problem. Created a dummy /data/tombstones/tombstone_00 to verify that the dump of these files still works. Compared a dump using the old dumpstate to the new dumpstate to verify nothing obviously different. https://android-review.googlesource.com/#/c/150928/ Don't use TEMP_FAILURE_RETRY on close in frameworks/native. https://android-review.googlesource.com/#/c/142952/ dumpstate: add O_CLOEXEC Add O_CLOEXEC to various file descriptor calls, to avoid leaking file descriptors to dumpstate's child processes.
这个问题在来小米之前就遇到并解决过,当时的解决方案与朴老师的初步解决方案一样,本文在之前的初步分析结果之上进一步进行了深入分析,最终得出了当前看起来相对合理并符合原来架构设计的最终方案。 文中引用了朴老师抓的backtrace,同时在进一步分析的过程中朴老师也提出的大量有建设性的问题,感谢朴老师! 一、问题现象 1、systemui高频崩溃 2、system server崩溃导致重启 二、解决方案 通过初步分析、深入分析(具体分析过程和关键代码及log在下面)我们知道了问题的原因: 1、_CompressedAsset::getBuffer不是线程安全的 2、多个AssetManager一起share同一个_CompressedAsset对象,而不同的AssetManager在对_CompressedAsset进行get和set时存在多线程并发竞争同一资源的场景 3、多线程对_CompressedAsset进行get和set时没有用同一个lock同步 4、两个线程同时走进_CompressedAsset::getBuffer并发生double free 针对问题的根本原因,给出以下解决方案: 对临界资源的get和set用同一个lock同步,有序访问。 patch已同步提交给AOSP review并merged,patch详情请猛击:https://android-review.googlesource.com/#/c/271434/ 三、初步分析 1、查看崩溃时的backtrace,发现是对象析构时free memory时出问题了,为什么? backtrace: #00 pc 0003d546 /system/lib/libc.so (arena_run_dalloc+97) #01 pc 0003e5e9 /system/lib/libc.so (je_arena_dalloc_large+24) #02 pc 000463d9 /system/lib/libc.so (ifree+700) #03 pc 0000fa13 /system/lib/libc.so (free+10) #04 pc 00018f15 /system/lib/libandroidfw.so (android::StreamingZipInflater::~StreamingZipInflater()+44) #05 pc 0000dc3f /system/lib/libandroidfw.so (android::_CompressedAsset::getBuffer(bool)+62) #06 pc 000177c3 /system/lib/libandroidfw.so (android::ResTable::add(android::Asset*, android::Asset*, int, bool)+22) #07 pc 00010a33 /system/lib/libandroidfw.so (android::AssetManager::appendPathToResTable(android::AssetManager::asset_path const&, unsigned int*) const+270) #08 pc 00010e5f /system/lib/libandroidfw.so (android::AssetManager::getResTable(bool) const+102) #09 pc 00080935 /system/lib/libandroid_runtime.so #10 pc 0001a963 /data/dalvik-cache/arm/system@framework@boot.oat 之前抓的system server double free crash时的coredump中的thread trace: 2、查看backtrace对应的源码发现是在_CompressedAsset::getBuffer函数中的delete mZipInflater;时挂掉的,为什么? * Get a pointer to a read-only buffer of data. * The first time this is called, we expand the compressed data into a * buffer. const void* _CompressedAsset::getBuffer(bool) unsigned char* buf = NULL; if (mBuf != NULL) return mBuf; * Allocate a buffer and read the file into it. buf = new unsigned char[mUncompressedLen]; if (buf == NULL) { ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen); goto bail; if (mMap != NULL) { if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf, mUncompressedLen, mCompressedLen)) goto bail; } else { assert(mFd >= 0); * Seek to the start of the compressed data. if (lseek(mFd, mStart, SEEK_SET) != mStart) goto bail; * Expand the data into it. if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen, mCompressedLen)) goto bail; * Success - now that we have the full asset in RAM we * no longer need the streaming inflater delete mZipInflater; mZipInflater = NULL; mBuf = buf; buf = NULL; bail: delete[] buf; return mBuf; 3、继续查看源码,mZipInflater在_CompressedAsset构造时会被赋值为NULL,delete NULL不会有问题,也就是说mZipInflater一定被创建了,继续找代码发现是在openChunk函数中被创建的 * Constructor. _CompressedAsset::_CompressedAsset(void) : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0), mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL) * Open a chunk of compressed data in a mapped region. * Nothing is expanded until the first read call. status_t _CompressedAsset::openChunk(FileMap* dataMap, size_t uncompressedLen) assert(mFd < 0); // no re-open assert(mMap == NULL); assert(dataMap != NULL); mMap = dataMap; mStart = -1; // not used mCompressedLen = dataMap->getDataLength(); mUncompressedLen = uncompressedLen; assert(mOffset == 0); if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) { mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen); return NO_ERROR; 4、回到原来的问题,既然mZipInflater一定被创建了,那么为什么delete它时会挂掉?内存被踩?野指针?double free?仔细看_CompressedAsset以及Asset的代码,以及出问题时的backtrace,发现被踩的可能性比较小,因为每次都是死在同一个地方,如果是被踩应该是随机的死,不会每次都死在同一个地方,好了先排除内存被踩的可能性。 5、继续分析是否有野指针的可能,野指针正常是分配了一块内存用指针记录下来内存地址,然后free之后没有置空指针或者是指针变量没有初始化是随机值,导致在使用这个指针的时候出现问题,通过看mZipInflater被使用的代码,发现既不是没有初始化的野指针,也不是free之后没有置空的野指针,好了又排除了野指针的可能。 6、继续分析double free的可能,其实free之后没有置空再次使用并free的野指针是double free的一种简单情况,通过看代码发现mZipInflater被delete之后会置空,所以现在就要考虑一种复杂的double free情况,就是多线程并发时,同时走到了delete mZipInflater;导致第二个线程double free,从出问题的backtrace以及对应的_CompressedAsset的getBuffer代码来看这种情况是很有可能发生的,因为getBuffer以及getBuffer的调用者没有对getBuffer中的临界资源加锁以及做任何同步来保证线程安全,再看一下getBuffer的代码: const void* _CompressedAsset::getBuffer(bool) unsigned char* buf = NULL; if (mBuf != NULL) return mBuf; * Allocate a buffer and read the file into it. buf = new unsigned char[mUncompressedLen]; if (buf == NULL) { ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen); goto bail; if (mMap != NULL) { if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf, mUncompressedLen, mCompressedLen)) goto bail; } else { assert(mFd >= 0); * Seek to the start of the compressed data. if (lseek(mFd, mStart, SEEK_SET) != mStart) goto bail; * Expand the data into it. if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen, mCompressedLen)) goto bail; * Success - now that we have the full asset in RAM we * no longer need the streaming inflater delete mZipInflater; mZipInflater = NULL; mBuf = buf; buf = NULL; bail: delete[] buf; return mBuf; 考虑下面的场景: Thread A调用getBuffer Thread B调用getBuffer Thread A走到delete mZipInflater Thread B在Thread A走完delete mZipInflater还没有执行mZipInflater = NULL时也走到了delete mZipInflater 如果同时有多个上面的线程做这样的事情,上面的场景是很容易出现的,所以就发生了double free,该如何解决? 7、通过初步分析backtrace和review代码,发现上面的调用代码的逻辑关系都是对的,上层的调用以及AssetManager都对自己的操作在内部有lock来同步了,而发生问题时是两个不同的AssetManager访问了同一个Asset对象导致的,虽然每个AssetManager都对自己操作进行了同步,但是它们的同步无法对公共的Asset对象生效,既然如此那我们就在Asset自己内部来加lock同步吧,初步方案如下: 代码也一并给AOSP去review了,patch进了我们的代码库之后同样的问题也没有再出现过了,但是问题就这样结束了吗?当然没有,因为这样的剧情没意思,观众喜欢跌宕起伏的剧情! 四、深入分析 1、很遗憾,AOSP不采纳这个patch,原因是_CompressedAsset是非线程安全的,需要使用者自己保证线程安全,需要我们找到使用者并使其保证线程安全。好你丫的,给你提patch提高系统稳定性,你还挑三拣四的。。。。。不过话说回来我们还得再争取争取,与AOSP的人理论,说这个patch至少能提升系统稳定性啥的,不过最终理论的结果是codereview-2变成了两个,有兴趣的观众可以猛击链接查看:https://android-review.googlesource.com/#/c/235040/ 2、冷静下来仔细想想,AOSP的人说的也有道理(看起来有点像鸡蛋里挑骨头),那鸡蛋里是不是真有骨头?好吧,接下来就继续找这个骨头:_CompressedAsset的使用者为什么没有做好同步保护? 3、对着代码看呀看,对着backtrace看呀看,终于发现了端倪 #0 0xb6f6f546 in arena_run_dalloc (arena=arena@entry=0xb5127040, run=<optimized out>, dirty=dirty@entry=true, cleaned=cleaned@entry=false) at external/jemalloc/src/arena.c:1270 #1 0xb6f705c2 in je_arena_dalloc_large_locked (arena=arena@entry=0xb5127040, chunk=chunk@entry=0x8a900000, ptr=ptr@entry=0x8a90a000) at external/jemalloc/src/arena.c:2063 #2 0xb6f705ec in je_arena_dalloc_large (arena=0xb5127040, chunk=chunk@entry=0x8a900000, ptr=ptr@entry=0x8a90a000) at external/jemalloc/src/arena.c:2071 #3 0xb6f783dc in je_arena_dalloc (try_tcache=true, ptr=0x8a90a000, chunk=0x8a900000) at external/jemalloc/include/jemalloc/internal/arena.h:1168 #4 je_idalloct (try_tcache=true, ptr=0x8a90a000) at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:774 #5 je_iqalloct (try_tcache=true, ptr=0x8a90a000) at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:793 #6 je_iqalloc (ptr=0x8a90a000) at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:800 #7 ifree (ptr=0x8a90a000) at external/jemalloc/src/jemalloc.c:1228 #8 0xb6f41a14 in free (mem=<optimized out>) at bionic/libc/bionic/malloc_debug_common.cpp:251 #9 0xb6c74f06 in android::StreamingZipInflater::~StreamingZipInflater (this=0x8eb24280, __in_chrg=<optimized out>) at frameworks/base/libs/androidfw/StreamingZipInflater.cpp:93 #10 0xb6c69c42 in android::_CompressedAsset::getBuffer (this=0x8eb32330) at frameworks/base/libs/androidfw/Asset.cpp:887 #11 0xb6c737c4 in android::ResTable::add (this=0x8e870c00, asset=asset@entry=0x8eb32330, idmapAsset=idmapAsset@entry=0x0, cookie=cookie@entry=5, copyData=copyData@entry=false) at frameworks/base/libs/androidfw/ResourceTypes.cpp:3371 #12 0xb6c6ca36 in android::AssetManager::appendPathToResTable (this=this@entry=0x8e838390, ap=..., entryIdx=entryIdx@entry=0x89de3444) at frameworks/base/libs/androidfw/AssetManager.cpp:678 #13 0xb6c6ce62 in android::AssetManager::getResTable (this=0x8e838390, required=<optimized out>) at frameworks/base/libs/androidfw/AssetManager.cpp:727 #14 0xb6c6cebc in android::AssetManager::getResources (this=<optimized out>, required=required@entry=true) at frameworks/base/libs/androidfw/AssetManager.cpp:813 #15 0xb6ea5b20 in android::android_content_AssetManager_getStringBlockCount (env=<optimized out>, clazz=<optimized out>) at frameworks/base/core/jni/android_util_AssetManager.cpp:886 #0 syscall () at bionic/libc/arch-arm/bionic/syscall.S:44 #1 0xb6f46016 in __futex (timeout=0x0, value=2, op=128, ftx=0xb5127048) at bionic/libc/private/bionic_futex.h:45 #2 __futex_wait_ex (ftx=ftx@entry=0xb5127048, shared=shared@entry=false, value=value@entry=2, timeout=0x0) at bionic/libc/private/bionic_futex.h:66 #3 0xb6f463ac in _normal_lock (shared=0, mutex=0xb5127048) at bionic/libc/bionic/pthread_mutex.cpp:337 #4 pthread_mutex_lock (mutex=mutex@entry=0xb5127048) at bionic/libc/bionic/pthread_mutex.cpp:457 #5 0xb6f6fb78 in je_malloc_mutex_lock (mutex=0xb5127048) at external/jemalloc/include/jemalloc/internal/mutex.h:77 #6 arena_bin_nonfull_run_get (bin=0xb5127300, arena=0xb5127040) at external/jemalloc/src/arena.c:1468 #7 arena_bin_malloc_hard (arena=arena@entry=0xb5127040, bin=bin@entry=0xb5127300) at external/jemalloc/src/arena.c:1515 #8 0xb6f6fdf6 in je_arena_tcache_fill_small (arena=0xb5127040, tbin=tbin@entry=0x8eb0e098, binind=binind@entry=5, prof_accumbytes=prof_accumbytes@entry=0) at external/jemalloc/src/arena.c:1573 #9 0xb6f7d97a in je_tcache_alloc_small_hard (tcache=tcache@entry=0x8eb0e000, tbin=tbin@entry=0x8eb0e098, binind=binind@entry=5) at external/jemalloc/src/tcache.c:72 #10 0xb6f79552 in je_tcache_alloc_small (zero=false, size=<optimized out>, tcache=0x8eb0e000) at external/jemalloc/include/jemalloc/internal/tcache.h:272 #11 je_arena_malloc (try_tcache=true, zero=false, size=<optimized out>, arena=0x0) at external/jemalloc/include/jemalloc/internal/arena.h:1074 #12 je_imalloct (arena=0x0, try_tcache=true, size=<optimized out>) at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:647 #13 je_imalloc (size=<optimized out>) at external/jemalloc/include/jemalloc/internal/jemalloc_internal.h:656 #14 imalloc_body (usize=<synthetic pointer>, size=<optimized out>) at external/jemalloc/src/jemalloc.c:920 #15 je_malloc (size=<optimized out>) at external/jemalloc/src/jemalloc.c:932 #16 0xb6f41a40 in malloc (bytes=<optimized out>) at bionic/libc/bionic/malloc_debug_common.cpp:259 #17 0xb6f0eb0c in operator new (size=size@entry=44) at bionic/libc/bionic/new.cpp:26 #18 0xb6c73230 in android::ResTable::parsePackage (this=this@entry=0x8eb85180, pkg=pkg@entry=0x8a923b88, header=0x8eb58040) at frameworks/base/libs/androidfw/ResourceTypes.cpp:5806 #19 0xb6c73600 in android::ResTable::addInternal (this=this@entry=0x8eb85180, data=<optimized out>, data@entry=0x8a91a000, dataSize=<optimized out>, idmapData=idmapData@entry=0x0, idmapDataSize=idmapDataSize@entry=0, cookie=cookie@entry=5, copyData=copyData@entry=false) at frameworks/base/libs/androidfw/ResourceTypes.cpp:3539 #20 0xb6c73824 in android::ResTable::add (this=0x8eb85180, asset=asset@entry=0x8eb32330, idmapAsset=idmapAsset@entry=0x0, cookie=cookie@entry=5, copyData=copyData@entry=false) at frameworks/base/libs/androidfw/ResourceTypes.cpp:3389 #21 0xb6c6ca36 in android::AssetManager::appendPathToResTable (this=this@entry=0x8eb2c160, ap=..., entryIdx=entryIdx@entry=0x8b8d4444) at frameworks/base/libs/androidfw/AssetManager.cpp:678 #22 0xb6c6ce62 in android::AssetManager::getResTable (this=0x8eb2c160, required=<optimized out>) at frameworks/base/libs/androidfw/AssetManager.cpp:727 #23 0xb6c6cebc in android::AssetManager::getResources (this=<optimized out>, required=required@entry=true) at frameworks/base/libs/androidfw/AssetManager.cpp:813 #24 0xb6ea5b20 in android::android_content_AssetManager_getStringBlockCount (env=<optimized out>, clazz=<optimized out>) at frameworks/base/core/jni/android_util_AssetManager.cpp:886 "Binder_1" prio=5 tid=10 Native at android.content.res.AssetManager.getStringBlockCount(Native method) at android.content.res.AssetManager.makeStringBlocks(AssetManager.java:263) at android.content.res.AssetManager.ensureStringBlocks(AssetManager.java:255) - locked <0x138b32bf> (a android.content.res.AssetManager) at android.content.res.Resources.<init>(Resources.java:240) at android.content.res.MiuiResources.<init>(MiuiResources.java:109) at android.app.ResourcesManager.getTopLevelResources(ResourcesManager.java:231) at android.app.ActivityThread.getTopLevelResources(ActivityThread.java:1630) at android.app.LoadedApk.getResources(LoadedApk.java:533) at android.app.ContextImpl.<init>(ContextImpl.java:2256) at android.app.ContextImpl.createApplicationContext(ContextImpl.java:2102) at android.content.ContextWrapper.createApplicationContext(ContextWrapper.java:671) at android.app.Notification$Builder.rebuild(Notification.java:3205) at android.service.notification.NotificationListenerService$INotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:615) at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:71) at android.os.Binder.execTransact(Binder.java:446) "Binder_3" prio=5 tid=20 Native at android.content.res.AssetManager.getStringBlockCount(Native method) at android.content.res.AssetManager.makeStringBlocks(AssetManager.java:263) at android.content.res.AssetManager.ensureStringBlocks(AssetManager.java:255) - locked <0x38f64cea> (a android.content.res.AssetManager) at android.content.res.Resources.<init>(Resources.java:240) at android.content.res.MiuiResources.<init>(MiuiResources.java:109) at android.app.ResourcesManager.getTopLevelResources(ResourcesManager.java:231) at android.app.ActivityThread.getTopLevelResources(ActivityThread.java:1630) at android.app.LoadedApk.getResources(LoadedApk.java:533) at android.app.ContextImpl.<init>(ContextImpl.java:2256) at android.app.ContextImpl.createApplicationContext(ContextImpl.java:2102) at android.content.ContextWrapper.createApplicationContext(ContextWrapper.java:671) at android.app.Notification$Builder.rebuild(Notification.java:3205) at android.service.notification.NotificationListenerService$INotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:615) at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:71) at android.os.Binder.execTransact(Binder.java:446) 4、首先捋清问题相关的Native关键对象之间的包含关系: AssetManager包含ZipSet,ZipSet mZipSet; ZipSet包含SharedZip,mutable Vector<sp<SharedZip> > mZipFile; SharedZip包含Asset,Asset* mResourceTableAsset; 这里的mResourceTableAsset就对应我们出问题时的_CompressedAsset,既然上面的初步分析是多个AssetManager访问同一个Asset导致的问题,那可能有观众会想知道不同的AssetManager是如何共享同一个Asset,好吧,接下来满足观众的好奇心,继续看代码: class SharedZip : public RefBase { public: static sp<SharedZip> get(const String8& path, bool createIfNotPresent = true); ZipFileRO* getZip(); Asset* getResourceTableAsset(); Asset* setResourceTableAsset(Asset* asset); ResTable* getResourceTable(); ResTable* setResourceTable(ResTable* res); bool isUpToDate(); void addOverlay(const asset_path& ap); bool getOverlay(size_t idx, asset_path* out) const; protected: ~SharedZip(); private: SharedZip(const String8& path, time_t modWhen); SharedZip(); // <-- not implemented String8 mPath; ZipFileRO* mZipFile; time_t mModWhen; Asset* mResourceTableAsset; ResTable* mResourceTable; Vector<asset_path> mOverlays; static Mutex gLock; static DefaultKeyedVector<String8, wp<SharedZip> > gOpen; 发现原来SharedZip的get方法是静态的,并且还有一个静态的gOpen成员,继续看一下SharedZip的get方法是如何实现的: sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path, bool createIfNotPresent) AutoMutex _l(gLock); time_t modWhen = getFileModDate(path); sp<SharedZip> zip = gOpen.valueFor(path).promote(); if (zip != NULL && zip->mModWhen == modWhen) { return zip; if (zip == NULL && !createIfNotPresent) { return NULL; zip = new SharedZip(path, modWhen); gOpen.add(path, zip); return zip; get的时候会先在gOpen里面找,如果找到了就直接返回,找不到就new一个,所以对于同时存在的多个AssetManager,在传同样的path参数时不同的AssetManager会获取到同一个SharedZip,其实不同的AssetManager共享的是SharedZip,看到这里就明白了,还记得上面的对象包含关系吗?不记得也没关系,再看一下,SharedZip包含Asset,也就是说SharedZip共享,它的Asset成员也同样被共享。 5、在分析代码的过程中我们还发现SharedZip是继承RefBase的,这样就可以通过sp和wp来管理SharedZip对象的生命周期,同时get方法返回的是sp被存在ZipSet的mutable Vector AssetManager::SharedZip::~SharedZip() if (kIsDebug) { ALOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath); if (mResourceTable != NULL) { delete mResourceTable; if (mResourceTableAsset != NULL) { delete mResourceTableAsset; if (mZipFile != NULL) { delete mZipFile; ALOGV("Closed '%s'\n", mPath.string()); _CompressedAsset::~_CompressedAsset(void) close(); a、线程A被调度到,先调用getZipResourceTableAsset没有获取到asset,然后调用openNonAssetInPathLocked获得了一个 asset对象并调用setZipResourceTableAsset将这个对象保存,在保存的过程中会先AutoMutex _l(gLock);然后 mResourceTableAsset = asset;最后asset->getBuffer(true);这个getBuffer相对耗时因为有IO相关的操作 b、线程B被调度到,调用getZipResourceTableAsset获取到了线程A刚刚mResourceTableAsset = asset;的asset, 并继续执行到了mResources->add(ass, idmap, *entryIdx + 1, !shared);在add函数中会调用asset的getBuffer() c、线程A再次被调度到,并执行到了delete mZipInflater; d、线程B在线程A刚执行完delete mZipInflater;时也再次被调度到,并执行到了delete mZipInflater; 接着double free的问题就发生了,问题对应的关键代码如下:
"InputDispatcher" prio=10 tid=31 Native | group="main" sCount=1 dsCount=0 obj=0x133d90a0 self=0x7f73f8c800 | sysTid=1662 nice=-8 cgrp=default sched=0/0 handle=0x7f5f80b440 | state=S schedstat=( 1388341757 1177043722 12975 ) utm=81 stm=57 core=2 HZ=100 | stack=0x7f5f70f000-0x7f5f711000 stackSize=1013KB | held mutexes= kernel: (couldn't read /proc/self/task/1662/stack) native: #00 pc 00000000000684e0 /system/lib64/libc.so (__ioctl+4) native: #01 pc 0000000000072508 /system/lib64/libc.so (ioctl+100) native: #02 pc 000000000002d584 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14talkWithDriverEb+164) native: #03 pc 000000000002e050 /system/lib64/libbinder.so (_ZN7android14IPCThreadState15waitForResponseEPNS_6ParcelEPi+104) native: #04 pc 000000000002e2c4 /system/lib64/libbinder.so (_ZN7android14IPCThreadState8transactEijRKNS_6ParcelEPS1_j+176) native: #05 pc 0000000000025654 /system/lib64/libbinder.so (_ZN7android8BpBinder8transactEjRKNS_6ParcelEPS1_j+64) native: #06 pc 00000000000e02cc /system/lib64/libandroid_runtime.so (???) native: #07 pc 0000000000505e64 /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_os_BinderProxy_transactNative__ILandroid_os_Parcel_2Landroid_os_Parcel_2I+200) at android.os.BinderProxy.transactNative(Native method) at android.os.BinderProxy.transact(Binder.java:503) at android.app.IActivityController$Stub$Proxy.activityStarting(IActivityController.java:172) at com.android.server.am.ExtraActivityManagerService.checkStartActivityLocked(ExtraActivityManagerService.java:259) at com.android.server.am.ExtraActivityManagerService.checkStartActivityPermission(ExtraActivityManagerService.java:207) - locked <0x025805bb> (a com.android.server.am.ActivityManagerService) at com.android.server.am.ActivityStackSupervisor.startActivityMayWait(ActivityStackSupervisor.java:983) at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:3962) at android.app.ContextImpl.startActivityAsUser(ContextImpl.java:696) at android.app.ContextImpl.startActivityAsUser(ContextImpl.java:675) at com.android.server.policy.PhoneWindowManager.startActivityAsUser(PhoneWindowManager.java:3250) at com.android.server.policy.PhoneWindowManager.startDockOrHome(PhoneWindowManager.java:6545) at com.android.server.policy.PhoneWindowManager.launchHomeFromHotKey(PhoneWindowManager.java:3391) at com.android.server.policy.PhoneWindowManager.launchHomeFromHotKey(PhoneWindowManager.java:3342) at com.android.server.policy.BaseMiuiPhoneWindowManager.interceptKeyBeforeDispatching(BaseMiuiPhoneWindowManager.java:643) at com.android.server.wm.InputMonitor.interceptKeyBeforeDispatching(InputMonitor.java:375) at com.android.server.input.InputManagerService.interceptKeyBeforeDispatching(InputManagerService.java:1751) 调用ActivityController的activityStarting接口时阻塞了? 这里说明一下,通过名字我们大概能猜到ActivityController的作用,简单来说就是一个监控器,提供的接口如下: * Testing interface to monitor what is happening in the activity manager * while tests are running. Not for normal application development. * {@hide} interface IActivityController * The system is trying to start an activity. Return true to allow * it to be started as normal, or false to cancel/reject this activity. boolean activityStarting(in Intent intent, String pkg); * The system is trying to return to an activity. Return true to allow * it to be resumed as normal, or false to cancel/reject this activity. boolean activityResuming(String pkg); * An application process has crashed (in Java). Return true for the * normal error recovery (app crash dialog) to occur, false to kill * it immediately. boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace); * Early call as soon as an ANR is detected. int appEarlyNotResponding(String processName, int pid, String annotation); * An application process is not responding. Return 0 to show the "app * not responding" dialog, 1 to continue waiting, or -1 to kill it * immediately. int appNotResponding(String processName, int pid, String processStats); * The system process watchdog has detected that the system seems to be * hung. Return 1 to continue waiting, or -1 to let it continue with its * normal kill. int systemNotResponding(String msg); 为什么调用一下这个ActivityController的activityStarting就被block了呢?这个ActivityController是谁?在那个进程中? 继续往下分析,看看都有谁实现了ActivityController 比较可疑的有两个,一个是monkey的,一个是Am的,然后就问了一下JT同学问题发生的场景,是不是在做monkey测试或者执行adb shell am相关的命令操作?他说是的,问题发生当时在执行am命令。 好,问题有了一点眉目,但是为什么执行am命令会导致阻塞并重启呢?继续看代码 @Override public boolean activityStarting(Intent intent, String pkg) { synchronized (this) { System.out.println("** Activity starting: " + pkg); return true; 执行activityStarting需要ActivityController自己本身的锁,如果这个时候拿不到锁就会block system server,那就继续看看这个锁还会被谁用,看了一圈代码,发现锁的粒度都非常小,不像会block的样子,这就是上面为什么说traces比较诡异的原因,block在了不应该block的地方,那接下来怎么办呢? 通过初步分析我们觉得调用ActivityController的activityStarting不应该被block,而这次调用是一个binder call,跨进程的,有没有可能没有到达am进程那边?或者am进程出了什么问题?或者另有隐情?赶紧再问问JT同学当时的操作步骤,看能不能重现问题,然后dump一下相关信息,JT同学不愧是专业的测试,操作现场还保留着,执行的一堆命令也还在终端上,赶紧再操作一遍 ps | grep ^u am monitor ctrl+z 问题奇迹复现了,点击一个icon去startactivity之后系统就卡死了,赶紧在一分钟内看看am进程的信息 ps | grep am 没有am的信息?那就看看app_process ps | grep app_process 原来am小伙通过app_process起来之后连名字都没改。。。。 等等,我好像擦觉到了一丝异常?上面为什么要用ctrl+z?赶紧问JT同学,他说这是HH同学敲的命令,难道HH同学是想停止am monitor继续在终端里面输命令吗?如果同样的需求我都是用ctrl+c的啊?他为啥要用ctrl+z,有特别的含义吗?赶紧去问一下HH同学,他说他只是想停止am monitor继续在终端里面输命令,没有特殊含义,而且他都是这么用的?这下问题就看到曙光了,这两个组合键操作虽然都能满足停止当前任务继续输入其他命令的需求,但是ctrl+z和ctrl+c是有本质区别的,在Linux下区别是什么呢? ctrl-c: ( kill foreground process ) 发送 SIGINT 信号给前台进程组中的所有进程,强制终止程序的执行 ctrl-z: ( suspend foreground process ) 发送 SIGTSTP 信号给前台进程组中的所有进程,常用于挂起一个进程,而并 非结束进程,用户可以使用使用fg/bg操作恢复执行前台或后台的进程。fg命令在前台恢复执行被挂起的进 程,此时可以使用ctrl-z再次挂起该进程,bg命令在后台恢复执行被挂起的进程,而此时将无法使用ctrl-z 再次挂起该进程; 一个比较常用的功能: 正在使用vi编辑一个文件时,需要执行shell命令查询一些需要的信息,可以使用ctrl-z挂起vi,等执行 完shell命令后再使用fg恢复vi继续编辑你的文件(当然,也可以在vi中使用!command方式执行shell命令, 但是没有该方法方便)。 ctrl-d: ( Terminate input, or exit shell ) 一个特殊的二进制值,表示 EOF,作用相当于在终端中输入exit后回车; 类似的组合键操作还有以下几个: ctrl-/ 发送 SIGQUIT 信号给前台进程组中的所有进程,终止前台进程并生成 core 文件 ctrl-s 中断控制台输出 ctrl-q 恢复控制台输出 ctrl-l 清屏 看到这里我想有些同学可能已经知道问题的原因了,am monitor执行之后,am进程会向AMS注册一个ActivityController,启动一个activity的时候会回调ActivityController的activityStarting接口,但是HH同学用Ctrl+Z挂起am进程之后,AMS在通过binder call activityStarting的时候就被block了,因为activityStarting是同步调用,虽然binder driver把这次调用的transaction放到了am进程的todo list里面,但是am进程因为是STOPED状态所以不会拿到时间片去执行todo list里面的transaction,进而也不会有reply,就这样system server就被卡住了,瞬间又加重了Android系统的脆弱感。。。 到这就结束了吗?当然没有,我们细心的JT同学又发现说好的一分钟watchdog重启,都等了好几分钟了怎么还没有重启?好问题,继续看代码 代码是最清晰、最好的解释,原来watchdog线程也依赖ActivityController,会调用ActivityController的systemNotResponding,从而导致连watchdog线程也被卡住了,本来说好的一分钟重启就做不到了,近在咫尺的重启代码执行不了,连GOODBYE也没出来就死了,哎,生活就是这样,这就是生活。。。。 Slog.w(TAG, "*** GOODBYE!"); Process.killProcess(Process.myPid()); System.exit(10); 下一步动作 虽然生活总让人无奈,但我们还是要想办法做的更好,接下来会尝试改善watchdog线程依赖ActivityController的机制,不能因为ActivityController出了问题就永远卡住系统,之前就分析过一个monkey导致的系统永远卡住的问题,也是因为在调用ActivityController的systemNotResponding时候卡住:由MONKEY测试引发的跨多个进程的ANDROID系统死锁问题分析 通过下面的步骤可以在所有Android手机上复现这个问题: am monitor ctrl+z 随便点击一个桌面图标或者按下home键
10-10 15:26:47.762 3152 3152 W ContextImpl: Calling a method in the system process without a qualified user: android.app.ContextImpl.bindService:1295 miui.provider.ExtraGuard.init:69 com.android.server.pm.PackageManagerServiceInjector.initExtraGuard:429 com.android.server.pm.PackageManagerService.systemReady:15195 com.android.server.SystemServer.startOtherServices:1133 继续看log发现以下异常信息 10-10 15:27:07.784 3152 3221 E ActivityManager: Attempt to launch receivers of broadcast intent Intent { act=android.net.conn.DATA_ACTIVITY_CHANGE (has extras) } before boot completion 这说明系统启动没有正常完成,ActivityManager的状态还没有就绪,难道system server的启动流程出现了异常? 赶紧打出system server的traces看一下 "main" prio=5 tid=1 Native | group="main" sCount=1 dsCount=0 obj=0x75f14fb8 self=0x558db1ec10 | sysTid=3152 nice=-2 cgrp=default sched=0/0 handle=0x7fb6a3afc8 | state=S schedstat=( 5043600992 157397768 10523 ) utm=367 stm=137 core=5 HZ=100 | stack=0x7fe85de000-0x7fe85e0000 stackSize=8MB | held mutexes= kernel: __switch_to+0x70/0x7c kernel: SyS_epoll_wait+0x2a0/0x32c kernel: SyS_epoll_pwait+0xa4/0x120 kernel: cpu_switch_to+0x48/0x4c native: #00 pc 0000000000069be4 /system/lib64/libc.so (__epoll_pwait+8) native: #01 pc 000000000001cca4 /system/lib64/libc.so (epoll_pwait+32) native: #02 pc 000000000001be88 /system/lib64/libutils.so (_ZN7android6Looper9pollInnerEi+144) native: #03 pc 000000000001c268 /system/lib64/libutils.so (_ZN7android6Looper8pollOnceEiPiS1_PPv+80) native: #04 pc 00000000000d2580 /system/lib64/libandroid_runtime.so (_ZN7android18NativeMessageQueue8pollOnceEP7_JNIEnvP8_jobjecti+48) native: #05 pc 000000000000082c /data/dalvik-cache/arm64/system@framework@boot.oat (Java_android_os_MessageQueue_nativePollOnce__JI+144) at android.os.MessageQueue.nativePollOnce(Native method) at android.os.MessageQueue.next(MessageQueue.java:323) at android.os.Looper.loop(Looper.java:135) at com.android.server.SystemServer.run(SystemServer.java:299) at com.android.server.SystemServer.main(SystemServer.java:181) at java.lang.reflect.Method.invoke!(Native method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:738) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:628) 看起来主线程没有什么异常,已经进入主消息循环了,也就是说startOtherServices已经走完了,都走完了为啥activitymanager的状态还没有就绪?看代码吧 已知PMS的systemready已经走了,整个startOtherServices也走完了,所以AMS的systemready一定调用了,但是调用了为什么没有ready? 仔细看上面的代码可以看到一些端倪,因为调用AMS的systemready时传入的是一个runnable,runnable里面是启动systemui并通知一堆系统service running,从代码中可以看到这个runnable是在systemready函数的后半部分执行的,而当前出问题的状态是这个runnable并没有执行。 除了这个runnable之外还有一个关键的状态就是AMS的mSystemReady,通过am start发现log中打出来AMS没有ready的信息,产生这种状态的唯一的可能就是在AMS的systemready函数中没有正常执行完毕。 查看代码发现OTA后第一次调用到AMS的systemready之后mDidUpdate为false,mWaitingUpdate也是false,继续往下走到deliverPreBootCompleted,这里又传入了一个runnable,非常关键的一步,如果是OTA后第一次调用deliverPreBootCompleted会返回true给mWaitingUpdate,以为在deliverPreBootCompleted里面会发送ACTION_PRE_BOOT_COMPLETED给所有注册的receiver,并且添加FLAG_RECEIVER_BOOT_UPGRADE,在发送广播的时候就从同步调用变成了异步,返回后继续执行,mWaitingUpdate为true,然后return出去,mSystemReady在这一次没有机会设置为true,那什么时候设置呢?AMS ready的剩余代码什么时候执行呢?带着问题继续看代码 还记得上面调用deliverPreBootCompleted时传入的runnable吗?当OTA后第一开机的ACTION_PRE_BOOT_COMPLETED广播发送给所有的receiver之后就会调用这个runnable,它里面会将mDidUpdate置为true并再次调用AMS的systemready函数,这次会正常执行完所以的流程,包括设置mSystemReady等状态为true,调用startOtherServices传入的runnable启动systemui和notify systemservice running,启动home等,但是现在这些都没有做。。。好吧,一言不合说不做就不做,Android就是任性! 赶紧看看它为啥没做,deliverPreBootCompleted也调用了,runnable也传过去,那问题就出在deliverPreBootCompleted里面的广播发送了?要想知道,还得看代码和log 好了,代码和log看到这里,基本定位到了大概原因,这个广播是有序发送的,并且是显式指定component的方式,每个发送完了都会把结果给PreBootContinuation并调用performReceive发送下一个,如果都发完了会把之前传入的runnable post 到消息队列里面,显而易见,没有走到post这一步,也就是说上面的广播在发送过程中出问题了,出什么问题了呢?还好这个问题可以必现,赶紧复现追一下代码,发现需要给6个receiver发送广播,出问题时只发送到第二个contactsproviders的时候就断了,log也对应了这一点,并且log中发现了contactsproviders升级数据库版本的信息,同时又仔细看了一下system server的trace,发现有个关于getprovider的线程不是太正常,一直处于waiting状态 contactsproviders执行之后一直没有完成,而AMS这么又有一个binder 线程一直在等待provider,这是不是有某种对应关系?赶紧看一下contactsproviders所在进程的traces,发现真有关系,在ContactsUpgradeReceiver里面执行数据库升级之后去请求一个content provider的时候block住了 真是踏破铁鞋无觅处,得来好不费功夫啊,system server一直在等待你完成通知它,你却在这睡大觉,但是又引来一个问题,这里的调用为什么会一直block?继续看代码,断点追代码,在getContentProviderImpl里发现了蹊跷,为contactsprovider 请求的yellowpage provider去startProcessLocked的时候由于system还没有ready所以start的操作被hold住了,所以导致contactsprovider的query操作被一直block 通过分析代码发现,不允许yellowpageprovider的进程起来是合理的,不合理的是contactsprovider在OTA过第一次开机upgrade的过程中不合理的请求了query yellowpageprovider,从而block整个系统启动,导致卡白米 到这里可能有细心的同学会问,为什么之前OTA没问题,今天就有问题了? 原因是contactprovider的数据库版本有升级,在升级的同时触发了T9索引重建,重建的过程中用到了yellowpageprovider,来一下change再配合上面的traces可能会更直观 这个索引重建不是不能做,而是不能在OTA第一次开机过程中调用upgradereceiver的做,可以等正常开机后,BOOT_COMPLETED广播发出去的时候再触发做 可能还有更细心的同学会问,为什么卡住之后再重启一下就好了呢? 这是因为在ContactsUpgradeReceiver中会先判断DB VERSION,第一次因为升级了所以不相等,就走升级流程,升级之前先把最新的DB VERSION put到了preference中,这样第二次的时候因为相等了就不会再走升级流程了,所以就不会卡白米了
工作职责: 1.从Linux Kernel、Framework(Java+Native)等多个层面改进、增强Andorid系统和MIUI,提升用户体验 2.负责Android系统稳定性,解决各类系统问题,如死机重启、FC、ANR等各类疑难问题 3.研究和跟进Android版本升级及最新Linux kernel机制和特性 4.Android系统及MIUI相关功能设计、开发和维护 5.将MIUI适配到各芯片平台,高通,MTK,Intel等 岗位要求: 1.熟悉Java/C/C++语言,了解Android App开发 2.熟悉Android Framework(熟悉一种service即可,比如AMS/WMS等)者优先 3.熟悉虚拟机(ART、Dalvik)、系统C库、Linux Kernel主要模块(内存、调度、IO等)的技术架构者优先 4.熟悉常用调试工具Android Studio、Eclipse、GDB、strace等 5.优秀的分析问题和解决问题能力,对解决具有挑战性问题充满激情 工作地点: 北京市海淀区安宁庄东路融科融智 简历发至:songjinshi@xiaomi.com 小米公司正式成立于2010年4月,是一家专注于高端智能手机、互联网电视以及智能家居生态链建设的创新型科技企业。小米公司在互联网电视机顶盒、互联网智能电视,以及家用智能路由器和智能家居产品等领域也颠覆了传统市场。截至2014年年底,小米公司旗下生态链企业已达22家,其中紫米科技的小米移动电源、华米科技的小米手环、智米科技的小米空气净化器、加一联创的小米活塞耳机等产品均在短时间内迅速成为影响整个中国消费电子市场的明星产品。 小米生态链建设将秉承开放、不排他、非独家的合作策略,和业界合作伙伴一起推动智能生态链建设。欢迎您加入! 欢迎投递简历到songjinshi@xiaomi.com,收到简历后我们会马上处理!
解决方案: 当前是根据当前问题场景即竖屏强制更改为横屏的需求而做的改动,基本是hardcode定义的状态,总共修改有效代码行数5行,如果后续有其他需求或者需要更灵活的配置横屏和竖屏,可以采用编译开关生成系统属性的方式来控制,在系统中通过属性来判断当前的屏幕状态以达到灵活目的,可以在本次修改的基础上增加条件判断达到目的,具体修改代码如下图中圈红标识。 一、Android Framework Java层的修改: 1、WindowManagerService的默认Rotation修改: 2、PhoneWindowManager的Rotation返回修改: 二、Android Framework Native(C/C++)层的修改: 1、SurfaceFlinger的DispalyDevice中默认orientation的修改: 默认frame rect的宽高交换修改: 2、bootanimation中申请的surface frame buffer的宽高交换修改: 问题背景: 1、当前设备是基于Android4.4.2KK 2、将之前的横屏更换成功了竖屏 3、更换屏幕后无法达到预期的横屏效果,包括开机动画、屏幕横竖切换动画、锁屏界面等。
#include <stdlib.h> void Swap(int Arra[],unsigned int LeftIndex,unsigned int RightIndex) int TeampValue = Arra[LeftIndex]; Arra[LeftIndex]=Arra[RightIndex]; Arra[RightIndex]=TeampValue; void MinHeapFixDown(int Arra[],unsigned int StartIndex,unsigned int ArraSize) int TeampValue = Arra[StartIndex]; unsigned int MinValueIndexOfChild = 2*StartIndex+1; while(MinValueIndexOfChild<ArraSize) //printf("%u,%d,%d,%d\n",StartIndex,TeampValue,MinValueIndexOfChild,ArraSize); if (MinValueIndexOfChild+1<ArraSize&&Arra[MinValueIndexOfChild]>Arra[MinValueIndexOfChild+1]) MinValueIndexOfChild=MinValueIndexOfChild+1; if (Arra[MinValueIndexOfChild]>=TeampValue) break; Arra[StartIndex]=Arra[MinValueIndexOfChild]; StartIndex=MinValueIndexOfChild; MinValueIndexOfChild=2*StartIndex+1; Arra[StartIndex]=TeampValue; void BuildMinHeap(int Arra[],unsigned int ArraSize) if (ArraSize<2) return; //printf("build start\n"); for (unsigned int i = (ArraSize-1)/2+1; i >0; --i) MinHeapFixDown(Arra,i-1,ArraSize); //printf("build end\n"); void HeapSort(int Arra[],unsigned int ArraSize) BuildMinHeap(Arra,ArraSize); printf("ArraSize=%d\n",ArraSize); for (unsigned int i = ArraSize-1; i >=1; --i) Swap(Arra,0,i); MinHeapFixDown(Arra,0,i); int main() //int Arra[]={-6,-8,9,-3,-1,0,13,-15,28,-40}; int Arra[]={-6,10,-7,15,-9,12,50,-35,9}; HeapSort(Arra,sizeof(Arra)/sizeof(Arra[0])); for (int i = 0; i < sizeof(Arra)/sizeof(Arra[0]); ++i) printf("%d,",Arra[i] ); printf("\n"); #define InsertSortNumber 10 void InsertSort(int Arra[],unsigned int LowIndex,unsigned int HighIndex) printf("low=%d,high=%d\n",LowIndex,HighIndex); for (unsigned int i = LowIndex + 1; i <= HighIndex; ++i) int TempValue = Arra[i]; unsigned int j = i; for (; j > LowIndex && TempValue<Arra[j-1]; --j) Arra[j]=Arra[j-1]; printf("j=%d,i=%d\n",j,i); if(j!=i) { Arra[j]=TempValue; int GetPivotByMedianOfThree(int Arra[],unsigned int LowIndex,unsigned int HighIndex) int MedianIndex = LowIndex +(HighIndex - LowIndex)/2; if (Arra[MedianIndex]<Arra[LowIndex]) int TempValue = Arra[LowIndex]; Arra[LowIndex]=Arra[MedianIndex]; Arra[MedianIndex]=TempValue; if (Arra[MedianIndex]<Arra[HighIndex]) return Arra[MedianIndex]; else if (Arra[HighIndex]>Arra[LowIndex]) return Arra[HighIndex]; return Arra[LowIndex]; unsigned int Partition(int Arra[],unsigned int LowIndex,unsigned int HighIndex,int PivotValue) while(1){ while(Arra[LowIndex]<PivotValue) { LowIndex++; while(Arra[HighIndex]>PivotValue) { HighIndex--; if (LowIndex>HighIndex) return LowIndex; int TempValue = Arra[LowIndex]; Arra[LowIndex]=Arra[HighIndex]; Arra[HighIndex]=TempValue; LowIndex++; HighIndex--; void QuickSort(int Arra[],unsigned int LowIndex,unsigned int HighIndex) if ((HighIndex+1) - LowIndex > InsertSortNumber) int PivotValue = GetPivotByMedianOfThree(Arra,LowIndex,HighIndex); unsigned int MedianCutIndex = Partition(Arra,LowIndex,HighIndex,PivotValue); printf("PivotValue=%d,MedianCutIndex=%d\n",PivotValue,MedianCutIndex); QuickSort(Arra,LowIndex,MedianCutIndex-1); QuickSort(Arra,MedianCutIndex,HighIndex); else { InsertSort(Arra,LowIndex,HighIndex); int main() int Arra[] = {1,2,3,50,-5,-7,20,-3,-5,10,13,8,6,4,2,0,-2,-4,-6,-8,18}; //int Arra[] = {1,2,3,50,-5,-7,20,-3,10,8}; //int Arra[] = {3,4,6,8,5,1}; QuickSort(Arra,0,sizeof(Arra)/sizeof(Arra[0])-1); //InsertSort(Arra,0,sizeof(Arra)/sizeof(Arra[0])-1); printf("%d",Arra[0] ); for (unsigned int i = 1; i < sizeof(Arra)/sizeof(Arra[0]); ++i) printf(",%d",Arra[i] ); 待完善聚集重复元素的优化
温故知新,基础复习(一个有序从大到小不重复的数列,任意给出一个sum值,求出数列中所有满足和为sum的数对) #include<stdio.h> #include<stdlib.h> void PrintSumNumbers(int Arra[],int ASize,int Sum) //O(1) if (ASize<2) printf("The size of the Arra is invalid.\n"); return; if(Sum>=0) { if(Arra[ASize-1]>=Sum || Arra[0]<=0) { printf("The minimum value is bigger than the sum or the maximum value is equal or lesser than 0. \n"); return; if (Sum<0) if (Arra[0]<=Sum || Arra[ASize-1]>=0) { printf("The maximum value is smaller than the sum or the minimum value is equal or greater than 0. \n"); return; int LeftIndex=0,RightIndex=ASize-1; int MinTempValue = Arra[ASize-1]; int MaxTempValue = Arra[0]; if (Sum>=0&&MinTempValue<0) if (MaxTempValue>=0) MinTempValue = Sum-MaxTempValue; for (int i = ASize-1; i != 0; i--) if (Arra[i]>=MinTempValue) RightIndex=i; break; else { MaxTempValue = Sum-Arra[ASize-1]; for(int i=0;i<RightIndex;i++) { if(Arra[i]<=MaxTempValue) { LeftIndex=i; break; while( LeftIndex<RightIndex) int TempValue = Arra[LeftIndex]+Arra[RightIndex]; if(TempValue==Sum) { printf("(%d) + (%d) = (%d)\n",Arra[LeftIndex],Arra[RightIndex],Sum ); LeftIndex++; RightIndex--; else if (TempValue>Sum) LeftIndex++; else { RightIndex--; int main() int Arra[] = {2147483647,2147483646,9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-2147483646,-2147483647,-2147483647-1}; PrintSumNumbers(Arra,sizeof(Arra)/sizeof(Arra[0]),2147483647);
一种大数据外部排序(内存无法加载所有排序元素)、去除重复元素、快速找到随机被删除元素的BitMap小算法,核心思想即通过将一个数作为下标(index)来索引一个bit表示一个数是否存在,排序时的时间复杂度为O(N),需要的额外空间的复杂度O(N/8),支持整个int范围(正负数都支持)的算法示例如下: char BitMask[] = {0x80 , 0x40 , 0x20 , 0x10 , 0x8 , 0x4 , 0x2 , 0x1}; int WriteNumberBitToByte(char *ByteArra , unsigned int ByteArraSize , int Number) //printf("%d,%d,%d\n",(ByteArraSize * 4) - 1,-(ByteArraSize*4),Number); if (((int)(ByteArraSize * 4) - 1) < Number || Number<-(int)(ByteArraSize*4) ) return 0; //failed,number out of bytearra. int BaseArraBitPos = ByteArraSize *4; //ByteArraSize *8 /2 BaseArraBitPos+=Number; printf("BaseArraBitPos=%d,Number=%d\n",BaseArraBitPos,Number); ByteArra[BaseArraBitPos/8] |= Mask[BaseArraBitPos%8]; return 1; //success int IsNumberBitInByte(char *ByteArra , unsigned int ByteArraSize , int Number) if (((int)(ByteArraSize * 4) - 1) < Number || Number<-(int)(ByteArraSize*4) ) return 0; //failed,number out of bytearra. int BaseArraBitPos = ByteArraSize *4; //ByteArraSize *8 /2 BaseArraBitPos+=Number; if (ByteArra[BaseArraBitPos/8] & BitMask[BaseArraBitPos%8]) { return 1; return 0; //number not found. void PrintOrderedBitMap(char *BitMap,unsigned int BitMapCount) int MinmumNumber = -(BitMapCount*8/2); int MaximumValue = (BitMapCount*8/2)-1; for (int i = MinmumNumber; i <= MaximumValue; ++i) if (IsNumberBitInByte(BitMap,BitMapCount,i)) printf("%d,", i); printf("\n"); int main() int Arra[] = {3,-4,2,0,-1,-8,7,-12,10}; int MaximumValue =Arra[0],MinmumValue=Arra[0]; for (int i = 0; i < sizeof(Arra)/sizeof(Arra[0]); ++i) if(MaximumValue<Arra[i]) { MaximumValue = Arra[i]; if (MinmumValue>Arra[i]) MinmumValue = Arra[i]; MaximumValue=MaximumValue<0?-MaximumValue:MaximumValue; MinmumValue=MinmumValue<0?-MinmumValue:MinmumValue; MaximumValue=MaximumValue>MinmumValue?MaximumValue:MinmumValue; printf("MaximumValue=%d\n",MaximumValue); //unsigned int BitMapCount = (MaximumValue*2+7)/8; unsigned int BitMapCount = (MaximumValue+3)/4; BitMapCount = BitMapCount>0?BitMapCount:1; char *BitMap = (char*)malloc(BitMapCount); for (int i = 0; i < sizeof(Arra)/sizeof(Arra[0]); ++i) WriteNumberBitToByte(BitMap,BitMapCount,Arra[i]); PrintOrderedBitMap(BitMap,BitMapCount); 仅支持unsigned int范围的算法示例如下: char BitMask[] = {0x80 , 0x40 , 0x20 , 0x10 , 0x8 , 0x4 , 0x2 , 0x1}; int WriteNumberBitToByte(char *ByteArra , unsigned int ByteArraSize , unsigned int Number) if (((ByteArraSize * 8) - 1) < Number ) return 0; //failed,number out of bytearra. int BytePos = Number / 8; int BitPos = Number % 8; ByteArra[BytePos] |= BitMask[BitPos]; return 1; //success int IsNumberBitInByte(char *ByteArra , unsigned int ByteArraSize , unsigned int Number) if ((ByteArraSize * 8 - 1) < Number ) return 0; //failed,number out of bytearra. int BytePos = Number / 8; int BitPos = Number % 8; if (ByteArra[BytePos] & BitMask[BitPos]) { return 1; return 0; //number not found. 上面的算法都是用一个bit来表示一个数,即只有2种可能,要么有,要么无,可以扩展到一个字节表示一个数,这样就可以统计出现255次范围内的重复元素,原理以此类推。 另外用bit来表示一个int数,节约了31倍的内存空间,即int(4*8),bit(8/1),所以数据量越来使用这种方式的优势越明显,前提是场景适用这种方式。
一、出问题的场景 1、Sensorservice线程正在处理compass sensor事件的过程中,检查了一次buffer的指针的有效性,并在稍后会传递到AKM获取数据的函数接口中使用 2、Sensorservice线程所在进程的负责跨进程通信的Binder线程在sensorservice线程检查buffer指针之后没有真正使用之前, 收到了disable compass sensor的请求,从log中可以看到compass sensor先是被disable,disable的同时会free上面sensorservice检查过并正在使用的buffer指针,同时置空buffer指针 3、紧接着sensorservice线程继续执行AKM获取数据的函数接口,并使用到了已经被disable置空的buffer指针,然后产生异常 二、具体log和代码 Disable compass sensor并free buffer的代码: Free buffer 3ms之后sensorservice马上因为访问空指针异常的log: 传递buffer指针并使用的代码: 使用buffer指针产生异常的地方log:
一、初步分析结论 sensorservice多线程机制存在问题,导致在disable accel sensor并释放相应内存和数据之后, 有很小的概率发生继续读取到未处理完的sensor事件,从而继续使用相应的内存和数据, 并且没有做相应的防御保护措施,最终引起指针地址操作错误。 二、解决方案 1、首先在可能发生错误的地方做好防御保护措施 2、对多线程进行同步,对于临界变量的操作都放置到临界区中,使用锁来保护。 三、具体分析过程 log中显示打出accel sensor被disable的信息,然后接着1ms sensorservice就crash。 Disable accel sensor会先注销listener并释放相应内存,然后再调用具体的sensor的hal去disable,具体代码如下: 注销listener时会释放内存和数据: 真正发生地址操作失败的代码并没有进行相应的判空保护,如果781行得到的是0,那么783的item也是0,它的成员ctx相对偏移是8,在对8进行寻址和成员操作时就出现了内存错误,因为8不是一个有效的数据对象地址,具体如下:
一、问题现象 先遮盖P-Sensor,然后拨打电话,90%的情况下屏幕无法自动关闭背光显示。关闭Settings-》Display-》Brightness-》Auto,然后再执行以上操作则100%能够正常关闭背光显示。 Platform:MT6732 Android版本:4.4KK BuildType:user 系统软件版本:SWA1H+UM 系统RAM:1GB 参考机行为:参考机1正常,参考机2正常 二、MTK平台Android的Sensor流程框架 整个流程框架主要分为6个部分: 1,APPLICATIONS 这里主要是指Dialer/PhoneApp,他们是用户直接操作的接口,他们使用了P-Sensor来进行亮屏和灭屏的操作(通过向PowerManagerService请求PROXIMITY_SCREEN_OFF_WAKE_LOCK实现)。 2, FRAMEWORKS(JAVA) 主要包括两部分: 一、PowerManager/PowerManagerService,其作用是提供PROXIMITY_SCREEN_OFF_WAKE_LOCK,并与SensorManager/SystemSensorManager交互。 二、SensorManager/ SystemSensorManager,其作用是提供JAVA层控制sensor的接口,并透过JNI与Native的SensorManager通信。 3,FRAMEWORKS(JNI) 这里主要是是指SensorManager JNI,其主要作用是提供接口给SensorManager/SystemSensorManager与NatvieLibs的SensorManager通信。 4,FRAMEWORKS(LIBS/NativeService) 主要包括两部分: 一、SensorManager/SensorEventQueue,其属于NativeLibs,主要作用是为SensorManager JNI提供接口,请求SensorService创建SensorEventQueue(基于Binder的IPC),从而完成SensorEvent从SensorService到SensorManager JNI的传输。 二、Sensorservice/sensordevice,其属于NativeService,主要作用是为上层提供接口createSensorEventConnection,与Sensor的HAL层进行交互,控制sensor以及获取sensorEvent。 5,HAL 这里主要是指Nusensors和Hwmsen,其主要功能是为SensorService提供接口,与Sensor的Driver层进行交互,从而达到控制Sensor和获取SensorEvent的目的。 6,Driver 这里主要包括两大部分: 一、hwmsen_dev/StandardInputDevice,这部分是在具体的SensorDriver的上面又抽象出的一层,其主要功能是集中处理不同类型的sensor,包括lightsensor,proximitysensor等。 二、PROXIMITY/LIGHT(stk3x1x-new)等,这部分是指具体的SensorDriver,其主要功能是与具体的Sensor交互,包括控制、获取数据并上报等。这里有一点需要说明的是,Sensor分两种:一种是interrupt sensor,一种是polling sensor,他们的区别在于interrupt sensor是直接挂接在特定的中断上的,一旦sensor有数据就会触发中断处理(我们的PROXIMITYsensor就属于interrupt sensor),polling sensor是被动的提供数据,抽象出的hwmsen_dev会按照一定频率定时的去获取它的数据(我们的LIGHTsensor就属于polling sensor)。 三、问题分析 1、初步分析 根据问题在遮住p-sensor的情况下拨打电话无法灭屏的表面现象,我们首先考虑的是p-sensor硬件有没有产生数据并报给Driver,为了确认这一点我们首先在p-sensor的Driver中添加log: 然后执行操作,打出的log如下: 以上log中value 0x0,代表p-sensor的接近,0x1代表p-sensor的远离,所以说明硬件已经将p-sensor的数据上报到了SensorDriver中,SensorDriver又将数据上报到了driver抽象层hwmsen_dev/StandardInputDevice中,接下我们就要看driver抽象层是否将p-sensor的数据正确上报到了HAL。Nusensor.cpp中的关键代码如下: 以上部分代码是HAL中轮询所有sensor并获取event,其中就包括p-sensor,另外这里有一个很关键的data type,就是SENSOR_TYPE_META_DATA,下面会具体说明它的作用,同时说明一下这里的轮询的实现代码是通过读取StandardInputDevice的节点("/dev/input/event%d", num)来实现的。 在轮询完所有的sensor之后,如果还有空间没有用完就继续poll在sensor的data_fd上去继续获取event以填充完一次获取,然后返回给上层的sensorservice。使用同样的方法再这些代码流中添加log,以获取运行时的数据状态,发现p-sensor的数据同样正确的获取到了,这里就不再赘述。 接下来继续分析sensorservice中的数据流向,看是否正确上报到了SensorManager/SensorEventQueue中,sensorservice的关键代码如下: 首先,从代码中我们看到sensorservice继承于Thread,所以其本身是一个Thread并拥有一个闭合循环的threadloop,不断的从sensor的HAL中读取数据,通过添加log信息,发现在以后代码中p-sensor的数据依然被正确的获取到。 其次,读取完数据之后它会向当前所有连接到sensorservice的client发送sensorEvent,在以上代码中加上发送之前的log打印出p-sensor的数据依然存在。 然后,是具体的发送到client之前的处理代码,我们继续再其中添加log,发现在处理之前p-sensor的数据都是存在的,具体代码如下: 最后,是最终将处理完的数据发送到client的代码,我们在发送之前添加log,最终发现打印出的log中p-sensor的数据已经不存在了,具体代码如下: 2、继续分析 根据以上分析我们发现在被sendEvents处理之前,p-sensor的数据还存在,处理之后在发送之前p-sensor的数据已经被过滤掉,我们可以初步将问题的关键定位在处理过程中。 通过阅读和分析其处理过程的代码,发现了一个关键条件,就是如果sensor的数据想要被最终发送,必须满足mSensorInfo[index].mFirstFlushPending == false的条件,而这个条件满足的前提是当前这个SensorEventConnection必须先接收到SENSOR_TYPE_META_DATA,将mSensorInfo[index].mFirstFlushPending置为false,然后再接收到的具体sensor数据才能被最终发送出去。 根据初步分析的过程中添加的log,我们发现SENSOR_TYPE_META_DATA在sensor的数据流中是存在,但是却是在具体的sensor数据之后,从而导致具体的sensor数据被忽略(这里指是p-sensor的靠近数据)。 通过分析代码我们发现SENSOR_TYPE_META_DATA是sensor被enable之后发出的第一个first flush event,应当在具体的sensor数据之前,enable的关键代码如下: 首先batch,然后setFirstFlushPending为true并flush,在alto4.5TMO中这些操作就会为BatchSensor产生SENSOR_TYPE_META_DATA奠定基础。 最终会走到activate,将sensor真正enable,enable之后sensor会马上产生中断并上报数据,这里p-sensor的数据产生的地方是来自于Hwmsen(在nusensor的sensorlist中,Hwmsen是在第一位的,也就是说在做轮询数据的时候它会被第一个处理)。 这里需要说明的是Soul4NA和Yaris3.5AT&T为什么没有这个问题,原因就在于他们不支持batch,所以在执行batch之后会返回error,不会执行到setFirstFlushPending为true并flush的这个代码分支,而且mFirstFlushPending默认是false,具体代码如下: 从BatchSensor中获取SENSOR_TYPE_META_DATA的具体代码如下: 从log中我们发现在HAL返回数据给sensorservice时,SENSOR_TYPE_META_DATA已经被放在了具体的sensor数据之后,也就是说在sensorservice执行poll获取sensor数据的时候先获取到了具体的sensor数据,后获取到的SENSOR_TYPE_META_DATA。 3、深度分析 带着上面的问题,先获取到了sensor的数据,后获取到的SENSOR_TYPE_META_DATA,这是不正常的。对比关闭AUTO调节背光之后的正常log,我们发现一个关键现象,就是在打开AUTO调节背光的情况下,LIGHT sensor已经在工作,90%的情况下执行enable会阻塞sensorservice被系统调度,也就是执行的poll的thread不会被调度,直观的现象就是看到log中enable执行完才会调度sensorservice的poll的thread去获取事件,在这种情况下问题是必现的,另外10%的情况就是在enable执行的过程中batch刚刚执行完,满足产生SENSOR_TYPE_META_DATA的时候会先发生调度,这样sensorservice的poll的thread就先获取到了SENSOR_TYPE_META_DATA,这种情况下就没有此问题,屏幕可以正常熄灭。 根据这个关键现象我们又深入分析了enable的代码,然后再结合LIGHT sensor开和关的情况下,我们发现Driver中的执行流程存在一个条件差异: 一、100%可以正常熄灭屏幕的情况: 关闭AUTO调节背光,也就是关闭LIGHT sensor的情况下,在hwmsen_get_interrupt_data函数中obj->dc->polling_running的值为0,所以p-sensor上报数据时不会走hwmsen_work_func,具体代码如下: 在enable的IOCTL还没有执行完,并且没有其他活动的sensor时obj->dc->polling_running的值就会为0,enable的IOCTL执行完之后就会将obj->dc->polling_running的值置为1,同时开启polling的timer,按照一定的频率去polling sensor的数据 二、90%不能熄灭屏幕的情况以及10%可以熄灭屏幕的情况: 打开AUTO调节背光,也就是LIGHT sensor打开的情况下,在hwmsen_get_interrupt_data函数中obj->dc->polling_running的值为1,p-sensor上报数据时就会走hwmsen_work_func,具体代码如下: 在hwmsen_work_func中就会循环遍历所有sensor的driver,同时会区分interrupt sensor和polling sensor,然后获取它们的数据并上报。 1、90%不能熄灭的情况 由于p-sensor是interrupt sensor,所以这些调用都是在中断服务程序中处理的,由于正常情况下中断服务程序是不会被打断和抢占的,所以CPU会被其hold住直到中断服务器程序处理完毕并返回。如果在执行完enable中的batch和flush之后,系统没有发生调度并调度到sensorservice并先获取到SENSOR_TYPE_META_DATA,那么就会被activate的IOCTL触发p-sensor的数据中断,并将p-sensor的数据先上报,从而导致sensorservice被这个上报过程阻塞,最终影响到sensorservice获取数据的正常流程。 2、10%可以熄灭的情况 在执行完enable中的batch和flush之后,系统发生了调度并调度到sensorservice并先获取到SENSOR_TYPE_META_DATA,然后activate的IOCTL再触发p-sensor的数据中断,再将p-sensor的数据上报,这时就不会影响sensorservice获取数据的正常流程。 4、根本原因 在无法保证系统调度的情况下,我们发现问题的根本原因在于p-sensor的中断服务程序在LIGHT sensor打开时(obj->dc->polling_running的值为1)没有分上下段执行,而是将所有处理都放在了中断服务程序里面,从而影响了sensorservice的正常执行及数据流的正常顺序。 四、解决方案 通过以上分析,我们可以知道造成最终问题的原因是中断服务程序在LIGHT sensor打开时(obj->dc->polling_running的值为1)没有分上下段执行。 通过详细分析当前的代码逻辑、结构以及测试,我们给出以下改动和影响尽可能小的解决方案: 1、在Sensor的抽象Driver中(hwmsen_dev),将interrupt sensor(即p-sensor)的中断服务程序调用的函数:hwmsen_get_interrupt_data中的中断下半部处理的代码移除,只执行中断的上半部分处理,将关键数据和状态保存,即将以下代码移除: 中断的下半部分的操作放在固定频率的timer中执行,因为代码原来的逻辑就是在enable sensor的时候开启timer,去完成interrupt sensor的work,具体代码如下: 五、结论及后续动作 Sensor的抽象Driver中(hwmsen_dev)的中断服务程序不分上下段执行的问题在Yaris3.5AT&T以及SOUL4NA中都是存在的,但是由于没有BatchSensor的SENSOR_TYPE_META_DATA的影响,所有并没有出现这个问题。因此在以后的MTK的平台上我们都要检查是否存在此问题,以提前避免,Qualcomm平台也会同时关注。 #analyzed by vincent.song from SWD2 Framework team. #vincent.song@tcl.com #201501071718
一、问题现象 按下Power key点亮屏幕时,很大概率出现先亮button light,然后再亮屏的现象,明显感觉到卡顿。 线索1、快速按下Power key熄灭和唤醒基本不会出现以上问题现象 线索2、长按Power key会同时亮LCD和button light 线索3、插入2G sdcard基本没有出现过以上问题,换成32G的class 10的sdcard之后就很大概率出现以上问题 Platform:MT6732 Android版本:4.4.4KK BuildType:user 系统软件版本:SWA27+UM 系统RAM:1GB 参考机行为: 1、参考机1的机制是短按也同时亮button light和LCD,无法参考 2、参考机2与当前项目的机制相同,虽然也伴有卡顿,但是没有达到当前项目的程度,没有出现以上现象 二、Power key休眠和唤醒系统时的处理流程 这里仅说明按下Power key之后,Linux kernel态的休眠和唤醒流程。 1、休眠过程的主要步骤: 1) 冻结用户态进程和内核态任务 2) 调用注册的设备的suspend的回调函数, 顺序是按照注册顺序 3) 休眠核心设备和使CPU进入休眠态 冻结进程是内核把进程列表中所有的进程的状态都设置为停止,并且保存下所有进程的上下文. 当这些进程被解冻的时候,他们是不知道自己被冻结过的,只是简单的继续执行。 2、唤醒过程的主要步骤: 1) 休眠中的系统被Power key的按键中断唤醒,同时将按键事件放入输入设备的文件缓冲区中 2) 唤醒的顺序是和休眠的循序相反的,所以系统设备和总线会首先唤醒,使能系统中断,使能休眠时候停止掉的非启动CPU 3)继续唤醒每个设备,使能虚拟终端 4)继续来解冻进程和任务,唤醒终端 三、问题初步分析 初步分析结果: 通过分析log以及对应代码逻辑,发现Power key在分发时的状态出现了问题,最终导致处理流程异常。 下一步行动: 因为log中没有足够的信息来确认代码的执行流程,所以需要添加log再进一步确认问题并输出解决方案。 问题原理: 正常情况下: 1、Power key只有长按超过500ms才会通知上层点亮button light,其他按下power key的情况都不会点亮button light。 2、Power key短按经过预处理之后不会传递给上层,同样也不会点亮button light。 当前出问题的异常情况: 1、power key 在正常短按的时候通知上层点亮了button light,同时产生了异常的长按事件 四、问题进一步分析 进一步定义问题现象: 在插入sdcard的情况下,很大概率出现按下power key会点亮button light,然后再亮LCD,给用户的直接感觉就是卡顿一下。 进一步的分析结果: 插入sdcard出现问题的情况下: 从power key按下到用户空间接收到,花费了超过500ms的时间,导致inputdispatcher认为产生了长按事件,从而走了点亮button light的流程。 不插入sdcard的正常情况下: 从power key按下到用户空间接收到,花费200ms左右的时间,inputdispatcher认为是正常短按,不会走点亮button light的流程。 已知的问题原因: 从有问题的kernel log中看到power key 的中断产生,到唤醒用户空间进程,中间已经花费超过500ms, 通过进一步分析发现,这超过500ms的时间都花在了resume系统的过程中,包括resume各种器件的driver以及从cpu, 其中就包括sdcard,与此同时,从log中还可以看出,在resume sdcard的时候,其log的时间间隔都比较长,处理过程花费了比较久的时间,加起来大概380ms。 对比没有问题的kernel log,没有与resume sdcard相关的log信息打出,也没有花费比较久时间的处理log打出。 未知的问题原因: 为何按下power key resume的过程会超过500ms?特别是处理resume sdcard的时候,时间耗费比较久? 五、问题结论及后续动作 通过查看代码和与MTK的工程师沟通,resume high speed的sdcard的时间比较长是正常的,但是由于此问题特别影响用户体验,所以需要进行横向对比。 查看qualcomm平台的sdcard resume时间和机制,截止到目前还在添加log验证中,如果qualcomm平台的时间短或者机制比较好,就要敦促MTK去从根本上优化。 另外button light和LCD light之间为何会有时间差?给人感觉卡顿一下的原理: 在系统suspend的状态下,Power key上报到上层系统之后,上层pms经过电源管理状态的处理以及一些状态判断之后会先将wakeup的请求再发送给其内部的一个专门的控制者,其中就包括点亮lcd的操作,但是这个过程是异步的。接下来pms直接根据是否长按power key来决定点亮button与否,这个处理是同步的。 然后这个控制者负责与kernel底层交互,将wakeup的请求发送给kernel底层,等kernel处理完成之后再点亮LCD, 因此如果系统处于suspend状态,这个时候发送wakeup请求,与kernel底层的交互过程就会稍长,大概250ms左右,这个时间就是你先看到button light亮之后,lcd再亮的中间间隔时间。 六、附上关键代码及Log分析 1、休眠和唤醒的关键代码: 以上代码是:static int suspend_enter(suspend_state_t state, bool *wakeup) 以上代码是:int suspend_devices_and_enter(suspend_state_t state) 以上代码是:static int suspend_enter(suspend_state_t state, bool *wakeup) 2、初步分析的关键log: 按下power key时: 03-03 10:35:07.119074 795 845 D WindowManager: interceptKeyTq: result=2 keycode=26 screenIsOn=false keyguardActive=true policyFlags = #2000001 down =true canceled = false isWakeKey=true mVolumeDownKeyTriggered =false mVolumeUpKeyTriggered =false 通知上层时: 03-03 10:35:07.121838 795 844 D PowerManagerService: userActivityFromNative 03-03 10:35:07.121887 795 844 D PowerManagerService: userActivityNoUpdateLocked: eventTime=45028174, event=1, flags=0x0, uid=1000 点亮button light时: 03-03 10:35:07.122567 795 844 I PowerManagerService: setBrightness mButtonLight, screenBrightness=102 抬起power key时: 03-03 10:35:07.122476 795 845 D WindowManager: interceptKeyTq: result=1 keycode=26 screenIsOn=true keyguardActive=true policyFlags = #2000001 down =false canceled = false isWakeKey=true mVolumeDownKeyTriggered =false mVolumeUpKeyTriggered =false 异常的长按事件(正常的是在按下不抬起超过500ms才会产生): 03-03 10:35:07.123281 795 844 D WindowManager: interceptKeyTi keyCode=26 down=true repeatCount=1 keyguardOn=true mHomePressed=false canceled=false metaState:0 3、进一步分析的关键log: 用户空间获取到power key的过程以及时间戳: InputReader(eventhub):{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{ 03-05 14:55:49.518601 766 861 D InputReader: vincent loopOnce now=450968789025 03-05 14:55:52.140518 766 861 E EventHub: /dev/input/event0 got: time=451.959992(power key产生的时间), type=1, code=116, value=1 03-05 14:55:52.140590 766 861 E EventHub: /dev/input/event0 got: time=452.184186, type=1, code=116, value=0 452.184186-451.959992=0.224194 03-05 14:55:52.140840 766 861 D InputReader: vincent after getEvents now=452609428487(获取到power key的时间) 03-05 14:55:52.141047 766 861 D InputReader: vincent notifyKey - eventTime=451959992000, deviceId=7, source=0x101, policyFlags=0x1, action=0x0, flags=0x8, keyCode=0x1a, scanCode=0x74, metaState=0x0, downTime=451959992000 03-05 14:55:52.162639 766 860 D InputDispatcher: vincent dispatchOnceInnerLocked,currentTime=452631225948,nextRepeatTime=452459992000(应该产生repeat的时间,已经小于获取到power key的时间) }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} Kernel 空间处理power key的过程: kernel process power key:{{{{{{{{{{{{{{{{{{{{{{{{{{ <4>[ 451.708947] (2)[70:kworker/u8:1]Freezing user space processes ... <6>[ 451.712100] (2)[70:kworker/u8:1]last active wakeup source: pmicAuxadc irq wakelock <4>[ 451.712114] (2)[70:kworker/u8:1] <3>[ 451.712129] (2)[70:kworker/u8:1]Freezing of tasks aborted after 0.003 seconds (59 tasks refusing to freeze, wq_busy=0): <4>[ 451.712139] (2)[70:kworker/u8:1] <4>[ 451.712148] (2)[70:kworker/u8:1]Restarting tasks ... done. <5>[ 451.985808]-0)[Power/PMIC] [mt_pmic_eint_irq_mt6325] receiveinterrupt(power key的按键中断产生的时间) <5>[ 451.985974] 0)[Power/PMIC] [pwrkey_int_handler] Press pwrkey <4>[ 451.985979] 0)kpd: Power Key generate, pressed=1 <4>[ 451.986271] 0)kpd: (pressed) HW keycode =116 using PMIC <6>[ 451.988072] 0)Enabling non-boot CPUs ... <2>[ 451.988413] 0)Boot slave CPU(启动从cpu) <4>[ 451.988622]-1)CPU1: Booted secondary processor <4>[ 451.988628]-1)[Power/hotplug] platform_secondary_init, cpu: 1 <4>[ 451.990119] 0)[wdk]bind kicker thread[132] to cpu[1] Resume sdcard:{{{{{{{{{{{{{{{{{{{ <7>[ 452.012297] 0)[2] bus device_resume <7>[ 452.012302] 0)dev->driver->name=mtk-msdc <6>[ 452.012321]-0)[Power/clkmgr] [clkmux_enable_op]: mux->name=MUX_MSDC30_1 <3>[ 452.012376] 0)msdc1 -> PM Resume(开始resume sdcard) <5>[ 452.096177] 0)[Power/cpufreq] _calc_new_opp_idx(): for early suspend, idx = 3 <3>[ 452.306899] 0)msdc1 -> !!! Set<300KHz> Source<200000KHz> -> sclk<299KHz> state<0> mode<0> div<167> hs400_src<0> 452.306899-452.096177≈210ms <6>[ 452.308569] 0)[Power/PMIC] [pmic_ldo_enable] Receive powerId 11, action is 1 <4>[ 452.318583] 0)[msdc_set_mclk] hz = 260000 <5>[ 452.326109] 0)[Power/cpufreq] _calc_new_opp_idx(): for early suspend, idx = 3 <5>[ 452.346192] 0)[Power/cpufreq] _calc_new_opp_idx(): for early suspend, idx = 3 <5>[ 452.406189] 0)[Power/cpufreq] _calc_new_opp_idx(): for early suspend, idx = 3 <5>[ 452.426088] 0)[Power/cpufreq] _calc_new_opp_idx(): for early suspend, idx = 3 <3>[ 452.474040] 0)msdc1 -> !!! Set<260KHz> Source<200000KHz> -> sclk<259KHz> state<0> mode<0> div<193> hs400_src<0>[msdc_set_mclk] hz = 299401 <5>[ 452.476821] 0)[Power/cpufreq] _calc_new_opp_idx(): for early suspend, idx = 3 <3>[ 452.479275] 0)msdc1 -> !!! Set<299KHz> Source<200000KHz> -> sclk<297KHz> state<0> mode<0> div<168> hs400_src<0>[msdc_set_mclk] hz = 300000 <3>[ 452.481533] 0)msdc1 -> !!! Set<300KHz> Source<200000KHz> -> sclk<299KHz> state<0> mode<0> div<167> hs400_src<0> <4>[ 452.483992] 0)msdc1 select card<0x59b40000> <4>[ 452.483994] 0)msdc status[16] & 0xF = 0x4 bus_speed = 0x4 <4>[ 452.484003] 0)[msdc_set_mclk] hz = 300000 <3>[ 452.484146] 0)msdc1 -> !!! Set<300KHz> Source<200000KHz> -> sclk<299KHz> state<1> mode<2> div<83> hs400_src<0>[msdc_set_mclk] hz = 50000000 <3>[ 452.484223] 0)msdc1 -> !!! Set<50000KHz> Source<200000KHz> -> sclk<50000KHz> state<1> mode<2> div<0> hs400_src<0> 452.484223-452.318583≈170ms 170ms+210ms=380ms,有关resume sdcard 的处理大概花费380ms }}}}}}}}}}}}}}}}}}}}}}}} <7>[ 452.484942] 0)dev->driver->name=mtk-kpd <4>[ 452.484953] 0)kpd: kpd_early_suspend wake up source resume!! (0) <4>[ 452.484958] 0)kpd: enable kpd work! <4>[ 452.484963] 0)kpd: KEYPAD is enabled <4>[ 452.484967] 0)kpd: resume!! (0) <4>[ 452.632297] (0)[70:kworker/u8:1]Restarting tasks ... done.->>>>>2015-03-05 06:55:52.137685 UTC (开始唤醒用户空间的进程,包括inputreader) 452.632297-451.985808≈650ms 整个系统resume的过程花费了大概650ms <6>[ 452.641290] (0)[70:kworker/u8:1]PM: suspend exit 2015-03-05 06:55:52.146678077 UTC }}}}}}}}}}}}}}}}}}}}}}} #analyzed by vincent.song from SWD2 Framework team. #vincent.song@tcl.com #201503171631
一、问题现象 1、多次进出需要强制横屏的app,比如Real FootBall2015,在退出app的时候会有概率出现退出卡顿,然后TP无法输入的问题。 2、出问题时Power key有响应。 3、此问题同时在Driver only上有复现。 Platform:MSM8916 Android版本:5.0.2L BuildType:user 系统软件版本:vA6P+L5P0 系统RAM:1GB 参考机行为: 1、ALTO4.5TMO在同样的简单测试下没有重现此问题,后经分析代码发现是4.4.4KK与5.0.2L在机制上的区别,下面会有具体的对比分析过程。 2、Nexus4和Nexus5在同样的简单测试下没有重现此问题,因没有源码所以无法Debug和打印log,后续会尝试获取nexus的源码以了解它的修改方案。 二、解决方案 通过初步分析、深入分析、对比分析(具体分析过程、关键代码和log在下面会附上)我们清楚的知道了问题发生的原因、流程以及正常情况下的流程,在这个问题中有很多条件都起到了关键作用,最终促成了发生问题的死循环。如果是紧急的备用方案我们可以在任何关键条件的环节进行条件判断处理将出问题的这种情况加以避免,但是如果是长期的最终解决方案我们就要从问题的源头进行解决。 明确一下问题的根本原因: 1、动画的执行依赖VSYNC信号,但是VSYNC信号的到来具有规律性,不到16.66ms不能强行发生 2、WIN DEATH和APP DEATH具有不规律性,在任何时间点都有可能发生 3、退出Window需要横屏切换到竖屏,需要冻结屏幕和输入 4、解冻屏幕和恢复输入需要所有Window都Rotate ready 5、出现问题时退出的窗口需要在屏幕和输入没有冻结之前执行退场动画的状态处理,然后才能正常finishExit满足Rotate ready条件 一旦上面的退出窗口没有在冻结屏幕和输入之前执行一次退场动画的状态处理就会成为触发问题死循环的导火索。 针对以上问题的根本原因,我们给出以下解决方案: 1、掐断问题的导火索 在不对冻结和动画进行大机制上的同步改动的前提下,对已经冻结VSYNC才来执行退出窗口的退场动画的情况下进行状态处理,由于已经冻结,所以退场动画不需要再执行,需要做的是清除这个待执行的动画,确保执行finishExit的正常清理操作,从而满足Rotate ready条件,让系统的正常机制来进行恢复处理。AOSP的这块代码已经考虑到了VSYNC先到来但是还有窗口动画的一种情况,但是没有考虑到退场动画的情况,所以在不做大影响的改动下我们在AOSP原来的机制上新增一种退场动画的条件判断,达到解决问题的目的。 2、原生代码和注释 通过以上代码可以清楚的看到如果屏幕已经冻结okToDisplay就会为false,因此if里面的代码主体就不会被执行到,因此动画的状态就不会被更新,所以下面就会直接返回。另外我们还可以看到还有一个否则条件以及它的注释,清楚的说明了如果屏幕已经冻结但是还有一个窗口动画(不是退场动画),那么就清除这个动画,确保执行下面的清理代码,但是很遗憾这个条件没有把当前这个问题的退场动画包含,所以就无法执行正常的finishExit的清理代码,finishExit中的工作也至关重要,具体代码如下: 通过以上代码我们可以看到finishExit中主要做了几个关键动作,首先finishExit当前窗口的所有子窗口,然后将窗口加入待销毁surface的列表中,同时将mExiting置为false,这个false非常重要,直接影响我们之前分析的apptoken的mDeferRemoval是否能被正常删除,同时将窗口加入待删除的列表中,这个是真正删除的列表。 3、最终修改的代码方案 我们需要修改一行代码在原来的机制流程上比较完美的解决这个问题,具体代码如下: 如果屏幕和输入已经冻结,但是窗口的退场动画还没有在执行,那么将正常执行动画的状态置为true,确保正常执行下面的finishExit,最终恢复正常流程,解决问题。 三、问题初步分析 以Idol347出问题时候的一份典型log为例,发现出问题时log中打出大量如下信息: 通过查看代码,发现上面的log是在InputDispatcher的dispatchOnceInnerLocked中打出来的,具体如下: 通过以上代码我们可以发现,是mDispatchFrozen条件成立才打印出的这句log。 mDispatchFrozen条件是什么时候被置成true的?代码中找答案: 通过上面的代码我们可以发现是在setInputDispatchMode函数中设置的mDispatchFrozen,接下来继续看哪里调用的setInputDispatchMode,通过查看代码发现是从WindowManagerService一路调下来的,中间经过了JNI,具体如下: 什么场景下会让WindowManagerService冻结的输入?通过查看代码和添加log,我们可以查看出问题时具体的调用栈信息: 通过具体调用栈我们可以发现是ActivityManagerService在处理app死亡通知时,会resume下一个app,在resume的过程中会去调用WindowManagerService的方法检查是否需要转屏,如果需要转屏则调用startFreezingDisplayLocked冻结显示,在冻结显示的过程中会冻结输入: 知道了冻结输入的场景,接下来还有一个更重要的问题,什么场景下会让WindowManagerService恢复正常输入?有冻结就有解冻,继续查看代码和调用栈信息: 从调用栈信息中我们可以发现在处理app died通知并resume下一个app的过程中会调用解冻显示,解冻显示的过程中会解冻输入,但是从log中可以看到,出问题时解冻显示函数在还没有走到解冻输入的时候就因为mWindowsFreezingScreen条件为true而返回了,因此输入没有恢复正常。初步分析到这里已经定位到第一个问题点:mWindowsFreezingScreen为true导致不能正常解冻而恢复输入。但是mWindowsFreezingScreen条件为什么为true?难道只有这一个地方会去解冻恢复吗?带着这两个问题我们继续分析。 四、深入分析问题 经过初步我们定位到了第一个问题点,同时也产生了两个问题,接下来我们继续深入分析以期能到找到答案和问题的根本原因。 1、mWindowsFreezingScreen条件为什么为true? 2、还有什么地方会解冻恢复正常输入? 通过查看代码发现mWindowsFreezingScreen有两个地方置为true,一个地方置为false: 通过以上代码我们可以知道在执行updateRotationUncheckedLocked的时候如果需要转屏则会会将mWindowsFreezingScreen置为true一次,然后每次调用makeWindowFreezingScreenIfNeededLocked的时候如果屏幕已经frozen,也会将mWindowsFreezingScreen置为true。而将mWindowsFreezingScreen置为false的地方只有一个,置为false的同时也会解冻输入。这也间接回答了我们的第二个问题,除了初步分析中的第一个解冻输入的地方,还有一个解冻输入的地方,那就是performLayoutAndPlaceSurfacesLockedInner: 这个函数是WindowManagerService非常重要的一个函数,根据名字我们可以知晓其功能的一二,他里面主要执行布局、计算、窗口的移除以及动画的调度等各种状态管理,是调用频率非常高的一个函数,只要窗口状态有任何的变化都会执行到这里。到这如果mWindowsFreezingScreen想要被置为false,还需要满足一个条件,那就是mInnerFields.mOrientationChangeComplete必须为true,我们继续追踪mInnerFields.mOrientationChangeComplete何时被置为true,发现只有一个地方mInnerFields.mOrientationChangeComplete会被置为true,具体代码如下: 分析到这可以看到与Animation开始产生关系了,继续追踪调用关系,发现copyAnimToLayoutParamsLocked是在WindowAnimator的animateLocked中调用的,而animateLocked是由VSYNC信号来了之后由Choreographer的FrameDisplayEventReceiver调用的,具体调用栈如下: 在调用copyAnimToLayoutParamsLocked之前,animateLocked会先调用updateWindowsLocked去更新所有应用的动画,包括正在退出和已经删除的应用,然后还会调用WindowStateAnimator的prepareSurfaceLocked去做相应的状态计算,具体代码如下: 在执行updateWindowsLocked时会调用WindowStateAnimator的stepAnimationLocked,这个函数在当前这个问题中有非常关键的作用,下面会着重介绍。 由于ActivityManagerService在处理app died的时候并没有与WindowManagerService处理Window died和执行动画进行同步,因此就有可能出现Window died的退场动画还没有来得及等到下一个VSYNC(16.666ms一次)执行一次动画操作,就被ActivityManagerService在resume下一个需要转屏的应用时冻结屏幕和输入,在下一个VSYNC来了之后去执行window died的退场动画时发现屏幕已经冻结,从而不能正常finishExit的window而直接返回,成为这个问题的一个最关键的点。 Window died的关键处理代码如下: performLayoutAndPlaceSurfacesLocked最终会调用到performLayoutAndPlaceSurfacesLockedInner,然后就会执行窗口大小的计算和相关状态更新,其中影响此问题非常关键的操作是调用updateResizingWindows: 由于window dead,所以window和可视内容以及大小发生了变化,因此会调用makeWindowFreezingScreenIfNeededLocked,这个函数中会判断屏幕是否已经冻结,如果已经冻结则会将mInnerFields.mOrientationChangeComplete一直置为false,虽然WindowAnimator会调用copyAnimToLayoutParamsLocked将mInnerFields.mOrientationChangeComplete置为true,但是因为执行copyAnimToLayoutParamsLocked之后仍然需要调用requestTraversalLocked去执行performLayoutAndPlaceSurfacesLocked,所以会被makeWindowFreezingScreenIfNeededLocked再次置为false,其实performLayoutAndPlaceSurfacesLocked的执行过程中会对Window的内容和大小变化进行更新,正常情况下执行makeWindowFreezingScreenIfNeededLocked的条件不会一直满足,具体代码如下: 但是当前这种情况比较特殊,因为Window已经结束,所以调用mClient.resized会发生RemoteException,导致上面代码中的状态不能被置为false,从而导致调用makeWindowFreezingScreenIfNeededLocked的条件一直满足,最终使WindowManagerService不能解冻屏幕和恢复输入,一旦屏幕先冻结,这里会与WindowStateAnimator的stepAnimationLocked的处理一起形成一个不能解冻和恢复正常输入的死循环。 死循环在哪里? 1、Window退出调用WindowManagerService的removeWindowLocked 2、removeWindowLocked会执行退场动画,并调用performLayoutAndPlaceSurfacesLocked进行一次计算和状态处理,并将动画进行调度处理,放入下一个VSYNC的处理列表中,因为动画还没有被执行处理所以mInnerFields.mOrientationChangeComplete不为true,因此mWindowsFreezingScreen也不会被置为false,屏幕和输入不会被解冻和恢复。 3、ActivityManagerService接收到app died的通知之后resume下一个app,下一个app与当前结束的这个app的orientation不一样,触发冻结屏幕和输入。 4、VSYNC到来,执行动画的相关操作,因为屏幕已经被冻结,所以正在退出的Window不能执行动画操作而直接返回,导致finishExit不能被执行,最终Window不会被正常删除。执行copyAnimToLayoutParamsLocked将mInnerFields.mOrientationChangeComplete置为true,然后调用requestTraversalLocked发送执行下一次performLayoutAndPlaceSurfacesLocked的消息到消息队列中。 5、performLayoutAndPlaceSurfacesLocked执行,调用updateResizingWindows。因为退出的Window没有被finishExit,并且执行reportResized更新窗口大小和内容状态的过程中由于Window已经退出,所以调用mClient.resized执行IPC(跨进程调用)时发生RemoteException,导致关键状态值没有被置位清空,所以执行updateResizingWindows的过程中会因为Window的状态一直满足条件而调用makeWindowFreezingScreenIfNeededLocked,因为此时窗口已经被冻结,所以会将mInnerFields.mOrientationChangeComplete一直置为false,因此不会将mWindowsFreezingScreen置为false和调用stopFreezingDisplayLocked解冻屏幕和恢复输入。接着会调用scheduleAnimationLocked将下一次动画调度到VSYNC的列表中。 6、下一次VSYNC到来,重复第四步和第五步构成不能解冻屏幕和恢复输入的死循环 五、KK4.4.4与L5.0.2的机制区别 1、L5.0.2新增条件mDeferRemoval 接收到app的dead通知之后,ActivityManagerService会调用WindowManagerService的removeAppToken,具体代码和调用关系如下: 在removeAppToken的过程中,KK4.4.4与L5.0.2有一些区别,L5.0.2新增加了一个条件mDeferRemoval,为了这个处理这个条件L5.0.2新增加一些代码来一起完成这个机制特性,关键具体代码如下: mDeferRemoval这个条件会影响mExitingAppTokens中apptoken的删除和与apptoken关联的Window的删除,而且这个条件与正在执行动画或者正在退出也强相关,通过上面的分析和代码我们知道发生问题时WindowManagerService存在一个死循环,因此在执行performLayoutAndPlaceSurfacesLocked的过程中调用checkForDeferredActions时,stack.isAnimating()条件会一直满足,因为有正在退出的窗口还没有finishExit,因此不会做mExitingAppTokens的删除,所以apptoken会一直存在,与这个apptoken关联的Window也会一直存在。 KK4.4.4没有mDeferRemoval这个条件,所以会在performLayoutAndPlaceSurfacesLocked的过程中直接删除apptoken。 另外KK4.4.4在同样先冻结屏幕再来VSYNC执行动画的情况下Window同样不会被finishExit而保留在WindowList中,但是由于KK4.4.4没有mDeferRemoval的机制,所以在rebuildAppWindowListLocked的时候会将不能正常被finishExit但是它的apptoken已经从task和exitingAppTokens中删除的窗口删除,同时MTK还加了一个patch用来彻底remove Window防止窗口泄露,具体代码如下: 所以虽然在退出强制横屏的窗口进入launcher不能正常finishExit的时候,在不进入其他app的情况下可以通过adb shell dumpsys |grep “game”看到这个窗口还在,但是当你进入其他app窗口的时候会调用handleAppTransitionReadyLocked,然后再调用rebuildAppWindowListLocked将其删除,这里你在执行上面的命令就看不到不能finishExit的窗口了,原因就是上面分析的机制和代码所致。 2、L5.0.2使用带有虚拟按键的NavigationBar 因为L5.0.2使用带有虚拟按键的NavigationBar,所以在退出强制横屏app的窗口回到Launcher时,与KK4.4.4手机使用实体按键在config change时的布局条件不同。L5.0.2在布局时因为从全屏显示到退出到launcher会发生rotate引起config change,所以会执行一次布局,同时需要更新显示NavigationBar区域,所以在执行computeFrameLw过程中mContentFrame会发生变化,进而引起mContentInsets的变化,最终win.setInsetsChanged()条件满足,由于前面说到的执行reportResized更新窗口大小和内容状态的过程中由于Window已经退出,所以调用mClient.resized执行IPC(跨进程调用)时发生RemoteException,导致关键状态值没有被置位清空,所以这里也是同样情况,布局条件会因此一直满足,performLayoutLockedInner中具体的关键代码如下: win.mLayoutSeq这个条件会影响到窗口freezing状况的保持以及mInnerFields.mOrientationChangeComplete状态的置位,具体的关键代码如下: 执行窗口rotate之后的布局参数log如下: 圈红的参数中cf是contentframe的矩形大小,它会与frame做计算,得出一个ci,因为从全屏横屏到launcher的变化,所以NavigationBar会被计算布局和刷新,因此ci与上次不同w.setInsetsChanged();满足为true,同时w.mContentInsetsChanged为true。 KK4.4.4因为没有使用NavigationBar所以上面的条件不会满足,通过实验我将KK4.4.4的NavigationBar打开,同样情况下上面的条件也会满足,但是KK4.4.4依然不会冻屏,因为我参考的KK4.4.4有MTK的patch,增加一个条件:mDisplayFrozenTimeout,当WINDOW_FREEZE_TIMEOUT之后,mDisplayFrozenTimeout会被置为true,所以makeWindowFreezingScreenIfNeededLocked函数中保持屏幕冻结状态的代码不会被走到,具体如下: 另外我参考的KK4.4.4还有一处修改也会直接影响不会发生此问题,在WindowAnimator中加了一处判断转屏动画结束并解冻恢复输入的代码,具体如下: 以上这些区别让我参考的MTK的KK4.4.4不会出现冻屏不能输入的问题。 六、后续动作和其他相关问题 1、尝试获取Nexus的源码以了解它的解决方案 2、在Window Freezing timeout的时候强制解冻和恢复输入的临时方案以及它会引起的问题: 引起的第一个问题:为什么每次退出强制横屏的应用到竖屏都会Window Freezing timeout? 分析:由于正在退出的窗口没有被finishExit,所以它会一直存在并影响mInnerFields.mOrientationChangeComplete条件一直为false,所以不会正常执行解冻恢复的代码以及app transition的代码,最终导致超时后强制解冻和恢复输入,关键代码如下: 引起的第二个问题:为什么在同一个应用执行转屏动作也会等到Window Freezing timeout才进行转屏? 与第一个问题是同样原理,由于有一个没有被finishExit的窗口,所以stopFreezingDisplayLocked不会被及时的正常执行,而stopFreezingDisplayLocked中会进行ScreenRotationAnimation的dismiss操作,dismiss会将ScreenRotationAnimation给start起来,然后进行ScreenRotationAnimation,关键的具体代码如下: Analyzed by vincent.song from SWD2 Framework team. vincent.song@tcl.com 201504231130
二、解决方案 通过初步分析、深入分析(具体分析过程、关键代码和log在下面会附上)我们清楚的知道了问题发生的原因: 1、开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭 2、在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出 3、这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回, 执行的方式是多线程异步处理 在当前代码的执行状态下有一定概率(很小,只有开机或者重启时走这个流程)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。 针对以上问题的根本原因,我们给出以下解决方案: 1、修正代码的处理顺序 Closecamera时先执行m_thermalAdapter.deinit等待thermal deamon线程将结果处理完成退出并返回,再free all pending api results,因为m_thermalAdapter.deinit会依赖pending api results,这同样是遵循初始化和反初始化的栈原则,即opencamera时最后初始化的是依赖别人最多的,但是不被别人依赖,因此closecamera时需要先反初始化在opencamera时最后初始化的,按照栈的方式原则处理。 2、方案相关的具体代码和log 三、问题初步分析 以Idol347出问题时候的一份典型trace和log为例,发现出问题时SystemUI的主线程block在了一个向CameraService发起的Binder调用中,从而导致SystemUI 的后续事件TimeOut引起ANR,主线程的具体trace如下: 然后继续追踪CameraService的服务端的trace,发现/system/bin/mediaserver中的处理CameraService的Binder线程也被block了,然后追踪trace中各个线程的调用栈和互斥锁的使用,发现处理调用CameraService的addlistener的Binderthread之所以被阻塞,是因为另外一个binder thread先占用了锁,然后在占用的过程中去注册thermal回调,但是注册的过程需要占用另外一个锁,但是这个锁被第三个thread在注销thermal回调的时候先占用,并且join另外一个thread,因此整个依赖环需要另外一个thread退出才能解,从当前的问题现象来看,这个thread不会太快退出,所以导致了ANR和黑屏问题。 通过初步分析我们发现的问题: 是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理? 具体的调用栈和代码中锁的关系如下: 四、深入分析问题 经过初步我们定位到了第一个问题点,同时也产生了1个问题,接下来我们继续深入分析以期能到找到答案和问题的根本原因。 1、是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理? 通过进一步分析和查看代码发现,Join的另外一个thread不能很快退出是因为它在执行callback时等待另外一个条件的满足,具体逻辑调用关系如下: 另外一个条件之所以不满足的原因: 开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭,在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出,但是这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回,由于是多线程的异步处理,在当前代码的执行状态下就有一定概率(很小)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。 一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。 五、其他相关问题 第一个问题:为什么大部分发生在FOTA升级之后? 分析:由于这个问题是发生在启动或者重启时,而且只有这个过程才会有很小概率触发。而FOTA之后会自动重启,所以就有概率触发这个问题,由于用户平时使用中很少重启,所以概率不高。 Analyzed by vincent.song from SWD2 Framework team. vincent.song@tcl.com 201505221140通过初步分析、深入分析(具体分析过程、关键代码和log在下面会附上)我们清楚的知道了问题发生的原因: 1、SystemServer进程中的AudioService相关的setindex和getindex等方法会在多个Thread中进行,而这些方法都是以synchronized关键字声明的,即这些方法会以类的每个实例中的lock进行多线程同步。 2、这些synchronized的方法中存在穿插调用其他同类型实例的synchronized方法的行为。 3、完全将方法用synchronized关键字声明是一个比较懒的做法,这样会导致同步锁的粒度太大,没有细化临界区,在多线程高并发的状态下会降低代码流执行的吞吐量,而且会加大在穿插调用过程中彼此依赖发生死锁碰撞的可能。 在当前代码的执行状态下就有一定概率(很小,依赖于具体的进程调度时机)出现因为调度原因而先调度到了Thread1并执行了对象A的同步方法,然后调度到了Thread2并执行对象B的同步方法,在对象B的同步方法中想要去调用对象A的同步方法,此时发生阻塞,接着又调度到了Thread1,继续执行对象A的同步方法中的代码,然后又去调用对象B的同步方法,因为在Thread2中已经持有了对象B的lock,所以此时Thread1也发生了阻塞,当前的状态就是Thread1和Thread2相互等待对方释放锁而无限期等待,各种的代码流得不到执行而死锁。 考虑AudioService中复杂的逻辑,所以要以最小风险的改动来修复这个问题,因此这里给出的方案没有进行太大的改动,而且比较显而易见的是全部加synchronized关键字的这些出问题的代码,AOSP还有一定的优化空间。 最终针对以上问题的根本原因,我们给出以下解决方案: 1、更换同步锁的类型 在需要同步的临界代码区使用类的全局锁来代替每个实例自己的锁,从而保证多个Thread在相互穿插调用时不会发生死锁。 2、方案相关的具体代码和backtrace 三、问题初步分析 以ALTO4.5TMO出问题时候的一份典型backtrace和log为例,发现出问题时SystemServer的主线程block在了一个AudioService内部的一个函数上,从而引发ANR和SWT重启,具体backtrace如下: 为什么会block?通过查看如上对应代码,发现这个方法是个synchronized的,而且方法中在满足条件时还会遍历并调用同类型但是不同实例对象的synchronized方法,因此这里被block就需要满足一个条件:调用的同类型不同实例对象的synchronized方法无法进入,即在其他thread中已经进入了这个synchronized方法。 根据这个线索继续查看SystemServer中与AudioService相关的thread的调用栈,找到Binder_2这个thread,具体的backtrace如下: 通过backtrace和对应代码我们发现Binder_2这个thread也block在了一个AudioService内部的synchronized函数中,同样的这个函数中在满足一定的条件时也会调用同类型不同实例的synchronized方法。 四、深入分析问题 经过初步分析我们定位到了第一个问题点,即两个不同的Thread都block在了同一个类型的synchronized方法上,同时也产生了1个问题,接下来我们继续深入分析以期能到找到答案和问题的根本原因。 1、两个Thread为什么会同时block? 通过进一步分析和查看代码发现,由于两个Thread所执行的都是synchronized方法,如果它们由于调度和执行原因而产生了相互依赖的关系,那么就会发生同时block的现象而死锁,由于backtrace只能看到调用关系,不能知道运行时各个对象实例的状态,所以我们根据backtrace模拟systemserver中当前这两个thread的问题状态,结果完全匹配当前的问题现象,具体的模拟代码如下: 先自定义一个Thread类,接收两个TestSync类的实例并在run里面调用实例1的同步方法,同时将实例2传递过去。 接着定义一个TestSync类,并定义两个synchronized的成员函数,然后在每个函数开始的地方都先sleep 10ms,以满足进程调度切换的状态。 最后在activity的onResume方法中进行测试,结果测试的activity就会ANR,为什么会ANR? 原理和上面systemServer ANR并SWT重启一样,这里activity的UI主线程和新建的ct1线程发生了死锁。 以上代码的执行流程大致如下: 1、新建t1,t2两个TestSync类的实例以及CThread类的实例ct1并将t1和t2传递过去 2、启动ct1这个thread 3、无论是ct1的代码流先被调度到执行还是UI主线程继续执行都会进入t1或者t2的synchronized方法。 4、这里假设ct1在start之后立马被调度到并执行了t1的synchronized方法,然后sleep 10ms,此时再次发生调度。 5、UI主线程被再次调度到,然后执行t2的synchronized方法,sleep 10ms,再次调度到其它thread。 6、等到ct1的10ms sleep先结束之后再次调度到ct1,然后执行t2的synchronized方法,这里会发生阻塞,因为在UI主线程中已经进入到了t2的synchronized方法,即t2实例自己的lock已经处于锁定状态,然后调度到其他thread。 7、等到UI主线程的10ms sleep结束之后再次调度到UI主线程,然后执行t1的synchronized的方法,这里同样会发生阻塞,因为在ct1中已经进入t1的synchronized方法,t1实例自己的lock已经处于锁定状态,然后调度到其他thread。 8、此时ct1和UI主线程已经产生相互依赖而死锁。 将上面的代码中使用的synchronized关键字更改为同步类的全局锁,问题解决,activity不会再发生ANR,具体更改如下: 五、解决方案潜在的影响 由于使用类的全局锁,而且没有细分临界区,所以在高并发的情况下可能会略微降低代码执行流的吞吐量,但是这个影响对SystemServer中AudioService的setindex和getindex等方法可以忽略,因为这几个方法都非常轻量级并且并发量不会达到太高的量级。 Analyzed by vincent.song from SWD2 Framework team. vincent.song@tcl.com 201506241646
通过初步分析、深入分析(具体分析过程、关键代码和log在下面会附上)我们清楚的知道了问题发生的原因: 1、SystemServer进程的主线程访问media audio的接口进行跨进程调用时被block。 2、Media audio的接口最终会调用到mediaserver进程中。 3、Mediaserver进程中产生了死锁。 4、最终导致systemServer先ANR然后被SWT重启。 在当前代码的执行状态下有一定概率(很小,必须满足sp指针获取到之后并成为最后一个强引用的条件)出现递归调用一个加了默认为NORMAL类型的mutex autolock的方法而产生死锁。 考虑mediaserver中复杂的逻辑,所以要以最小风险的改动来修复这个问题,因此这里给出的方案没有进行太大的改动。 最终,针对以上问题的根本原因,我们给出以下解决方案: 1、延长sp指针的生命周期,使其生命周期长于mutex autolock 在可能递归调用的加了mutex autolock的函数中,使用vector容器延长sp指针的生命周期,使其在mutex autolock生命周期结束并unlock之后再析构sp指针,此时如果再发生递归调用则不会死锁。 2、方案相关的具体代码和backtrace 以上是发生死锁时锁对应的backtrace调用栈以及相应的代码,通过红线圈住部分我们可以看到发生问题时的关键调用关系和状态。 3、最终方案的代码修改 三、问题初步分析 以ALTO4.5TF出问题时候的一份典型backtrace和log为例,发现出问题时SystemServer的主线程block在了一个AudioSystem内部的一个native函数上,从而引发ANR和SWT重启,具体backtrace如下: 为什么会block?通过查看如上对应代码,发现setParameter这个方法会通过audioflinger的代理对象跨进程调用到mediaserver中的audioflinger的setParameter方法,因此接下来就要mediaserver进程是否工作的正常。 根据这个线索继续查看mediaserver中各个thread的调用栈,发现几乎所有的Binder thread都被block了,这也就解释了为什么SystemServer的调用得不到处理而发生ANR以及SWT重启。其中绝大多数Binder thread被block的backtrace如下: 由于mediaserver是native的C/C++代码,所以需要用address2line这个工具将对应的PC指针的地址在symbols中找到对应的源码来分析,具体过程不再赘述,大家可以找一些专门的文章来简单学习一下,经过这个过程之后得到的对应的源码如下: 从以上代码中我们可以看到getplayertype的实现中第一句代码就是要进行sLock.lock(),如果lock不成功则会等待,因此接下来继续看其他thread中谁占用了这个lock。通过查看其他thread的backtrace,发现Binder_5这个thread占用了sLock,但是它也被block在另外一个mutex lock上,具体的backtrace和代码如下: 顺着这个线索我们继续看mediaserver进程中的其他thread,看谁占用了ALooperRoster的mLock,最终找到了rtsp thread在调用ALooperRoster的unregisterStaleHandlers函数时占用了mLock这个锁,但同时它自己又间接调用了unregisterStaleHandlers,最终也被block在了这个mLock上,具体的backtrace和代码如下: 四、深入分析问题 经过初步分析我们定位到了第一个问题点,即rtsp thread block在了它已经lock的一个mLock上,同时也产生了2个问题,接下来我们继续深入分析以期能到找到答案和问题的根本原因。 1、为什么会间接递归调用ALooperRoster::unregisterStaleHandlers方法? 2、为什么rtsp thread自己会block在mLock上? 通过进一步分析和查看代码发现,rstp thread之所以会间接递归调用ALooperRoster::unregisterStaleHandlers方法,是因为sp<ALooper> looper = info.mLooper.promote();这句代码中的looper强指针sp在出现问题的场景下,出了一次for循环的作用域时会成为最后一个持有ALooper对象的sp而在析构时发生ALooper对象的释放,从而引发ALooper对象的析构,在ALooper对象析构时就会再次调用ALooperRoster::unregisterStaleHandlers方法而产生间接递归调用。关于Android中native层的C++的智能指针、sp、wp这里简单介绍一下,大概原理就是利用sp和wp指针对象的构造函数和析构函数来对所指向的对象进行引用计数的加减,指针所指向的对象必须要继承自class RefBase,根据对象的生命周期标志在强引用计数或弱引用计数为0时来进行对象的释放: 生命周期默认是STRONG,即当对象的强引用计数为0时就释放对象,关键代码如下: 回答完第一个问题,我们接下来看第二个问题,在回答上面第二个问题之前我们需要先简单说一下Autolock这个类,这个类是在system/core/include/utils/Mutex.h中Mutex类内部定义的,具体代码如下: 这个类的原理简单理解就是在构造函数中lock,析构函数中unlock,这正好利用了C++的特性,所以进入Autolock对象的作用域就会lock,出了作用域就会unlock,从而实现自动的Mutex。而Mutex类是pthread_mutex_t的一个封装,而pthread_mutex_t默认是NORMAL类型的,即不可递归重入的: 而ALooperRoster::unregisterStaleHandlers方法就是使用的Mutex::Autolock autoLock(mLock)进行同步的,在没有出Autolock的作用域时间接递归调用,这时mLock这个Mutex是lock着的,由于Mutex默认的这个不可递归重入的属性最终就导致了当前这个死锁的现象,到这这也就回答了为什么rtsp thread自己为什么会block在mLock上。 五、解决方案潜在的影响 由于仅仅是稍微延迟了sp指针的生命周期,在出了函数作用域之后就会马上释放,所以当前的方案没有其他已知的影响,最后再次看一下修改的代码和注释: Analyzed by vincent.song from SWD2 Framework team. vincent.song@tcl.com 201506242052
Firefox OS 平台的用户接口层。屏幕启动时渲染到屏幕上的一切都是Gaia层的产物。Gaia 实现了 lock screen, home screen, 和所有你所期待在智能手机上看到的标准应用。Gaia 完全使用 HTML, CSS, 和 JavaScript实现。Web APIs 是Gaia层到底层系统的唯一入口,Web APIs 是由Gecko 层实现的。第三方应用可以安装在Gaia层。 Gecko Firefox OS 应用的运行环境;该层提供了对: HTML, CSS, and JavaScript三个标准的支持,它能确保APIs能够在gecko支持的系统上良好工作。也就是说,它包括了网络栈,图形栈,布局引擎,js虚拟机和端口层。 Gonk 是Firefox OS平台更低层的系统,包括了 Linux kernel (基于 AOSP)和用户空间硬件抽象层 (HAL)。内核和一些用户空间库都是公共的开源项目:linux, libusb, bluez等。其他的一些硬件抽象层部分是与android项目共享的:GPS, camera等。你可以认为 Gonk 是一个非常简单的 Linux 版本。Gonk 是 Gecko 层的端口目标;也就是说 Gecko 层有到 Gonk 的端口,就像Gecko 到 Mac OS X, Windows, 和 Android 一样。因为Firefox OS 对 Gonk 拥有完全的控制权,相比其他操作系统,我们可以释放更多的接口到 Gecko。例如,Gecko 拥有到 Gonk 电话栈和帧缓冲区的直接入口,但在其他操作系统却没有。 这个术语经常用在移动app空间的讨论中,主要是指在app中缓慢/低效的代码操作会导致 block UI的更新甚至出现无响应状态。
Iptables简介 iptables是与最新的 2.6.x 版本 Linux 内核集成的 IP 信息包过滤系统。如果 Linux 系统连接到因特网或 LAN、服务器或连接 LAN 和因特网的代理服务器, 则该系统有利于在 Linux 系统上更好地控制 IP 信息包过滤和防火墙配置。 Iptables工作原理 netfilter/iptablesIP 信息包过滤系统是一种功能强大的工具, 可用于添加、编辑和除去规则,这些规则是在做信息包过滤决定时,防火墙所遵循和组成的规则。这些规则存储在专用的信息包过滤表中, 而这些表集成在 Linux 内核中。 在信息包过滤表中,规则被分组放在我们所谓的 链(chain)中。我马上会详细讨论这些规则以及如何建立这些规则并将它们分组在链中。 虽然 netfilter/iptables IP 信息包过滤系统被称为单个实体,但它实际上由两个组件 netfilter和 iptables 组成。 netfilter 组件也称为 内核空间(kernelspace),是内核的一部分,由一些信息包过滤表组成, 这些表包含内核用来控制信息包过滤处理的规则集。 iptables组件是一种工具,也称为 用户空间(userspace),它使插入、修改和除去信息包过滤表中的规则变得容易。 除非您正在使用 Red Hat Linux 7.1 或更高版本,否则需要从 netfilter.org 下载该工具并安装使用它。 通过使用用户空间,可以构建自己的定制规则,这些规则存储在内核空间的信息包过滤表中。 这些规则具有 目标,它们告诉内核对来自某些源、前往某些目的地或具有某些协议类型的信息包做些什么。 如果某个信息包与规则匹配,那么使用目标 ACCEPT 允许该信息包通过。还可以使用目标 DROP 或 REJECT 来阻塞并杀死信息包。对于可对信息包执行的其它操作,还有许多其它目标。 根据规则所处理的信息包的类型,可以将规则分组在链中。处理入站信息包的规则被添加到 INPUT 链中。处理出站信息包的规则被添加到 OUTPUT 链中。处理正在转发的信息包的规则被添加到 FORWARD 链中。这三个链是基本信息包过滤表中内置的缺省主链。 另外,还有其它许多可用的链的类型(如 PREROUTING 和 POSTROUTING ), 以及提供用户定义的链。每个链都可以有一个 策略, 它定义“缺省目标”,也就是要执行的缺省操作,当信息包与链中的任何规则都不匹配时,执行此操作。 建立规则并将链放在适当的位置之后,就可以开始进行真正的信息包过滤工作了。 这时内核空间从用户空间接管工作。当信息包到达防火墙时,内核先检查信息包的头信息,尤其是信息包的目的地。 我们将这个过程称为 路由。 如果信息包源自外界并前往系统,而且防火墙是打开的,那么内核将它传递到内核空间信息包过滤表的 INPUT 链。如果信息包源自系统内部或系统所连接的内部网上的其它源,并且此信息包要前往另一个外部系统, 那么信息包被传递到 OUTPUT 链。类似的,源自外部系统并前往外部系统的信息包被传递到 FORWARD 链。 接下来,将信息包的头信息与它所传递到的链中的每条规则进行比较,看它是否与某条规则完全匹配。 如果信息包与某条规则匹配,那么内核就对该信息包执行由该规则的目标指定的操作。 但是,如果信息包与这条规则不匹配,那么它将与链中的下一条规则进行比较。 最后,如果信息包与链中的任何规则都不匹配,那么内核将参考该链的策略来决定如何处理该信息包。 理想的策略应该告诉内核 DROP 该信息包。下图用图形说明了这个信息包过滤过程。 使用Iptables进行防火墙软件设计的解决方案 由于Iptables已经有了完善的防火墙规则,我们只需要设计一个基于Iptables的Android前台,通过运行脚本,调用iptables设置防火墙规则即可。 相关示例代码:http://code.google.com/p/droidwall/
android:allowTaskReparenting 用来标记Activity能否从启动的Task移动到有着affinity的Task(当这个Task进入到前台时)——“true”,表示能移动,“false”,表示它必须呆在启动时呆在的那个Task里。 如果这个特性没有被设定,设定到<application>元素上的allowTaskReparenting特性的值会应用到Activity上。默认值为“false”。 一般来说,当Activity启动后,它就与启动它的Task关联,并且在那里耗尽它的整个生命周期。当当前的Task不再显示时,你可以使用这个特性来强制Activity移动到有着affinity的Task中。典型用法是:把一个应用程序的Activity移到另一个应用程序的主Task中。 例如,如果e-mail中包含一个web页的链接,点击它就会启动一个Activity来显示这个页面。这个Activity是由Browser应用程序定义的,但是,现在它作为e-mail Task的一部分。如果它重新宿主到Browser Task里,当Browser下一次进入到前台时,它就能被看见,并且,当e-mail Task再次进入前台时,就看不到它了。 Actvity的affinity是由taskAffinity特性定义的。Task的affinity是通过读取根Activity的affinity决定。因此,根据定义,根Activity总是位于相同affinity的Task里。由于启动模式为“singleTask”和“singleInstance”的Activity只能位于Task的底部,因此,重新宿主只能限于“standard”和“singleTop”模式。android:alwaysRetainTaskState 用来标记Activity所在的Task的状态是否总是由系统来保持——“true”,表示总是;“false”,表示在某种情形下允许系统恢复Task到它的初始化状态。默认值是“false”。这个特性只针对Task的根Activity有意义;对其它Activity来说,忽略之。 一般来说,特定的情形如当用户从主画面重新选择这个Task时,系统会对这个Task进行清理(从stack中删除位于根Activity之上的所有Activivity)。典型的情况,当用户有一段时间没有访问这个Task时也会这么做,例如30分钟。 然而,当这个特性设为“true”时,用户总是能回到这个Task的最新状态,无论他们是如何启动的。这非常有用,例如,像Browser应用程序,这里有很多的状态(例如多个打开的Tab),用户不想丢失这些状态。android:clearTaskOnLaunch 用来标记是否从Task中清除所有的Activity,除了根Activity外(每当从主画面重新启动时)——“true”,表示总是清除至它的根Activity,“false”表示不。默认值是“false”。这个特性只对启动一个新的Task的Activity(根Activity)有意义;对Task中其它的Activity忽略。 当这个值为“true”,每次用户重新启动这个Task时,都会进入到它的根Activity中,不管这个Task最后在做些什么,也不管用户是使用BACK还是HOME离开的。当这个值为“false”时,可能会在一些情形下(参考alwaysRetainTaskState特性)清除Task的Activity,但不总是。 假设,某人从主画面启动了Activity P,并从那里迁移至Activity Q。接下来用户按下HOME,然后返回Activity P。一般,用户可能见到的是Activity Q,因为它是P的Task中最后工作的内容。然而,如果P设定这个特性为“true”,当用户按下HOME并使这个Task再次进入前台时,其上的所有的Activity(在这里是Q)都将被清除。因此,当返回到这个Task时,用户只能看到P。 如果这个特性和allowTaskReparenting都设定为“true”,那些能重新宿主的Activity会移动到共享affinity的Task中;剩下的Activity都将被抛弃,如上所述。android:finishOnTaskLaunch 用来标记当用户再次启动它的Task(在主画面选择这个Task)时已经存在的Activity实例是否要关闭(结束)——“true”,表示应该关闭,“false”表示不关闭。默认值是“false”。 如果这个特性和allowTaskReparenting都设定为“true”,这个特性胜出。Activity的affinity忽略。这个Activity不会重新宿主,但是会销毁。android:launchMode 用于指示Activity如何启动。这里有四种模式,与Intent对象中的Activity Flags(FLAG_ACTIVITY_*变量)共同作用,来决定Activity如何启动来处理Intent。它们是: "standard" "singleTop" "singleTask" "singleInstance" 默认模式是“standard”。 这些模式可以分成两大组别,“standard”和“singleTop”一组,“singleTask”和“singleInstance”一组。具有“standard”和“singleTop”启动模式的Activity可以实例化很多次。这些实例可以属于任何Task并且可以位于Activity stack的任何位置。典型的情况是,它们会进入调用startActivity()的Task(除非Intent对象包含FLAG_ACTIVITY_NEW_TASK标志,在这种情况下会选择一个不同的Task——参考taskAffinity特性)。 相反的,“singleTask”和“singleInstance”只能启动一个Task。它们总是位于Activity stack的底部。甚至,设备一次只能拥有一个Activity的实例——只有一个这样的Task。 “standard”和“singleTop”模式只在一种情况下有差别:每次有一个新的启动“standard”Activity的Intent,就会创建一个新的实例来响应这个Intent。每个实例处理一个Intent。相似的,一个“singleTop”的Activity实例也有可能被创建来处理新的Intent。然而,如果目标Task已经有一个存在的实例并且位于stack的顶部,那么,这个实例就会接收到这个新的Intent(调用onNewIntent());不会创建新的实例。在其他情况下——例如,如果存在的“singleTop”的Activity实例在目标Task中,但不是在stack的顶部,或者它在一个stack的顶部,但不是在目标Task中——新的实例都会被创建并压入stack中。 “singleTask”和“singleInstance”模式也只在一种情况下有差别:“singleTask”Activity允许其它Activity成为它的Task的部分。它位于Activity stack的底部,其它Activity(必须是“standard”和“singleTop”Activity)可以启动加入到相同的Task中。“singleInstance”Activity,换句话说,不允许其它Activity成为它的Task的部分。它是Task中的唯一Activity。如果它启动其它的Activity,这个Activity会被放置到另一个task中——好像Intent中包含了FLAG_ACTIVITY_NEW_TASK标志。android:noHistory 用于标记当用户从Activity上离开并且它在屏幕上不再可见时Activity是否从Activity stack中清除并结束(调用finish()方法)——“true”,表示它应该关闭,“false”,表示不需要。默认值是“false”。 “true”值意味着Activity不会留下历史痕迹。因为它不会在Activity stack的Task中保留,因此,用户不能返回它。android:taskAffinity Activity为Task拥有的一个affinity。拥有相同的affinity的Activity理论上属于相同的Task(在用户的角度是相同的“应用程序”)。Task的affinity是由它的根Activity决定的。 affinity决定两件事情——Activity重新宿主的Task(参考allowTaskReparenting特性)和使用FLAG_ACTIVITY_NEW_TASK标志启动的Activity宿主的Task。 默认情况,一个应用程序中的所有Activity都拥有相同的affinity。捏可以设定这个特性来重组它们,甚至可以把不同应用程序中定义的Activity放置到相同的Task中。为了明确Activity不宿主特定的Task,设定该特性为空的字符串。 如果这个特性没有设置,Activity将从应用程序的设定那里继承下来(参考<application>元素的taskAffinity特性)。应用程序默认的affinity的名字是<manifest>元素中设定的package名。 FLAG_ACTIVITY_BROUGHT_TO_FRONT 这个标志一般不是由程序代码设置的,如在launchMode中设置singleTask模式时系统帮你设定。FLAG_ACTIVITY_CLEAR_TOP 如果设置,并且这个Activity已经在当前的Task中运行,因此,不再是重新启动一个这个Activity的实例,而是在这个Activity上方的所有Activity都将关闭,然后这个Intent会作为一个新的Intent投递到老的Activity(现在位于顶端)中。 例如,假设一个Task中包含这些Activity:A,B,C,D。如果D调用了startActivity(),并且包含一个指向Activity B的Intent,那么,C和D都将结束,然后B接收到这个Intent,因此,目前stack的状况是:A,B。 上例中正在运行的Activity B既可以在onNewIntent()中接收到这个新的Intent,也可以把自己关闭然后重新启动来接收这个Intent。如果它的启动模式声明为“multiple”(默认值),并且你没有在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,那么它将关闭然后重新创建;对于其它的启动模式,或者在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,都将把这个Intent投递到当前这个实例的onNewIntent()中。 这个启动模式还可以与FLAG_ACTIVITY_NEW_TASK结合起来使用:用于启动一个Task中的根Activity,它会把那个Task中任何运行的实例带入前台,然后清除它直到根Activity。这非常有用,例如,当从Notification Manager处启动一个Activity。FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 如果设置,这将在Task的Activity stack中设置一个还原点,当Task恢复时,需要清理Activity。也就是说,下一次Task带着FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记进入前台时(典型的操作是用户在主画面重启它),这个Activity和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的Activity。 这在你的程序有分割点的时候很有用。例如,一个e-mail应用程序可能有一个操作是查看一个附件,需要启动图片浏览Activity来显示。这个Activity应该作为e-mail应用程序Task的一部分,因为这是用户在这个Task中触发的操作。然而,当用户离开这个Task,然后从主画面选择e-mail app,我们可能希望回到查看的会话中,但不是查看图片附件,因为这让人困惑。通过在启动图片浏览时设定这个标志,浏览及其它启动的Activity在下次用户返回到mail程序时都将全部清除。FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 如果设置,新的Activity不会在最近启动的Activity的列表中保存。FLAG_ACTIVITY_FORWARD_RESULT 如果设置,并且这个Intent用于从一个存在的Activity启动一个新的Activity,那么,这个作为答复目标的Activity将会传到这个新的Activity中。这种方式下,新的Activity可以调用setResult(int),并且这个结果值将发送给那个作为答复目标的Activity。FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 这个标志一般不由应用程序代码设置,如果这个Activity是从历史记录里启动的(常按HOME键),那么,系统会帮你设定。FLAG_ACTIVITY_MULTIPLE_TASK 不要使用这个标志,除非你自己实现了应用程序启动器。与FLAG_ACTIVITY_NEW_TASK结合起来使用,可以禁用把已存的Task送入前台的行为。当设置时,新的Task总是会启动来处理Intent,而不管这是是否已经有一个Task可以处理相同的事情。 由于默认的系统不包含图形Task管理功能,因此,你不应该使用这个标志,除非你提供给用户一种方式可以返回到已经启动的Task。 如果FLAG_ACTIVITY_NEW_TASK标志没有设置,这个标志被忽略。FLAG_ACTIVITY_NEW_TASK 如果设置,这个Activity会成为历史stack中一个新Task的开始。一个Task(从启动它的Activity到下一个Task中的Activity)定义了用户可以迁移的Activity原子组。Task可以移动到前台和后台;在某个特定Task中的所有Activity总是保持相同的次序。 这个标志一般用于呈现“启动”类型的行为:它们提供用户一系列可以单独完成的事情,与启动它们的Activity完全无关。 使用这个标志,如果正在启动的Activity的Task已经在运行的话,那么,新的Activity将不会启动;代替的,当前Task会简单的移入前台。参考FLAG_ACTIVITY_MULTIPLE_TASK标志,可以禁用这一行为。 这个标志不能用于调用方对已经启动的Activity请求结果。FLAG_ACTIVITY_NO_ANIMATION 如果在Intent中设置,并传递给Context.startActivity()的话,这个标志将阻止系统进入下一个Activity时应用Acitivity迁移动画。这并不意味着动画将永不运行——如果另一个Activity在启动显示之前,没有指定这个标志,那么,动画将被应用。这个标志可以很好的用于执行一连串的操作,而动画被看作是更高一级的事件的驱动。FLAG_ACTIVITY_NO_HISTORY 如果设置,新的Activity将不再历史stack中保留。用户一离开它,这个Activity就关闭了。这也可以通过设置noHistory特性。FLAG_ACTIVITY_NO_USER_ACTION 如果设置,作为新启动的Activity进入前台时,这个标志将在Activity暂停之前阻止从最前方的Activity回调的onUserLeaveHint()。 典型的,一个Activity可以依赖这个回调指明显式的用户动作引起的Activity移出后台。这个回调在Activity的生命周期中标记一个合适的点,并关闭一些Notification。 如果一个Activity通过非用户驱动的事件,如来电或闹钟,启动的,这个标志也应该传递给Context.startActivity,保证暂停的Activity不认为用户已经知晓其Notification。FLAG_ACTIVITY_PREVIOUS_IS_TOP If set and this intent is being used to launch a new activity from an existing one, the current activity will not be counted as the top activity for deciding whether the new intent should be delivered to the top instead of starting a new one. The previous activity will be used as the top, with the assumption being that the current activity will finish itself immediately. FLAG_ACTIVITY_REORDER_TO_FRONT 如果在Intent中设置,并传递给Context.startActivity(),这个标志将引发已经运行的Activity移动到历史stack的顶端。 例如,假设一个Task由四个Activity组成:A,B,C,D。如果D调用startActivity()来启动Activity B,那么,B会移动到历史stack的顶端,现在的次序变成A,C,D,B。如果FLAG_ACTIVITY_CLEAR_TOP标志也设置的话,那么这个标志将被忽略。FLAG_ACTIVITY_RESET_TASK_IF_NEEDEDIf set, and this activity is either being started in a new task or bringing to the top an existing task, then it will be launched as the front door of the task. This will result in the application of any affinities needed to have that task in the proper state (either moving activities to or from it), or simply resetting that task to its initial state if needed. FLAG_ACTIVITY_SINGLE_TOP 如果设置,当这个Activity位于历史stack的顶端运行时,不再启动一个新的。 Activity和Task 之前提到的,一个Activity可以启动另一个,即便是定义在不同应用程序中的Activity。例如,假设你想让用户显示一些地方的街景。而这里已经有一个Activity可以做到这一点,因此,你的Activity所需要做的只是在Intent对象中添加必要的信息,并传递给startActivity()。地图浏览将会显示你的地图。当用户按下BACK键,你的Activity会再次出现在屏幕上。 对于用户来说,看起来好像是地图浏览与你的Activity一样,属于相同的应用程序,即便是它定义在其它的应用程序里,并运行在那个应用程序的进程里。Android通过将这两个Activity保存在同一个Task里来体现这一用户体验。简单来说,一个Task就是用户体验上的一个“应用”。它将相关的Activity组合在一起,以stack的方式管理。stack中根Activity启动Task——典型的,它就是用户在应用程序启动栏中选择的Activity。位于stack顶端的Activity是当前正在运行的——能够聚焦用户的动作。当一个Activity启动另一个,新的Activity进入stack;它成为正在运行的Activity。之前的Activity仍保留在stack中。当用户按下BACK键,当前的Activity从stack中退出,之前的那个成为正在运行的Activity。 stack包含对象,因此,如果一个Task中有多个同一个Activity的实例时——多个地图浏览,例如——stack为每个实例拥有一个独立的入口。位于stack中的Activity不会重新调整,只是进入和退出。 一个Task就是一组Activity,不是一个类或者在manifest中定义的一个元素。因此,没有办法为Task设置独立于它的Activity的属性值。Task的值作为整体在根Activity中设置。例如,下一个章节会讨论Task的“affinity”;那个值就是从Task中的根Activity中读取的。 Task中的所有Activity作为一个单元一起移动。整个Task(整个Activity stack)可以进入前台或者退到后台。例如,假设当前Task中的stack中有4个Activity——3个位于当前Activity下方。用户按下HOME键,进入到应用程序启动栏,然后选择一个新的应用程序(实际上,一个新的Task)。当前Task退到后台,并且新Task中的根Activity会显示出来。然后,经过一段时间后,用户回到Home画面,然后再次选择前一个应用程序(前一个Task)。那个拥有4个Activity的Task会进入前台。当用户按下BACK键,屏幕不会显示用户刚刚离开的Activity(前一个Task的根Activity)。而是,这个stack中的顶端Activity移除,相同Task中的前一个Activity会显示出来。 刚才描述的行为是Activity和Task的默认行为。但有方法来完全改变它。Task之间的关联,和一个Task中的一个Activity行为,受启动Activity的Intent对象中设置的Flag和manifest文件中Activity的<activity>元素的特性值交互控制。调用者和响应者都有权决定如何发生。 核心的Intent Flag有: FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP FLAG_ACTIVITY_RESET_TASK_IF_NEEDED FLAG_ACTIVITY_SINGLE_TOP 核心的<activity>特性有: taskAffinity launchMode allowTaskReparenting clearTaskOnLaunch alwaysRetainTaskState finishOnTaskLaunch 接下来的章节将描述一些Flag和特性的用法,如何相互影响,以及在使用时的建议。 Affinity和新Task 默认情况下,一个应用程序中的所有Activity都有affinity——也就是说,属于同一个Task中所有Activity有一个设定。然而,每个Activity都可以在<activity>元素的taskAffinity特性上设置单独的值。定义在不同应用程序中的Activity可以共享同一个affinity,或者定义在同一个应用程序中的Activity设置不同的affinity。Affinity在两种环境下工作:Intent对象包含FLAG_ACTIVITY_NEW_TASK标志,和Activity的allowTaskReparenting特性设置为“true”。 FLAG_ACTIVITY_NEW_TASK: 之前描述的,一个Activity一般通过调用startActivity()启动并加入到Task中。它同调用者一样,进入同一个Task。然而,如果传递给startActivity()的Intent对象中包含FLAG_ACTIVITY_NEW_TASK时,系统会搜索一个新的Task来容纳新的Activity。通常,如标志的名字所示,是一个新的Task。然而,并不是必须是。如果已经存在一个Task与新Activity的affinity相同,这个Activity就会加入到那个Task中。如果不是,启动一个新的Task。 allowTaskReparenting: 如果一个Activity的allowTaskReparenting特性设置为“true”,它就能从启动的Task中移到有着相同affinity的Task(这个Task进入到前台的时候)。例如,在一个旅游的程序中定义了一个可以报告选择城市的天气情况的Activity。它和同一个应用程序的其它Activity一样,有着相同的Affinity(默认的Affinity),并且它允许重新宿主。你的Activity中的一个启动了天气预报,因此,它初始化到和你Activity相同的Task中。然而,当旅游应用程序下一次进入到前台时,天气预报那个Activity将会重新编排并在那个Task中显示。 如果从用户的角度出发,一个.apk文件包含多个“应用”的话,你可能希望为关联的Activity设置不同的affinity。
< drawable name="white">#FFFFFF< /drawable>< !--白色 --> < drawable name="black">#000000< /drawable>< !--黑色 --> < drawable name="ivory">#FFFFF0< /drawable>< !--象牙色 --> < drawable name="lightyellow">#FFFFE0< /drawable>< !--亮黄色 --> < drawable name="yellow">#FFFF00< /drawable>< !--黄色 --> < drawable name="snow">#FFFAFA< /drawable>< !--雪白色 --> < drawable name="floralwhite">#FFFAF0< /drawable>< !--花白色 --> < drawable name="lemonchiffon">#FFFACD< /drawable>< !--柠檬绸色 --> < drawable name="cornsilk">#FFF8DC< /drawable>< !--米绸色 --> < drawable name="seashell">#FFF5EE< /drawable>< !--海贝色 --> < drawable name="lavenderblush">#FFF0F5< /drawable>< !--淡紫红 --> < drawable name="papayawhip">#FFEFD5< /drawable>< !--番木色 --> < drawable name="blanchedalmond">#FFEBCD< /drawable>< !--白杏色 --> < drawable name="mistyrose">#FFE4E1< /drawable>< !--浅玫瑰色 --> < drawable name="bisque">#FFE4C4< /drawable>< !--桔黄色 --> < drawable name="moccasin">#FFE4B5< /drawable>< !--鹿皮色 --> < drawable name="navajowhite">#FFDEAD< /drawable>< !--纳瓦白 --> < drawable name="peachpuff">#FFDAB9< /drawable>< !--桃色 --> < drawable name="gold">#FFD700< /drawable>< !--金色 --> < drawable name="pink">#FFC0CB< /drawable>< !--粉红色 --> < drawable name="lightpink">#FFB6C1< /drawable>< !--亮粉红色 --> < drawable name="orange">#FFA500< /drawable>< !--橙色 --> < drawable name="lightsalmon">#FFA07A< /drawable>< !--亮肉色 --> < drawable name="darkorange">#FF8C00< /drawable>< !--暗桔黄色 --> < drawable name="coral">#FF7F50< /drawable>< !--珊瑚色 --> < drawable name="hotpink">#FF69B4< /drawable>< !--热粉红色 --> < drawable name="tomato">#FF6347< /drawable>< !--西红柿色 --> < drawable name="orangered">#FF4500< /drawable>< !--红橙色 --> < drawable name="deeppink">#FF1493< /drawable>< !--深粉红色 --> < drawable name="fuchsia">#FF00FF< /drawable>< !--紫红色 --> < drawable name="magenta">#FF00FF< /drawable>< !--红紫色 --> < drawable name="red">#FF0000< /drawable>< !--红色 --> < drawable name="oldlace">#FDF5E6< /drawable>< !--老花色 --> < drawable name="lightgoldenrodyellow">#FAFAD2< /drawable>< !--亮 金黄色 --> < drawable name="linen">#FAF0E6< /drawable>< !--亚麻色 --> < drawable name="antiquewhite">#FAEBD7< /drawable>< !--古董白 --> < drawable name="salmon">#FA8072< /drawable>< !--鲜肉色 --> < drawable name="ghostwhite">#F8F8FF< /drawable>< !--幽灵白 --> < drawable name="mintcream">#F5FFFA< /drawable>< !--薄荷色 --> < drawable name="whitesmoke">#F5F5F5< /drawable>< !--烟白色 --> < drawable name="beige">#F5F5DC< /drawable>< !--米色 --> < drawable name="wheat">#F5DEB3< /drawable>< !--浅黄色 --> < drawable name="sandybrown">#F4A460< /drawable>< !--沙褐色 --> < drawable name="azure">#F0FFFF< /drawable>< !--天蓝色 --> < drawable name="honeydew">#F0FFF0< /drawable>< !--蜜色 --> < drawable name="aliceblue">#F0F8FF< /drawable>< !--艾利斯兰 --> < drawable name="khaki">#F0E68C< /drawable>< !--黄褐色 --> < drawable name="lightcoral">#F08080< /drawable>< !--亮珊瑚色 --> < drawable name="palegoldenrod">#EEE8AA< /drawable>< !--苍麒麟色 --> < drawable name="violet">#EE82EE< /drawable>< !--紫罗兰色 --> < drawable name="darksalmon">#E9967A< /drawable>< !--暗肉色 --> < drawable name="lavender">#E6E6FA< /drawable>< !--淡紫色 --> < drawable name="lightcyan">#E0FFFF< /drawable>< !--亮青色 --> < drawable name="burlywood">#DEB887< /drawable>< !--实木色 --> < drawable name="plum">#DDA0DD< /drawable>< !--洋李色 --> < drawable name="gainsboro">#DCDCDC< /drawable>< !--淡灰色 --> < drawable name="crimson">#DC143C< /drawable>< !--暗深红色 --> < drawable name="palevioletred">#DB7093< /drawable>< !--苍紫罗兰色 --> < drawable name="goldenrod">#DAA520< /drawable>< !--金麒麟色 --> < drawable name="orchid">#DA70D6< /drawable>< !--淡紫色 --> < drawable name="thistle">#D8BFD8< /drawable>< !--蓟色 --> < drawable name="lightgray">#D3D3D3< /drawable>< !--亮灰色 --> < drawable name="lightgrey">#D3D3D3< /drawable>< !--亮灰色 --> < drawable name="tan">#D2B48C< /drawable>< !--茶色 --> < drawable name="chocolate">#D2691E< /drawable>< !--巧可力色 --> < drawable name="peru">#CD853F< /drawable>< !--秘鲁色 --> < drawable name="indianred">#CD5C5C< /drawable>< !--印第安红 --> < drawable name="mediumvioletred">#C71585< /drawable>< !--中紫罗兰 色 --> < drawable name="silver">#C0C0C0< /drawable>< !--银色 --> < drawable name="darkkhaki">#BDB76B< /drawable>< !--暗黄褐色 < drawable name="rosybrown">#BC8F8F< /drawable>< !--褐玫瑰红 --> < drawable name="mediumorchid">#BA55D3< /drawable>< !--中粉紫色 --> < drawable name="darkgoldenrod">#B8860B< /drawable>< !--暗金黄色 --> < drawable name="firebrick">#B22222< /drawable>< !--火砖色 --> < drawable name="powderblue">#B0E0E6< /drawable>< !--粉蓝色 --> < drawable name="lightsteelblue">#B0C4DE< /drawable>< !--亮钢兰色 --> < drawable name="paleturquoise">#AFEEEE< /drawable>< !--苍宝石绿 --> < drawable name="greenyellow">#ADFF2F< /drawable>< !--黄绿色 --> < drawable name="lightblue">#ADD8E6< /drawable>< !--亮蓝色 --> < drawable name="darkgray">#A9A9A9< /drawable>< !--暗灰色 --> < drawable name="darkgrey">#A9A9A9< /drawable>< !--暗灰色 --> < drawable name="brown">#A52A2A< /drawable>< !--褐色 --> < drawable name="sienna">#A0522D< /drawable>< !--赭色 --> < drawable name="darkorchid">#9932CC< /drawable>< !--暗紫色 --> < drawable name="palegreen">#98FB98< /drawable>< !--苍绿色 --> < drawable name="darkviolet">#9400D3< /drawable>< !--暗紫罗兰色 --> < drawable name="mediumpurple">#9370DB< /drawable>< !--中紫色 --> < drawable name="lightgreen">#90EE90< /drawable>< !--亮绿色 --> < drawable name="darkseagreen">#8FBC8F< /drawable>< !--暗海兰色 --> < drawable name="saddlebrown">#8B4513< /drawable>< !--重褐色 --> < drawable name="darkmagenta">#8B008B< /drawable>< !--暗洋红 --> < drawable name="darkred">#8B0000< /drawable>< !--暗红色 --> < drawable name="blueviolet">#8A2BE2< /drawable>< !--紫罗兰蓝色 < drawable name="lightskyblue">#87CEFA< /drawable>< !--亮天蓝色 --> < drawable name="skyblue">#87CEEB< /drawable>< !--天蓝色 --> < drawable name="gray">#808080< /drawable>< !--灰色 --> < drawable name="grey">#808080< /drawable>< !--灰色 --> < drawable name="olive">#808000< /drawable>< !--橄榄色 --> < drawable name="purple">#800080< /drawable>< !--紫色 --> < drawable name="maroon">#800000< /drawable>< !--粟色 --> < drawable name="aquamarine">#7FFFD4< /drawable>< !--碧绿色 --> < drawable name="chartreuse">#7FFF00< /drawable>< !--黄绿色 --> < drawable name="lawngreen">#7CFC00< /drawable>< !--草绿色 --> < drawable name="mediumslateblue">#7B68EE< /drawable>< !--中暗蓝色 --> < drawable name="lightslategray">#778899< /drawable>< !--亮蓝灰 --> < drawable name="lightslategrey">#778899< /drawable>< !--亮蓝灰 --> < drawable name="slategray">#708090< /drawable>< !--灰石色 --> < drawable name="slategrey">#708090< /drawable>< !--灰石色 --> < drawable name="olivedrab">#6B8E23< /drawable>< !--深绿褐色 --> < drawable name="slateblue">#6A5ACD< /drawable>< !--石蓝色 --> < drawable name="dimgray">#696969< /drawable>< !--暗灰色 --> < drawable name="dimgrey">#696969< /drawable>< !--暗灰色 --> < drawable name="mediumaquamarine">#66CDAA< /drawable>< !--中绿色 --> < drawable name="cornflowerblue">#6495ED< /drawable>< !--菊兰色 --> < drawable name="cadetblue">#5F9EA0< /drawable>< !--军兰色 --> < drawable name="darkolivegreen">#556B2F< /drawable>< !--暗橄榄绿 < drawable name="indigo">#4B0082< /drawable>< !--靛青色 --> < drawable name="mediumturquoise">#48D1CC< /drawable>< !--中绿宝石 --> < drawable name="darkslateblue">#483D8B< /drawable>< !--暗灰蓝色 --> < drawable name="steelblue">#4682B4< /drawable>< !--钢兰色 --> < drawable name="royalblue">#4169E1< /drawable>< !--皇家蓝 --> < drawable name="turquoise">#40E0D0< /drawable>< !--青绿色 --> < drawable name="mediumseagreen">#3CB371< /drawable>< !--中海蓝 --> < drawable name="limegreen">#32CD32< /drawable>< !--橙绿色 --> < drawable name="darkslategray">#2F4F4F< /drawable>< !--暗瓦灰色 --> < drawable name="darkslategrey">#2F4F4F< /drawable>< !--暗瓦灰色 --> < drawable name="seagreen">#2E8B57< /drawable>< !--海绿色 --> < drawable name="forestgreen">#228B22< /drawable>< !--森林绿 --> < drawable name="lightseagreen">#20B2AA< /drawable>< !--亮海蓝色 --> < drawable name="dodgerblue">#1E90FF< /drawable>< !--闪兰色 --> < drawable name="midnightblue">#191970< /drawable>< !--中灰兰色 --> < drawable name="aqua">#00FFFF< /drawable>< !--浅绿色 --> < drawable name="cyan">#00FFFF< /drawable>< !--青色 --> < drawable name="springgreen">#00FF7F< /drawable>< !--春绿色 --> < drawable name="lime">#00FF00< /drawable>< !--酸橙色 --> < drawable name="mediumspringgreen">#00FA9A< /drawable>< !--中春绿 色 --> < drawable name="darkturquoise">#00CED1< /drawable>< !--暗宝石绿 --> < drawable name="deepskyblue">#00BFFF< /drawable>< !--深天蓝色 --> < drawable name="darkcyan">#008B8B< /drawable>< !--暗青色 --> < drawable name="teal">#008080< /drawable>< !--水鸭色 --> < drawable name="green">#008000< /drawable>< !--绿色 --> < drawable name="darkgreen">#006400< /drawable>< !--暗绿色 --> < drawable name="blue">#0000FF< /drawable>< !--蓝色 --> < drawable name="mediumblue">#0000CD< /drawable>< !--中兰色 --> < drawable name="darkblue">#00008B< /drawable>< !--暗蓝色 --> < drawable name="navy">#000080< /drawable>< !--海军色 -->
作为一个完整的应用程序,数据存储操作是必不可少的。因此,Android系统一共提供了四种数据存储方式。分别是:SharePreference、SQLite、Content Provider和File。由于Android系统中,数据基本都是私有的的,都是存放于“data/data/程序包名”目录下,所以要实现数据共享,正确方式是使用Content Provider。 SQLite SQLite是一个轻量级的数据库,支持基本SQL语法,是常被采用的一种数据存储方式。Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的API。 SharedPreference 除SQLite数据库外,另一种常用的数据存储方式,其本质就是一个xml文件,常用于存储较简单的参数设置。 即常说的文件(I/O)存储方法,常用语存储大数量的数据,但是缺点是更新数据将是一件困难的事情。 ContentProvider Android系统中能实现所有应用程序共享的一种数据存储方式,由于数据通常在各应用间的是互相私密的,所以此存储方式较少使用,但是其又是必不可少的一种存储方式。例如音频,视频,图片和通讯录,一般都可以采用此种方式进行存储。每个Content Provider都会对外提供一个公共的URI(包装成Uri对象),如果应用程序有数据需要共享时,就需要使用Content Provider为这些数据定义一个URI,然后其他的应用程序就通过Content Provider传入这个URI来对数据进行操作。 PS: URI由3个部分组成:"content://"、数据的路径、标识ID(可选)。
检测某Activity是否在当前Task的栈顶 public static boolean isTopActivy(String cmdName, Context context) ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<RunningTaskInfo> runningTaskInfos = manager.getRunningTasks(Integer.MAX_VALUE); String cmpNameTemp = null; if (null != runningTaskInfos) cmpNameTemp = (runningTaskInfos.get(0).topActivity).toString(); if (null == cmpNameTemp) return false; return cmpNameTemp.equals(cmdName); 判断Android应用是否在前台 public static boolean isAppOnForeground(Context context) ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); String packageName = context.getPackageName(); List<RecentTaskInfo> appTask = activityManager.getRecentTasks(Integer.MAX_VALUE, 1); if (appTask == null) return false; if (appTask.get(0).baseIntent.toString().contains(packageName)) return true; return false; 获取Android手机内安装的所有桌面 private static List<String> getAllTheLauncher(Context context) List<String> names = null; PackageManager pkgMgt = context.getPackageManager(); Intent it = new Intent(Intent.ACTION_MAIN); it.addCategory(Intent.CATEGORY_HOME); List<ResolveInfo> ra = pkgMgt.queryIntentActivities(it, 0); if (ra.size() != 0) names = new ArrayList<String>(); for (int i = 0; i < ra.size(); i++) String packageName = ra.get(i).activityInfo.packageName; names.add(packageName); return names; Android 判断程序前后台状态 public static boolean isLauncherRunnig(Context context) boolean result = false; List<String> names = getAllTheLauncher(context); ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> appList = mActivityManager.getRunningAppProcesses(); for (RunningAppProcessInfo running : appList) if (running.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) for (int i = 0; i < names.size(); i++) if (names.get(i).equals(running.processName)) result = true; break; return result; Application Components——应用程序组件 Activating components: intents——激活组件:意图 Shutting down components——关闭组件 The manifest file——清单文件 Intent filters——意图匹配器 Activities and Tasks——活动和任务 Affinities and new tasks——亲和力和新任务 Launch modes——加载样式 Clearing the stack——清除栈 Starting tasks——开始任务 Processes and Threads——进程和线程 Processes——进程 Threads——线程 Remote procedure calls——远程过程调用 Thread-safe methods——线程保险技术的方法 Component Lifecycles——组件生命周期 Activity lifecycle——活动生命周期 Service lifecycle——服务生命周期 Broadcast receiver lifecycle——广播接收器生命周期 Processes and lifecycles——进程和生命周期 Android applications are written in the Java programming language. The compiled Java code — along with any data and resource files required by the application — is bundled by the aapt tool into an Android package, an archive file marked by an .apk suffix. This file is the vehicle for distributing the application and installing it on mobile devices; it's the file users download to their devices. All the code in a single .apk file is considered to be one application.Android应用程序是用Java编程语言编写的。编译好的Java代码——随同所有应用程序要使用的数据和资源文件一起——使用aapt tool打包成一个Android包,即一个后缀为.apk的归档文件。此文件是发布应用程序和在移动设备上安装应用程序的有媒介;它是用户下载到他们设备上的文件。在一个.apk文件中的所有代码被认为是一个应用程序。 In many ways, each Android application lives in its own world:从很多方面来讲,每一个Android程序都运行在自己的空间里: By default, every application runs in its own Linux process. Android starts the process when any of the application's code needs to be executed, and shuts down the process when it's no longer needed and system resources are required by other applications. 默认的,每一个程序运行在其自己的Linux进程中。Android在应用程序中某段代码需要被执行时启动一个进程,不再使用或系统资源被其他应用程序请求时关闭这个进程。 Each process has its own Java virtual machine (VM), so application code runs in isolation from the code of all other applications. 每一个进程都有自己的Java虚拟机(VM),一个应用程序代码运行时同其他程序代码分开,不受任何其他程序的代码的影响。 By default, each application is assigned a unique Linux user ID. Permissions are set so that the application's files are visible only that user, only to the application itself — although there are ways to export them to other applications as well. 默认的,每个应用程序享有一个独自的Linux用户ID。由于权限设定原因,一个应用程序的文件只有本用户(其自己)可见——当然,也有把他们导出给其他应用程序的机制。 It's possible to arrange for two applications to share the same user ID, in which case they will be able to see each other's files. To conserve system resources, applications with the same ID can also arrange to run in the same Linux process, sharing the same VM.可以为两个应用程序安排为使用同一个用户ID,这样,他们的文件相互可见。为为了节省系统资源,拥有相同ID的程序可以作为一个Linux进程运行,分享同一个虚拟机。 Application Components——应用程序组件 A central feature of Android is that one application can make use of elements of other applications (provided those applications permit it). For example, if your application needs to display a scrolling list of images and another application has developed a suitable scroller and made it available to others, you can call upon that scroller to do the work, rather than develop your own. Your application doesn't incorporate the code of the other application or link to it. Rather, it simply starts up that piece of the other application when the need arises.Android的一个中心功能就是应用程序可以使用其他应用程序的元素(在提供元素的应用程序允许的情况下)。例如,如果您的应用程序想要显示一系列带有滑屏效果功能的图片,然后某个应用程序正巧开发出了合适的滑屏的模块,并且同意共享,您就可以调用哪个滑屏模块处理这些图片并显示出来,而不是您自己再去开发一个。您的应用程序并没有包含或链接到了其他的应用程序的代码。但是当请求发出后,您的应用程序确实可以简单的使用其他程序的部分功能。 For this to work, the system must be able to start an application process when any part of it is needed, and instantiate the Java objects for that part. Therefore, unlike applications on most other systems, Android applications don't have a single entry point for everything in the application (no main() function, for example). Rather, they have essentialcomponents that the system can instantiate and run as needed. There are four types of components:为了达成这个过程,系统必须可以再应用程序的某个部分被请求时启动这个程序的进程。然后为那个部分的Java对象创建实例。因此不像其他操作系统上的应用程序那样,Android程序没有一个单一的应用程序入口(例如没有main()函数)。然而他们含有当系统需要时创建实例的实质的组件。组件有四种: Activities——活动 An activity presents a visual user interface for one focused endeavor the user can undertake. For example, an activity might present a list of menu items users can choose from or it might display photographs along with their captions. A text messaging application might have one activity that shows a list of contacts to send messages to, a second activity to write the message to the chosen contact, and other activities to review old messages or change settings. Though they work together to form a cohesive user interface, each activity is independent of the others. Each one is implemented as a subclass of the Activity base class. 活动为需要用户关注和确认的部分显示一个可见的用户界面。例如,活动会显示一个用户可选的菜单项的列表,或是显示带有标签的照片。一个文本信息应用程序可能有一个显示联系人列表来发送信息,又有一个为选定的联系人撰写信息的活动,还有其他回顾旧信息或改变设定的活动。虽然他们整体组成一个相互关联的用户界面,但是每一个活动都是相对独立的。每一个都作为Activity基类的一个子类来实现。 An application might consist of just one activity or, like the text messaging application just mentioned, it may contain several. What the activities are, and how many there are depends, of course, on the application and its design. Typically, one of the activities is marked as the first one that should be presented to the user when the application is launched. Moving from one activity to another is accomplished by having the current activity start the next one. 一个应用程序可能只含有一个活动,或者像上边提到的文本信息程序一样含有几个。有几个活动,这些活动是什么样的取决于您的应用程序是如何设计的。最典型的是将一个活动设计为第一个活动,应用程序被加载时呈现给用户。从一个活动转到另一个是通过在当前活动开始另一个来实现的。 Each activity is given a default window to draw in. Typically, the window fills the screen, but it might be smaller than the screen and float on top of other windows. An activity can also make use of additional windows — for example, a pop-up dialog that calls for a user response in the midst of the activity, or a window that presents users with vital information when they select a particular item on-screen. 每一个活动提供了一个用以绘制的默认窗口。典型的,窗口填满整个屏幕,但是它覆盖在其他窗口上时,会变得略小一点。一个活动可以使用多个窗口——例如,在窗口中央显示一个需要用户回应的弹出窗口,或者在用户选择屏幕上一个特定项目是时为他显示重要信息的窗口。 The visual content of the window is provided by a hierarchy of views — objects derived from the base View class. Each view controls a particular rectangular space within the window. Parent views contain and organize the layout of their children. Leaf views (those at the bottom of the hierarchy) draw in the rectangles they control and respond to user actions directed at that space. Thus, views are where the activity's interaction with the user takes place. For example, a view might display a small image and initiate an action when the user taps that image. Android has a number of ready-made views that you can use — including buttons, text fields, scroll bars, menu items, check boxes, and more. 窗口的可见内容由一组view层提供——从View基类衍生出来的对象。每一个view控制窗口中的一块矩形区域。父view包含并组织子view的布局。页view(底层的view)绘制它们管理的矩形,并响应在“空白”的地方的动作。就是说,view就是活动和用户互动的地方。例如,一个view显示一个小图片,然后初始化用户点击这个小图片后的动作。 Android有很多已做好的view供您选择——包括按钮,文本输入框,滚动条,菜单项,多选列表等。 A view hierarchy is placed within an activity's window by the Activity.setContentView() method. The content view is the View object at the root of the hierarchy. (See the separate User Interface document for more information on views and the hierarchy.) 通过使用Activity.setContentView()方法将一组view层放置在一个活动的窗口中。content view是在层的最底部的View对象。(参见独立User Interface的文档获取更多有关view和层的信息。) Services——服务 A service doesn't have a visual user interface, but rather runs in the background for an indefinite period of time. For example, a service might play background music as the user attends to other matters, or it might fetch data over the network or calculate something and provide the result to activities that need it. Each service extends the Service base class. 服务没有可见的用户界面,但是可以在后台运行任意长的时间。例如,一个服务可以在用户转向其他工作后仍在后台播放音乐,或从网上下载数据,或计算什么然后在活动需要的时候提供给活动。服务是从Service基类继承来的。 A prime example is a media player playing songs from a play list. The player application would probably have one or more activities that allow the user to choose songs and start playing them. However, the music playback itself would not be handled by an activity because users will expect the music to keep playing even after they leave the player and begin something different. To keep the music going, the media player activity could start a service to run in the background. The system would then keep the music playback service running even after the activity that started it leaves the screen. 一个主要的例子就是一个从列表中播放音乐的媒体播放器。播放器程序可能会有一个或几个允许用户选择希望播放的音乐然后显示播放的活动。但是音乐的回放过程本身不会使用一个活动因为用户希望在切出播放器界面做别的事时音乐也能一直放下去。为了保持播放继续,活动可以启动一个服务在后台运行。然后即使启动这个服务的活动退出,音乐播放服务也能继续运行。 It's possible to connect to (bind to) an ongoing service (and start the service if it's not already running). While connected, you can communicate with the service through an interface that the service exposes. For the music service, this interface might allow users to pause, rewind, stop, and restart the playback. 您可以连接到(绑定到)一个正在运行的服务(若果服务没在运行,就会启动这个服务)。连接后,您就可以通过服务暴露出来的接口与其进行通信。对音乐播放服务,这个接口可能允许用户对播放进行暂停、回退、停止、重播等操作。 Like activities and the other components, services run in the main thread of the application process. So that they won't block other components or the user interface, they often spawn another thread for time-consuming tasks (like music playback). See Processes and Threads, later. 像活动和其他组件一样,服务运行在程序进程的主线程中。这样它们就不会阻碍其他组件或是用户界面,它们通常是运行一个子线程来进行实时的任务(例如音乐回放)。过后请参见Processes and Threads。 Broadcast receivers——广播接受器 A broadcast receiver is a component that does nothing but receive and react to broadcast announcements. Many broadcasts originate in system code — for example, announcements that the timezone has changed, that the battery is low, that a picture has been taken, or that the user changed a language preference. Applications can also initiate broadcasts — for example, to let other applications know that some data has been downloaded to the device and is available for them to use. broadcast receiver只是接受并对广播信息做出反应。多数的广播是由系统代码发出的——例如,时区变化的通知、电量低的通知、照了一张照了一张照片 或者用户改变了语言设定。应用程序也可以初始化一条广播——例如让其他应用程序知道有些数据下载完成可以使用难过了。 An application can have any number of broadcast receivers to respond to any announcements it considers important. All receivers extend the BroadcastReceiver base class. 应用程序可以含有任意数量的广播接收器对任意其认为重要的的广播进行响应。所有的广播接收器均由BroadcastReceiver基类衍生 Broadcast receivers do not display a user interface. However, they may start an activity in response to the information they receive, or they may use the BroadcastReceiver to alert the user. Notifications can get the user's attention in various ways — flashing the backlight, vibrating the device, playing a sound, and so on. They typically place a persistent icon in the status bar, which users can open to get the message. 广播接收器不显示用户界面。但是它可以启动一个活动来对接收到的信息进行响应或者是使用一个BroadcastReceiver来通知警告用户。Notification(通知)可以一各种方式引起用户注意——使背景灯闪烁,使设备振动,播放声音等等。通常是在状态条上显示一个不会消失图标,用户可以拉下状态条获得通知。 Content providers——内容提供者 A content provider makes a specific set of the application's data available to other applications. The data can be stored in the file system, in an SQLite database, or in any other manner that makes sense. The content provider extends the ContentProvider base class to implement a standard set of methods that enable other applications to retrieve and store data of the type it controls. However, applications do not call these methods directly. Rather they use aContentResolver object and call its methods instead. A ContentResolver can talk to any content provider; it cooperates with the provider to manage any interprocess communication that's involved. content provider 使程序中特定的数据可被其他程序使用。共享的数据可以存储在文件系统中、SQLite数据库中或其他任何可用的介质。内容提供者由ContentProvider基类派生,实现了一系列的 使其他程序获取和存储其支持的数据格式的 方法。但是应用程序不会直接调用这些方法。而是使用ContentResolver对象,然后调用这个对象的方法。ContentResolver能与任何内容提供者对话,它与提供者合作来管理所有涉及到的进程间通信。 See the separate Content Providers document for more information on using content providers. 获取更多使用内容提供者的详细信息请参见单独的文档——Content Providers。 Whenever there's a request that should be handled by a particular component, Android makes sure that the application process of the component is running, starting it if necessary, and that an appropriate instance of the component is available, creating the instance if necessary.当有应被特殊的组件操作的请求时,Android会确认这个组件是否正在运行,如果未在运行,则启动它,如果正在运行则检测需要的类是否实例化,如未实例化,则为类创建实例。 Activating components: intents——激活别的组件的组件:意图 Content providers are activated when they're targeted by a request from a ContentResolver. The other three components — activities, services, and broadcast receivers — are activated by asynchronous messages called intents. An intent is an Intent object that holds the content of the message. For activities and services, it names the action being requested and specifies the URI of the data to act on, among other things. For example, it might convey a request for an activity to present an image to the user or let the user edit some text. For broadcast receivers, the Intent object names the action being announced. For example, it might announce to interested parties that the camera button has been pressed.内容提供者获得ContentResolver请求的指向后,会被激活。另外的三种组件——活动,服务,广播接收器——是靠异一种叫做intents(意图)的异步消息激活的。意图是 承载了这种异步消息内容的 Intent的对象。对于活动和服务,它主要是为被请求的动作命名,然后指定操作此数据的URI。例如,它可能携带让一个活动为用户展现一张图片或者让用户编辑文本的请求。对广播接收器,Intent对象为将要广播的内容命名。例如,它可能会向有兴趣的接收器发布照相键被按下的广播。 There are separate methods for activiating each type of component:激活不同的组件需使用不同的方法: An activity is launched (or given something new to do) by passing an Intent object to Context.startActivity() or Activity.startActivityForResult(). The responding activity can look at the initial intent that caused it to be launched by calling its getIntent() method. Android calls the activity's onNewIntent() method to pass it any subsequent intents. 向Context.startActivity()或Activity.startActivityForResult()传递Intent对象可以激活(或获得新的任务)一个活动。响应的活动可以通过调用getIntent()方法查看导致自己被激活的最初的意图。Android调用活动的onNewIntent()方法来传递之后的意图。One activity often starts the next one. If it expects a result back from the activity it's starting, it calls startActivityForResult() instead of startActivity(). For example, if it starts an activity that lets the user pick a photo, it might expect to be returned the chosen photo. The result is returned in an Intent object that's passed to the calling activity's onActivityResult() method. 一个活动通常会开启另一个活动。如果前一个活动希望从新启动的活动获得一个运行结果,可以调用startActivityForResult()方法来代替startActivity()方法。例如,如果一个老活动开启了一个让用户照相的新活动,又希望从新活动获得这张照片。新活动的运行结果是通过一个 传递给老活动的onActivityResult()方法的 Intent对象传递的。 A service is started (or new instructions are given to an ongoing service) by passing an Intent object to Context.startService(). Android calls the service's onStart() method and passes it the Intent object. Similarly, an intent can be passed to Context.bindService() to establish an ongoing connection between the calling component and a target service. The service receives the Intent object in an onBind() call. (If the service is not already running, bindService() can optionally start it.) For example, an activity might establish a connection with the music playback service mentioned earlier so that it can provide the user with the means (a user interface) for controlling the playback. The activity would call bindService() to set up that connection, and then call methods defined by the service to affect the playback. 服务通过向Context.startService()传递一个Intent对象启动(或给予新任务)Android调用服务的onStart()方法然后将Intent对象传递给服务。类似的,一个意图可以被传给Context.bindService()来建立呼叫组件和被叫组件的实时连接。调用服务的onBind()来使其接受Intent对象。(如果服务未在运行,您可以选择使用bindService()来启动它。)例如上面提到过的音乐播放服务,活动会希望建立一个与此服务的连接以向用户提供控制播放的功能(例如一个UI界面)。这时活动就会调用bindService()来建立连接,然后调用服务中定义的方法来控制播放。 A later section, Remote procedure calls, has more details about binding to a service. 之后的章节会讲到Remote procedure calls,那有更详细的绑定服务的信息。 An application can initiate a broadcast by passing an Intent object to methods like Context.sendBroadcast(), Context.sendOrderedBroadcast(), and Context.sendStickyBroadcast() in any of their variations. Android delivers the intent to all interested broadcast receivers by calling their onReceive() methods. 程序可以通过向类似 Context.sendBroadcast(),Context.sendOrderedBroadcast(),Context.sendStickyBroadcast(),及它们的变种 的方法传递一个Intent对象来初始化一条广播。 For more on intent messages, see the separate article, Intents and Intent Filters.获得更多关于意图的信息,查看单独的章节Intents and Intent Filters。 Shutting down components——关闭组件 A content provider is active only while it's responding to a request from a ContentResolver. And a broadcast receiver is active only while it's responding to a broadcast message. So there's no need to explicitly shut down these components. 内容提供者只有在响应来自ContentResolver的请求时才会处于激活状态。广播接收器只有在响应广播信息时才处于激活状态。所以没有必要显式地关闭这些组件。 Activities, on the other hand, provide the user interface. They're in a long-running conversation with the user and may remain active, even when idle, as long as the conversation continues. Similarly, services may also remain running for a long time. So Android has methods to shut down activities and services in an orderly way: 活动,相反的,提供了用户界面。他们会与用户长时间地对话,而且可能在整个对话过程,即使处于空闲状态,都保持运行状态。同样的,服务也可能会运行很长时间。因此,Android提供了下列的关闭活动和服务的方法: An activity can be shut down by calling its finish() method. One activity can shut down another activity (one it started with startActivityForResult()) by calling finishActivity(). 活动可以通过调用他的finish()方法进行关闭。一个活动也可以调用finishActivity()关闭另一个活动(但只能是由他调用startActivityForResult()启动的)。 A service can be stopped by calling its stopSelf() method, or by calling Context.stopService(). 一个服务可以通过调用他的stopSelf()方法关闭,或者调用Context.stopService()方法关闭。 Components might also be shut down by the system when they are no longer being used or when Android must reclaim memory for more active components. A later section, Component Lifecycles, discusses this possibility and its ramifications in more detail. 当组件不会再被使用时,或Android必须回收内存以运行其他组件时,系统会自动关闭组件。后续的Component Lifecycles章节中详细讨论了这种可能和其后果。 The manifest file——清单文件 Before Android can start an application component, it must learn that the component exists. Therefore, applications declare their components in a manifest file that's bundled into the Android package, the .apk file that also holds the application's code, files, and resources. Android启动一个新的应用程序组件之前,他必须知道这个组件确实存在。因此,应用程序在清单文件中声明了他的全部组件。清单文件随同应用程序的代码、文件、资源一同打包在了Android包中,即.apk文件中。 The manifest is a structured XML file and is always named AndroidManifest.xml for all applications. It does a number of things in addition to declaring the application's components, such as naming any libraries the application needs to be linked against (besides the default Android library) and identifying any permissions the application expects to be granted. 清单文件是一个结构化的XML文件,同时在所有的应用程序都被命名为AndroidManifest.xml。除了声明应用程序的组件他还有很多功用,例如为所有应用程序需要进行连接的库命名(除了默认的Android库)还有识别所有应用程序期望获得的权限。 But the principal task of the manifest is to inform Android about the application's components. For example, an activity might be declared as follows: 但是清单文件的主要任务还是为Android提供应用程序的组件信息。例如,一个活动可能会像下面这样定义: <?xml version="1.0" encoding="utf-8"?> <manifest . . . > <application . . . > <activity android:name="com.example.project.FreneticActivity" android:icon="@drawable/small_pic.png" android:label="@string/freneticLabel" . . . > </activity> . . . </application> </manifest> The name attribute of the <activity> element names the Activity subclass that implements the activity. The icon and label attributes point to resource files containing an icon and label that can be displayed to users to represent the activity. <activity>元素的name属性命名了实现了activity的Activity子类。icon和label属性指向了 包含被显示给用户来代表这个活动的图标和标题 的文件。 The other components are declared in a similar way — <service> elements for services, <receiver> elements for broadcast receivers, and <provider> elements for content providers. Activities, services, and content providers that are not declared in the manifest are not visible to the system and are consequently never run. However, broadcast receivers can either be declared in the manifest, or they can be created dynamically in code (as BroadcastReceiver objects) and registered with the system by calling Context.registerReceiver().其他的组件的声明方式类似——声明服务用<service>元素,使用<receiver>声明广播接收器,使用<provider>声明内容提供者。没被声明的活动,服务,内容提供者对Android系统是不可见的,因此永远都不会被运行。但是广播接收器可以在清单文件中声明也可在代码中动态的创建(创建BroadcastReceiver对象),然后调用Context.registerReceiver()向系统注册。 For more on how to structure a manifest file for your application, see The AndroidManifest.xml File.更多有关如何为您的应用程序构造一个清单文件,请参见The AndroidManifest.xml File。 Intent filters——意图匹配器 An Intent object can explicitly name a target component. If it does, Android finds that component (based on the declarations in the manifest file) and activates it. But if a target is not explicitly named, Android must locate the best component to respond to the intent. It does so by comparing the Intent object to the intent filters of potential targets. A component's intent filters inform Android of the kinds of intents the component is able to handle. Like other essential information about the component, they're declared in the manifest file. Here's an extension of the previous example that adds two intent filters to the activity:Intent对象会详细地指明一个目标组件。Android会找到那个组件(根据清单文件中的声明)然后激活它。但是如果目标没有被明确指出, Android就必须找到能响应这个意图的最佳组件。Android将Intent对象和可能的目标组件的意图匹配器进行比较来找出这个最佳组件。组件的意图匹配器告诉Android这个组件能操作什么样的意图。就像组件的其他重要信息一样,它们声明于清单文件中。这里是上边那个例子的增扩版,向活动中添加了两个意图匹配器。 <?xml version="1.0" encoding="utf-8"?> <manifest . . . > <application . . . > <activity android:name="com.example.project.FreneticActivity" android:icon="@drawable/small_pic.png" android:label="@string/freneticLabel" . . . > <intent-filter . . . > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter . . . > <action android:name="com.example.project.BOUNCE" /> <data android:mimeType="image/jpeg" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> . . . </application> </manifest> The first filter in the example — the combination of the action "android.intent.action.MAIN" and the category "android.intent.category.LAUNCHER" — is a common one. It marks the activity as one that should be represented in the application launcher, the screen listing applications users can launch on the device. In other words, the activity is the entry point for the application, the initial one users would see when they choose the application in the launcher.例子中的第一个意图匹配器——"android.intent.action.MAIN"动作和"android.intent.category.LAUNCHER目录的结合——是常见的一种。它标明这个活动要在程序菜单上列出,就是要在列出设备上用户可用的应用程序的那一屏。换句话说,这个活动是这个应用程序的入口点,用户从菜单中选择运行这个程序后见到的初始的活动。 The second filter declares an action that the activity can perform on a particular type of data.第二个匹配器声明了这个活动能处理某种形式的数据。 A component can have any number of intent filters, each one declaring a different set of capabilities. If it doesn't have any filters, it can be activated only by intents that explicitly name the component as the target.一个组件可以有任意多个意图匹配器,每一个声明这个组件的一种能力。如果一个组件没有任何意图匹配器,他就只能被明确的指明他作为目标组件的意图激活。 For a broadcast receiver that's created and registered in code, the intent filter is instantiated directly as an IntentFilter object. All other filters are set up in the manifest.对在代码运行时创建并注册的广播接收器,意图匹配器被作为IntentFilter对象直接实例化。 For more on intent filters, see a separate document, Intents and Intent Filters.有关意图匹配器更多信息,参见单独的Intents and Intent Filters文档。 Activities and Tasks——活动和任务 As noted earlier, one activity can start another, including one defined in a different application. Suppose, for example, that you'd like to let users display a street map of some location. There's already an activity that can do that, so all your activity needs to do is put together an Intent object with the required information and pass it to startActivity(). The map viewer will display the map. When the user hits the BACK key, your activity will reappear on screen.上边提到过,一个活动能启动另一个活动,甚至是在别的应用程序中定义的活动。设想,例如,您想让用户显示某地的街区地图。而且已经有了一个可以完成此事的活动,所以您的活动要做的仅仅是将请求信息放进一个Intent对象中,将这个Intent对象传递给startActivity()。地图查看器这个活动就会显示出地图。当用户点击BACK按钮之后,您的活动就会重新出现在屏幕上。 To the user, it will seem as if the map viewer is part of the same application as your activity, even though it's defined in another application and runs in that application's process. Android maintains this user experience by keeping both activities in the same task. Simply put, a task is what the user experiences as an "application." It's a group of related activities, arranged in a stack. The root activity in the stack is the one that began the task — typically, it's an activity the user selected in the application launcher. The activity at the top of the stack is one that's currently running — the one that is the focus for user actions. When one activity starts another, the new activity is pushed on the stack; it becomes the running activity. The previous activity remains in the stack. When the user presses the BACK key, the current activity is popped from the stack, and the previous one resumes as the running activity.对用户来说这个地图查看器就好像您的应用程序中的活动一样,虽然这个地图查看器是定义在其他应用程序中而且运行在那个应用程序的进程中。Android将您的活动和那个借用的活动放在同一个task中,以维持用户体验。简单来讲任务就是用户觉得好像是一个“应用程序”的东西。任务就是以栈的形式组织起来起来的相互关联的一组活动。栈中最底部的是任务的起始活动——一般是用户在滑出的程序列表中选择启动的活动。最顶部的是正在运行的活动——用户正在关注操作的。当一个活动开启另一个时,新启动的活动被压入栈中;并且成为正在运行的活动。旧一个活动还在栈中。当用户按下BACK键后,正在运行的活动被弹出栈,旧一个恢复成为正在运行的活动。 The stack contains objects, so if a task has more than one instance of the same Activity subclass open — multiple map viewers, for example — the stack has a separate entry for each instance. Activities in the stack are never rearranged, only pushed and popped.栈中包含了对象,因此如果一个任务中开启了 同一个Activity子类的 多个对象——例如,多个地图浏览——则栈对每一个实例都有一个分开的入口。栈中的活动不会被重新排序,只会被压入、弹出。 A task is a stack of activities, not a class or an element in the manifest file. So there's no way to set values for a task independently of its activities. Values for the task as a whole are set in the root activity. For example, the next section will talk about the "affinity of a task"; that value is read from the affinity set for the task's root activity.任务是由栈中的活动组成的,而不是清单文件中声明的某个类或元素。所以无法单独为一个任务设定确定的活动的信息。任务的所有信息都是设定在根活动中的。例如,下一个章节会讲到“任务的亲和度”;亲和度信息就是从任务的根活动中获取的。 All the activities in a task move together as a unit. The entire task (the entire activity stack) can be brought to the foreground or sent to the background. Suppose, for instance, that the current task has four activities in its stack — three under the current activity. The user presses the HOME key, goes to the application launcher, and selects a new application (actually, a new task). The current task goes into the background and the root activity for the new task is displayed. Then, after a short period, the user goes back to the home screen and again selects the previous application (the previous task). That task, with all four activities in the stack, comes forward. When the user presses the BACK key, the screen does not display the activity the user just left (the root activity of the previous task). Rather, the activity on the top of the stack is removed and the previous activity in the same task is displayed.任务中的所有活动是作为一个整体运转的。整个任务(一个栈的所有活动)可以被送到前台或推到后台。假设,例如,现在有一个正在运行的任务,栈中有四个活动 ——正在运行的活动下边有三个,这是用户按下了HOME键,回到了应用程序的列表然后运行了一个新的应用程序(事实上,是一个新的任务)。则旧一个任务就被推到了后台,新一个任务的根活动被现实。一段时间过后用户回到了应用程序列表,又选择了旧一个应用程序(旧一个任务)。则旧一个任务的所有栈中的四个活动就都被送到了前台。这时用户如果按下BACK建屏幕不会回到用户刚离开的活动(就是新一个任务的跟活动)。而是旧一个任务的栈顶活动被弹出,下一个活动顶上,并被显示出来。 The behavior just described is the default behavior for activities and tasks. But there are ways to modify almost all aspects of it. The association of activities with tasks, and the behavior of an activity within a task, is controlled by the interaction between flags set in the Intent object that started the activity and attributes set in the activity's <activity> element in the manifest. Both requester and respondent have a say in what happens. 上面描述的过程是活动和任务的默认动作流程。但是那个流程很多方面都是可修改的。活动和任务的组合还有任务中的活动是由 开启活动的Intent对象中设定的控制标 和 清单文件中活动的<activity>元素的属性 共同控制的。 In this regard, the principal Intent flags are: 这种情况下,重要的Intent控制标有: FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP FLAG_ACTIVITY_RESET_TASK_IF_NEEDED FLAG_ACTIVITY_SINGLE_TOP The principal <activity> attributes are: 重要的<activity>属性有: taskAffinity launchMode allowTaskReparenting clearTaskOnLaunch alwaysRetainTaskState finishOnTaskLaunch The following sections describe what some of these flags and attributes do, how they interact, and what considerations should govern their use. 下面的章节描述了这些控制标和属性的作用,如何相连发生作用,在使用过程中的注意事项。 Affinities and new tasks——亲和度和新任务 By default, all the activities in an application have an affinity for each other — that is, there's a preference for them all to belong to the same task. However, an individual affinity can be set for each activity with the taskAffinity attribute of the <activity> element. Activities defined in different applications can share an affinity, or activities defined in the same application can be assigned different affinities. The affinity comes into play in two circumstances: When the Intent object that launches an activity contains the FLAG_ACTIVITY_NEW_TASK flag, and when an activity has its allowTaskReparenting attribute set to "true". 默认的,一个应用程序中的所有活动之间都互有 亲和度——就是他们属于一个任务的优先权。但是,您可以通过每个活动的<activity>元素的taskAffinity属相为某个活动设定单独的亲和度。不同程序中定义的活动可以共享一个亲和度,一个应用程序中的不同活动可以定义不同的亲和度。亲和度在两种情况下有用:一种情况是当激活活动的Intent对象包含了FLAG_ACTIVITY_NEW_TASK控制标,另一种情况是活动将allowTaskReparenting属性设置为了"true"。 The FLAG_ACTIVITY_NEW_TASK flag——FLAG_ACTIVITY_NEW_TASK控制标 As described earlier, a new activity is, by default, launched into the task of the activity that called startActivity(). It's pushed onto the same stack as the caller. However, if the Intent object passed to startActivity() contains theFLAG_ACTIVITY_NEW_TASK flag, the system looks for a different task to house the new activity. Often, as the name of the flag implies, it's a new task. However, it doesn't have to be. If there's already an existing task with the same affinity as the new activity, the activity is launched into that task. If not, it begins a new task. 正如上边提到的,默认情况下,调用startActivity()向任务中添加一个活动。新添加的活动会压入添加他的活动所在栈。但是,如果传递给startActivity()的Intent对象中含有FLAG_ACTIVITY_NEW_TASK控制标,系统会将新活动压入别的栈。通常,就像控制标的字面意思一样,会是一个新的任务。但是,不是一定要压入新的栈。如果已经有了一个亲和度和新活动相同的任务,活动就被压入那个任务的栈中。如果没有,才会新建任务。 The allowTaskReparenting attribute——allowTaskReparenting属性 If an activity has its allowTaskReparenting attribute set to "true", it can move from the task it starts in to the task it has an affinity for when that task comes to the fore. For example, suppose that an activity that reports weather conditions in selected cities is defined as part of a travel application. It has the same affinity as other activities in the same application (the default affinity) and it allows reparenting. One of your activities starts the weather reporter, so it initially belongs to the same task as your activity. However, when the travel application next comes forward, the weather reporter will be reassigned to and displayed with that task. 如果活动将allowTaskReparenting属性设为"true",他可以 在具有亲和度的任务被送到前台时 从开启他的任务移到亲和的任务中。例如,假设有一个已经做好的旅行的应用程序,定义了一个报告选择的城市的天气情况的活动。这个活动和这个应用程序中其他活动有相同亲和度(默认的亲和度),同时允许重定父级。您自己的一个应用程序中的一个活动开启了这个天气报告活动,他初始情况是处于您自己的应用程序的任务中。但是当这个旅行应用程序运行并被切换到前台后,天气报告活动就会被重新连接 到旅行应用程序中,并在那个任务中显示。 If an .apk file contains more than one "application" from the user's point of view, you will probably want to assign different affinities to the activities associated with each of them. 如果一个.apk 文件包含了多个用户看来的“应用程序”,您可能会为和每个“应用程序”相关的活动设定不同的亲和度。 Launch modes——启动模式 There are four different launch modes that can be assigned to an <activity> element's launchMode attribute: 下面是可以被设为<activity>元素launchMode属性的四种启动模式: "standard" (the default mode) ——标准(默认的)"singleTop" "singleTask" "singleInstance" The modes differ from each other on these four points: 每个任务都有以下四方面的特点: Which task will hold the activity that responds to the intent. For the "standard" and "singleTop" modes, it's the task that originated the intent (and called startActivity()) — unless the Intent object contains theFLAG_ACTIVITY_NEW_TASK flag. In that case, a different task is chosen as described in the previous section, Affinities and new tasks. 响应意图的活动会被装入哪个任务。"standard"和"singleTop"模式,是装入发出意图(调用了startActivity())的任务——除非Intent对象含有FLAG_ACTIVITY_NEW_TASK控制标。后种情况流程如前边Affinities and new tasks章节所述。In contrast, the "singleTask" and "singleInstance" modes mark activities that are always at the root of a task. They define a task; they're never launched into another task. 相反的,使用"singleTask"标记和"singleInstance"标记始终为根活动的活动。开启这样的活动会新建一个任务;而不是装入某个正在运行的任务。 Whether there can be multiple instances of the activity. A "standard" or "singleTop" activity can be instantiated many times. They can belong to multiple tasks, and a given task can have multiple instances of the same activity. 是否允许产生多个活动实例。2一个"standard"或"singleTop"活动可以被多次实例化。他们可以属于多个任务,同样的活动可以在一个确定的任务中有多个实例。In contrast, "singleTask" and "singleInstance" activities are limited to just one instance. Since these activities are at the root of a task, this limitation means that there is never more than a single instance of the task on the device at one time.相反的,"singleTask和"singleInstance"的活动只能有一个实例。因为这些活动是任务的根活动,这种限制意味着一个任务在同一时间只能有一个。 Whether the instance can have other activities in its task. A "singleInstance" activity stands alone as the only activity in its task. If it starts another activity, that activity will be launched into a different task regardless of its launch mode — as if FLAG_ACTIVITY_NEW_TASK was in the intent. In all other respects, the "singleInstance" mode is identical to "singleTask". 所在任务中是否允许有其他活动。"singleInstance"活动单独运行在一个任务中。如果他开启另一个活动,新一个活动不论启动模式都会运行在新任务中——就好像用带有FLAG_ACTIVITY_NEW_TASK控制标的Intent对象激活似的。其他方面"singleInstance"与"singleTask"相同。 The other three modes permit multiple activities to belong to the task. A "singleTask" activity will always be the root activity of the task, but it can start other activities that will be assigned to its task. Instances of "standard" and "singleTop" activities can appear anywhere in a stack. 另外三个模式允许任务中存在多个活动。"singleTask"活动将总是任务的根活动,但是由他启动的其他活动会被装入他所在的任务。"standard"和"singleTop"活动能在任务栈中任何任何位置出现。 Whether a new instance of the class will be launched to handle a new intent. For the default "standard" mode, a new instance is created to respond to every new intent. Each instance handles just one intent. For the "singleTop" mode, an existing instance of the class is re-used to handle a new intent if it resides at the top of the activity stack of the target task. If it does not reside at the top, it is not re-used. Instead, a new instance is created for the new intent and pushed on the stack. 获得新意图时是否使用新 类的 实例来操作。对于默认"standard"的模式,每次获得新意图时都会用新的实例响应。每个实例响应一个意图。"singleTop"模式中,如果意图响应类实例存在且在 意图的目标任务 栈的栈顶,那么意图响应类实例将会被重用。如果存在但不再栈顶,则不会被重用。新实例被创建并压入栈顶。For example, suppose a task's activity stack consists of root activity A with activities B, C, and D on top in that order, so the stack is A-B-C-D. An intent arrives for an activity of type D. If D has the default "standard" launch mode, a new instance of the class is launched and the stack becomes A-B-C-D-D. However, if D's launch mode is "singleTop", the existing instance is expected to handle the new intent (since it's at the top of the stack) and the stack remains A-B-C-D. 例如,假设 一个任务的几个活动是 根活动A,活动B,C,D 的顺序,栈内就是A-B-C-D。这时收到一个类D进行响应的意图。如果D是"standard"启动模式,则将创建类的新实例,栈内变成A-B-C-D-D。但是如果D是"singleTop"启动模式,用以响应新意图的实例已经存在(而且已经在栈顶),栈保持A-B-C-D不变。 If, on the other hand, the arriving intent is for an activity of type B, a new instance of B would be launched no matter whether B's mode is "standard" or "singleTop" (since B is not at the top of the stack), so the resulting stack would be A-B-C-D-B. 如果,另一种情况,收到一个类B响应的意图,不管B的启动类型是"standard"还是"singleTop"(因为B不在栈顶),新实例都会被创建,之后的栈会变成A-B-C-D-B。 As noted above, there's never more than one instance of a "singleTask" or "singleInstance" activity, so that instance is expected to handle all new intents. A "singleInstance" activity is always at the top of the stack (since it is the only activity in the task), so it is always in position to handle the intent. However, a "singleTask" activity may or may not have other activities above it in the stack. If it does, it is not in position to handle the intent, and the intent is dropped. (Even though the intent is dropped, its arrival would have caused the task to come to the foreground, where it would remain.) 就像前面已经提到过的,"singleTask"和"singleInstance"活动的类永远不可能会出现多个实例。所以他们的类就会处理所有的收到的意图。"singleInstance"活动永远是任务的栈的最顶活动(因为他是任务中的唯一活动),因此他也总可以处理一个意图。但是在"singleTask"活动栈位之上可能也可能没有另外的活动。如果有,那么他就不能对新到的意图进行处理,这个意图就丢失了。(即使意图已经丢失,意图的收到也会出发任务使其被送到并保持在前台。) When an existing activity is asked to handle a new intent, the Intent object is passed to the activity in an onNewIntent() call. (The intent object that originally started the activity can be retrieved by calling getIntent().) 当一个已有的活动被请求去处理一个新的意图,Intent对象会通过onNewIntent()的调用传递给这个活动。(传递进来的原始的Intent对象可以通过调用getIntent()获取。) Note that when a new instance of an Activity is created to handle a new intent, the user can always press the BACK key to return to the previous state (to the previous activity). But when an existing instance of an Activity handles a new intent, the user cannot press the BACK key to return to what that instance was doing before the new intent arrived. 注意,当创建一个新的Activity类的实例来处理一个新收到的意图时,用户可以按BACK键回到上一个状态(上一个活动)。但是使用一个已有的Activity类实例操作新收到的意图时,用户不能通过按下BACK键回到这个实例在接受到新意图之前的状态。 For more on launch modes, see the description of the <activity> element. 启动模式的更多信息,参见清单文件<activity>元素的描述。 Clearing the stack——清理栈 If the user leaves a task for a long time, the system clears the task of all activities except the root activity. When the user returns to the task again, it's as the user left it, except that only the initial activity is present. The idea is that, after a time, users will likely have abandoned what they were doing before and are returning to the task to begin something new. 如果用户长时间没有使用一个运行着的任务,系统会将任务和其中除根活动以外的所有活动清理掉。当用户再次使用这个任务时,也是用户当初离开的原因,希望只显示第一个活动,即初始状态。我们认为,用户很长时间不用这个任务,就是放弃了先前做出的修改,再次回到这个任务是要重新开始。 That's the default. There are some activity attributes that can be used to control this behavior and modify it: 这是默认的情况,但是您可以更改下列的活动的属性来控制这个机制: The alwaysRetainTaskState attribute——alwaysRetainTaskState属性 If this attribute is set to "true" in the root activity of a task, the default behavior just described does not happen. The task retains all activities in its stack even after a long period. 如果任务的根活动的此项属性设置成了"true",上面描述的默认的流程就不会发生。任务会保持其栈中所有的活动,无论用户有多长时间没有使用。 The clearTaskOnLaunch attribute——clearTaskOnLaunch属性 If this attribute is set to "true" in the root activity of a task, the stack is cleared down to the root activity whenever the user leaves the task and returns to it. In other words, it's the polar opposite of alwaysRetainTaskState. The user always returns to the task in its initial state, even after a momentary absence. 如果任务的根活动的此项属性设置成了"true",用户离开任务后一回来,系统就清除栈中除根活动外的所有活动。换句话说,这是alwaysRetainTaskState的极端反面情况。即使用户只是离开一会,任务也会变回初始的状态。 The finishOnTaskLaunch attribute——finishOnTaskLaunch属性 This attribute is like clearTaskOnLaunch, but it operates on a single activity, not an entire task. And it can cause any activity to go away, including the root activity. When it's set to "true", the activity remains part of the task only for the current session. If the user leaves and then returns to the task, it no longer is present. 这个属性和很相像clearTaskOnLaunch,但是finishOnTaskLaunch针对对单一活动,不是整个任务。而且他能关闭栈中任何活动,包括根活动。当他被设为"true"时,这个活动只是当前属于这个任务,用户离开,再回到这个任务,他就被关闭了。 There's another way to force activities to be removed from the stack. If an Intent object includes the FLAG_ACTIVITY_CLEAR_TOP flag, and the target task already has an instance of the type of activity that should handle the intent in its stack, all activities above that instance are cleared away so that it stands at the top of the stack and can respond to the intent. If the launch mode of the designated activity is "standard", it too will be removed from the stack, and a new instance will be launched to handle the incoming intent. That's because a new instance is always created for a new intent when the launch mode is "standard". 还有一种方法能将活动从栈中强行移除。如果一个Intent对象包含有FLAG_ACTIVITY_CLEAR_TOP控制标,而且目标任务中已经有一个处理此意图的活动的实例,则所有此活动栈位之上的所有活动实例都被移除,然后此活动可以对意图进行响应。如果此活动的启动模式是"standard",那么他自己也会被移除,新活动被建立,启动,来响应这个意图。因为如果启动模式是"standard",则每一个意图都会用一个新的实例进行处理。 FLAG_ACTIVITY_CLEAR_TOP is most often used in conjunction with FLAG_ACTIVITY_NEW_TASK. When used together, these flags are a way of locating an existing activity in another task and putting it in a position where it can respond to the intent. FLAG_ACTIVITY_CLEAR_TOP通常会和FLAG_ACTIVITY_NEW_TASK联合使用。一起使用时,这个组合是 找到另一个任务中的已有活动然后将它转入新的任务中以响应一个意图的 一种方法。 Starting tasks——启动任务 An activity is set up as the entry point for a task by giving it an intent filter with "android.intent.action.MAIN" as the specified action and "android.intent.category.LAUNCHER" as the specified category. (There's an example of this type of filter in the earlier Intent Filters section.) A filter of this kind causes an icon and label for the activity to be displayed in the application launcher, giving users a way both to launch the task and to return to it at any time after it has been launched. 您可以 通过给活动添加一个"android.intent.action.MAIN" 的意图匹配器来指定动作,一个"android.intent.category.LAUNCHER"来指定其显示在应用程序列表中 来将这个活动设置为任务的入口活动。(上边的Intent Filters章节中有这两个类型的意图匹配器的例子。)这样的匹配器会使应用程序在应用程序列表中显示其图标和标题,使用户能运行他或在运行后的任何时候回到他。 This second ability is important: Users must be able to leave a task and then come back to it later. For this reason, the two launch modes that mark activities as always initiating a task, "singleTask" and "singleInstance", should be used only when the activity has a MAIN and LAUNCHER filter. Imagine, for example, what could happen if the filter is missing: An intent launches a "singleTask" activity, initiating a new task, and the user spends some time working in that task. The user then presses the HOME key. The task is now ordered behind and obscured by the home screen. And, because it is not represented in the application launcher, the user has no way to return to it. 第二个功能很重要:用户必须能够在离开这个任务后能再回到当初的状态。由于这个原因,将活动设定为切回后初始化任务的"singleTask"和"singleInstance"启动模式只能用在有MAIN和LAUNCHER匹配器的活动里。设想,例如,如果用在没有这两个匹配器的活动中会出现什么现象:一个意图激活了一个"singleTask"活动,初始化了一个新的任务,用户在这个任务中做了点什么,然后用户按下HOME键。这个活动就被退到后台,并被桌面屏幕掩盖,然后,由于他不在应用程序列表中显示,用户没法再回到那个活动中去了。 A similar difficulty attends the FLAG_ACTIVITY_NEW_TASK flag. If this flag causes an activity to begin a new task and the user presses the HOME key to leave it, there must be some way for the user to navigate back to it again. Some entities (such as the notification manager) always start activities in an external task, never as part of their own, so they always put FLAG_ACTIVITY_NEW_TASK in the intents they pass to startActivity(). If you have an activity that can be invoked by an external entity that might use this flag, take care that the user has a independent way to get back to the task that's started. 相似的难题在使用FLAG_ACTIVITY_NEW_TASK控制标时也会出现。如果这个控制标使活动开启了一个新的任务然后用户按下HOME键离开这个活动,他也没有办法再回来了。有些东西(例如通知管理器)总是在一个新的任务中打开活动,从来不在自己的任务中打开,所以他们总将包含FLAG_ACTIVITY_NEW_TASK的意图传递给startActivity()。所以如果您有一个会被其他东西以这个控制标调用的活动,请注意用户有独立的回到这个活动的方法。 For those cases where you don't want the user to be able to return to an activity, set the <activity> element's finishOnTaskLaunch to "true". See Clearing the stack, earlier. 如果您希望用户离开活动后就不能再回到这个活动,可以将<activity>元素的finishOnTaskLaunch设为"true"。查看上边提到过的Clearing the stack。 Processes and Threads——进程和线程 When the first of an application's components needs to be run, Android starts a Linux process for it with a single thread of execution. By default, all components of the application run in that process and thread. 当应用程序的第一个组件需要运行是,Android为其分配一个Linux进程,这个进程只有一个运行线程。默认的,这个应用程序的所有组件运行在那个进程的线程中。 However, you can arrange for components to run in other processes, and you can spawn additional threads for any process. 但是,您可以将一些组件安排在别的进程中,而且您可以为任何进程添加线程。 Processes——进程 The process where a component runs is controlled by the manifest file. The component elements — <activity>, <service>, <receiver>, and <provider> — each have a process attribute that can specify a process where that component should run. These attributes can be set so that each component runs in its own process, or so that some components share a process while others do not. They can also be set so that components of different applications run in the same process — provided that the applications share the same Linux user ID and are signed by the same authorities. The <application> element also has a process attribute, for setting a default value that applies to all components. 组件运行在那个进程中是由清单文件进行设定的。组件元素——<activity>, <service>,<receiver>和<provider>——都有一个process属性来指定组件应该运行在哪个进程中。您可以设定这个属性,使每个组件运行在自己的进程中,或者让某些组件共享一个进程而其他的不。您甚至可以设定不同的应用程序的组件运行在同一个进程中——这两个不同的应用程序须是由同一作者签名,且分享同一个Linux 用户ID。<application>元素也有一个process属性,来设定对所有组件的默认设定。 All components are instantiated in the main thread of the specified process, and system calls to the component are dispatched from that thread. Separate threads are not created for each instance. Consequently, methods that respond to those calls — methods like View.onKeyDown() that report user actions and the lifecycle notifications discussed later in the Component Lifecycles section — always run in the main thread of the process. This means that no component should perform long or blocking operations (such as networking operations or computation loops) when called by the system, since this will block any other components also in the process. You can spawn separate threads for long operations, as discussed under Threads, next. 所有的组件都是在指定进程的主线程中实例化的,对组件的系统调用也是由主线线程发出的。系统不会为每个实例建立新的线程。所以,对系统调用进行响应的方法,那些像 报告用户操作的 View.onKeyDown()和在后边Component Lifecycles章节提到的指示生命周期 的方法总是运行在进程的主线程中。这就是说当系统对组件进行调用时,组件不应进行长时间的或等待性的操作(例如网络操作或循环运算),因为这样会使同在这个进程中的其他组件运行受阻。您可以像下边Threads中讲的那样,为耗时的操作建立单独的进程。 Android may decide to shut down a process at some point, when memory is low and required by other processes that are more immediately serving the user. Application components running in the process are consequently destroyed. A process is restarted for those components when there's again work for them to do. Android系统可能会在某些时候结束一个进程,当内存不足且被其他对用户来说更紧要的进程请求时。因此,在此进程中的Android组件会被销毁。当这些组件有新的事要做时,系统会为他们重启进程。 When deciding which processes to terminate, Android weighs their relative importance to the user. For example, it more readily shuts down a process with activities that are no longer visible on screen than a process with visible activities. The decision whether to terminate a process, therefore, depends on the state of the components running in that process. Those states are the subject of a later section, Component Lifecycles. Android权衡对用户的重要性关系来决定结束哪个进程。例如,一个不是正在显示的进程比正在显示的进程更容易被结束。是否要结束某个进程是由里边运行的组件的状态决定的。组建的状态是下边Component Lifecycles章节的主题。 Threads——线程 Even though you may confine your application to a single process, there will likely be times when you will need to spawn a thread to do some background work. Since the user interface must always be quick to respond to user actions, the thread that hosts an activity should not also host time-consuming operations like network downloads. Anything that may not be completed quickly should be assigned to a different thread. 即使您将您的应用程序限制在了一个进程中,那您也很可能有需要新建一个线程来进行后台工作的可能。因为用户界面必须总是能快速地响应用户的动作,承载着活动的线程不能同时还承载者像网络下载这种耗时的操作。所有不能立刻完成的操作都应为其单独建立线程。 Threads are created in code using standard Java Thread objects. Android provides a number of convenience classes for managing threads — Looper for running a message loop within a thread, Handler for processing messages, andHandlerThread for setting up a thread with a message loop. 线程在代码中使用标准的Java Thread对象建立。Android提供了很多方便的管理线程的类——使用一个线程运行消息循环可以使用Looper,Handler响应消息,HandlerThread 创建带有消息循环的线程。 Remote procedure calls——远程过程调用 Android has a lightweight mechanism for remote procedure calls (RPCs) — where a method is called locally, but executed remotely (in another process), with any result returned back to the caller. This entails decomposing the method call and all its attendant data to a level the operating system can understand, transmitting it from the local process and address space to the remote process and address space, and reassembling and reenacting the call there. Return values have to be transmitted in the opposite direction. Android provides all the code to do that work, so that you can concentrate on defining and implementing the RPC interface itself. Android 有一个轻量级的远程过程调用的机制(RPCs)——使得方法在本地调用,然后远程执行(在另一个进程中),并将所有结果返回本地。这需要将方法的调用和随之的数据解释成操作系统可以识别的级别,将其从本地进程和地址空间传送到远程端的进程和地址空间中,在远程端重新装配和组织。返回数据传递过程相反。 Android提供了所有完成这些过程的代码,所以您可以将精力集中在RPC接口的定义和实现上。 An RPC interface can include only methods. All methods are executed synchronously (the local method blocks until the remote method finishes), even if there is no return value. RPC接口可以只包含方法。所有的方法都是同步执行的(本地方法会等待远程方法),在没有返回值的情况下也是这样。 In brief, the mechanism works as follows: You'd begin by declaring the RPC interface you want to implement using a simple IDL (interface definition language). From that declaration, the aidl tool generates a Java interface definition that must be made available to both the local and the remote process. It contains two inner class, as shown in the following diagram: 简单来讲,RPC机制是这样的流程:首先,您使用简单的IDL(接口定义语言)对您想要实现的RPC接口进行声明。在生声明中,您需要使用aidl工具生成一个本地和远程端进程都可以使用的Java接口定义。定义中含有两个内部类,下面图标显示了这个结构: The inner classes have all the code needed to administer remote procedure calls for the interface you declared with the IDL. Both inner classes implement the IBinder interface. One of them is used locally and internally by the system; the code you write can ignore it. The other, called Stub, extends the Binder class. In addition to internal code for effectuating the IPC calls, it contains declarations for the methods in the RPC interface you declared. You would subclass Stub to implement those methods, as indicated in the diagram. 内部类中含有所有 用以管理您使用IDL声明的接口的远程过程调用 的代码。两个内部类都实现了IBinder接口。一个被系统本地地,内部地使用;您自己写的代码会忽略掉它们。另一个,叫做Stub,由Binder类派生。不仅含有执行IPC调用的代码,还含有您声明的RPC接口中方法的声明。就像图标中显示的,您应该继承Stub类来实现这些方法。 Typically, the remote process would be managed by a service (because a service can inform the system about the process and its connections to other processes). It would have both the interface file generated by the aidl tool and the Stub subclass implementing the RPC methods. Clients of the service would have only the interface file generated by the aidl tool. 典型的情况是,远程端的进程是由一个服务进行管理的(因为服务能向系统告知 这个进程 还有 这个进程和其他进程间连接的 信息)。您应在aidl工具产生的接口文件和Stub子类中均实现RPC方法。服务器的客户端应该只有aidl工具生成的接口文件。 Here's how a connection between a service and its clients is set up:这是服务器和其客户端建立连接的流程: Clients of the service (on the local side) would implement onServiceConnected() and onServiceDisconnected() methods so they can be notified when a successful connection to the remote service is established, and when it goes away. They would then call bindService() to set up the connection. 服务器的客户端(即本地端)会实现onServiceConnected()和onServiceDisconnected()方法,然后客户端在在远程服务连接建立或丢失时得知这一信息。然后客户端会调用bindService()来对连接进行设定。 The service's onBind() method would be implemented to either accept or reject the connection, depending on the intent it receives (the intent passed to bindService()). If the connection is accepted, it returns an instance of the Stub subclass. 根据接收的意图(意图被传递到bindService())服务的onBind()方法会被实现于接受或拒绝连接。如果连接被接受,方法返回一个Stub子类的实例。 If the service accepts the connection, Android calls the client's onServiceConnected() method and passes it an IBinder object, a proxy for the Stub subclass managed by the service. Through the proxy, the client can make calls on the remote service. 如果服务接受了连接,Android调用客户端的onServiceConnected()方法并且传递给它一个IBinder对象,IBinder对象是由服务管理的Stub类的一个代理。通过这个代理,客户端可以调用远程端上的服务。 This brief description omits some details of the RPC mechanism. For more information, see Designing a Remote Interface Using AIDL and the IBinder class description.这个简要的RPC机制介绍省略了一些细节。更多信息请看Interface Using AIDL和IBinder类的介绍。 Thread-safe methods——线程保险的方法 In a few contexts, the methods you implement may be called from more than one thread, and therefore must be written to be thread-safe.在某些情况下,您实现的方法可能会从多个线程进行调用,因此您的实现必须是线程上讲安全的。 This is primarily true for methods that can be called remotely — as in the RPC mechanism discussed in the previous section. When a call on a method implemented in an IBinder object originates in the same process as the IBinder, the method is executed in the caller's thread. However, when the call originates in another process, the method is executed in a thread chosen from a pool of threads that Android maintains in the same process as the IBinder; it's not executed in the main thread of the process. For example, whereas a service's onBind() method would be called from the main thread of the service's process, methods implemented in the object that onBind() returns (for example, a Stub subclass that implements RPC methods) would be called from threads in the pool. Since services can have more than one client, more than one pool thread can engage the same IBinder method at the same time. IBinder methods must, therefore, be implemented to be thread-safe.这种情况主要出现在能被远程调用的方法中——像在上边讨论的RPC机制。当一个对 IBinder对象中实现的一个方法 进行的调用来自IBinder本身所在线程时,方法就运行在调用者所在线程中。但是,当调用来自另外一个进程时,方法会运行在从 Android为IBinder进程保持的线程池 中选择的一个线程中,而不是运行在另外那个进程的主线程中。例如,服务的onBind()方法的的调用来自服务的进程的主线程,onBind()返回的对象的实现的方法(例如,Stub类实现的RPC方法)会被在池中的线程调用。因为服务可以有很多客户,因此在同一时间可能有多个池中的线程调用了IBinder方法。因此,IBinder的实现对多进程调用必须是安全的。 Similarly, a content provider can receive data requests that originate in other processes. Although the ContentResolver and ContentProvider classes hide the details of how the interprocess communication is managed, ContentProvider methods that respond to those requests — the methods query(), insert(), delete(), update(), and getType() — are called from a pool of threads in the content provider's process, not the main thread of the process. Since these methods may be called from any number of threads at the same time, they too must be implemented to be thread-safe.相似的,内容提供者可以接受来自其他进程的数据请求。尽管ContentResolver和ContentProvider类隐藏了实现管理进程间通信的方法的细节,可是响应这些请求的ContentProvider方法——query(),insert(),delete(),update()和getType()方法——是从内容提供者进程的线程池中调用的,而不是主线程。因此,这些方法可能会在同一时间被多个线程调用,他们的实现对多进程调用也必须是安全的。 Component Lifecycles——组件生命周期 Application components have a lifecycle — a beginning when Android instantiates them to respond to intents through to an end when the instances are destroyed. In between, they may sometimes be active or inactive,or, in the case of activities, visible to the user or invisible. This section discusses the lifecycles of activities, services, and broadcast receivers — including the states that they can be in during their lifetimes, the methods that notify you of transitions between states, and the effect of those states on the possibility that the process hosting them might be terminated and the instances destroyed.应用程序的组建有生命周期——从Android为其实例化以响应意图开始 到 实例被销毁结束。中间这段时间,这些组件可能正在活动,也可能不在,或者对活动来讲是可见或不可见。这一节讨论了 活动、服务、广播接收器的生命周期——包括他们在生命周期内可能的状态 ,状态转换时的通知方式 ,和这些状态对承载组建的进程被结束(同时进程中的实例被销毁)的可能性的影响。 Activity lifecycle——活动的生命周期 An activity has essentially three states:活动本质上有三个状态: It is active or running when it is in the foreground of the screen (at the top of the activity stack for the current task). This is the activity that is the focus for the user's actions. 当他在前台时(在当前任务的活动栈的栈顶)是active或running的(活动的或运行中的)。这种活动是正在关注用户动作的。 It is paused if it has lost focus but is still visible to the user. That is, another activity lies on top of it and that activity either is transparent or doesn't cover the full screen, so some of the paused activity can show through. A paused activity is completely alive (it maintains all state and member information and remains attached to the window manager), but can be killed by the system in extreme low memory situations. 失去用户关注,但是仍然是可见的 是paused(暂停)状态。就是说,另一个透明或不覆盖整个屏幕的活动覆盖在了这个活动之上,因此部分paused的活动的界面还是显示出来的。暂停的活动依然是存活的(他保持着所有的状态和成员信息并且保持着和窗口管理器的联系),但是会在系统运行内存极低的情况下被关闭。 It is stopped if it is completely obscured by another activity. It still retains all state and member information. However, it is no longer visible to the user so its window is hidden and it will often be killed by the system when memory is needed elsewhere. 当一个活动被另一个活动完全覆盖时,就是stopped(停止)状态。停止的活动保留着所有的状态和成员信息。但是,因为他已经处于不可见状态,他的窗口就被隐藏了,而且通常系统会在内存需要用在别的地方时关闭停止的活动。 If an activity is paused or stopped, the system can drop it from memory either by asking it to finish (calling its finish() method), or simply killing its process. When it is displayed again to the user, it must be completely restarted and restored to its previous state.如果一个活动处于暂停或停止状态,系统会通过 勒令其完成工作并推出(调用其finish()方法)或简单的结束其所在进程 来将其从内存中清理掉。当他再次显示给用户是=时,他必须请完全的重新启动并恢复到之前的状态。 As an activity transitions from state to state, it is notified of the change by calls to the following protected methods:活动在上述几种状态间变动时,系统会调用下列的protected(受保护)的方法对其进行通知: void onCreate(Bundle savedInstanceState) void onStart() void onRestart() void onResume() void onPause() void onStop() void onDestroy() All of these methods are hooks that you can override to do appropriate work when the state changes. All activities must implement onCreate() to do the initial setup when the object is first instantiated. Many will also implement onPause()to commit data changes and otherwise prepare to stop interacting with the user.所有这些方法都是挂钩,你可以重写状态变化时的动作。所有活动都必须实现onCreate(),从而在对象第一次实例化后进行初始化设置。许多活动还要实现onPause()来确认数据变化或者准备停止与用户的交互。 Calling into the superclass——调用父类方法 An implementation of any activity lifecycle method should always first call the superclass version. For example:所有的活动的生命周期方法的实现都应首先调用父类的实现。例如: protected void onPause() { super.onPause(); . . . Taken together, these seven methods define the entire lifecycle of an activity. There are three nested loops that you can monitor by implementing them:总的来说,这七种方法确定活动的整个生命周期。实现生命周期后,您可以对生命周期的这几个嵌套循环进行监控: call to onCreate() through to a single final call to onDestroy(). An activity does all its initial setup of "global" state in onCreate(), and releases all remaining resources inonDestroy(). For example, if it has a thread running in the background to download data from the network, it may create that thread in onCreate() and then stop the thread inonDestroy(). 从调用onCreate()一直到结束性的onDestroy()的调用。活动在onCreate()中进行所有“全局”状态的初始化,后释放所有占用的资源例如,如果一个活动有一个后台的从网络上下载数据的线程,活动可能会使用onCreate()创建线程,使用onDestroy()停止线程。 The visible lifetime of an activity happens between a call to onStart() until a corresponding call to onStop(). During this time, the user can see the activity on-screen, though it may not be in the foreground and interacting with the user. Between these two methods, you can maintain resources that are needed to show the activity to the user. For example, you can register a BroadcastReceiver in onStart() to monitor for changes that impact your UI, and unregister it in onStop() when the user can no longer see what you are displaying. The onStart() and onStop() methods can be called multiple times, as the activity alternates between being visible and hidden to the user. 活动可见的时间是onStart()的调用到相应的onStop()的调用之间的时间。这段时间用户可以在屏幕上看到这个活动,即便这个活动可能不在前台,没在和用户进行交互。在这两个方法的调用之间的时间,您可以保持将活动展现给用户的资源。例如,您可以在onStart()中注册一个BroadcastReceiver来监控UI的变化,然后在用户不能再看到您要显示的后在onStop()中注销它。活动在可见和隐藏间切换过程中,onStart()和onStop()会被调用多次。 The foreground lifetime of an activity happens between a call to onResume() until a corresponding call to onPause(). During this time, the activity is in front of all other activities on screen and is interacting with the user. An activity can frequently transition between the resumed and paused states — for example, onPause() is called when the device goes to sleep or when a new activity is started, onResume() is called when an activity result or a new intent is delivered. Therefore, the code in these two methods should be fairly lightweight. 活动的前台时间是onResume()和对应的onPause()的调用之间的时间。这段时间中,活动处在其他活动之上,并和用户进行交互。活动可以在暂停和继续之间多次切换——例如,当设备休眠或启动了新的活动,onPause()会被调用,收到新启动活动返回结果或旧活动收到新意图时,onResume()会被调用。所以这两个方法中的代码应绝对轻量。 The following diagram illustrates these loops and the paths an activity may take between states. The colored ovals are major states the activity can be in. The square rectangles represent the callback methods you can implement to perform operations when the activity transitions between states.下面的图标演示了这三个循环还有活动在状态间转换的步骤。彩色的椭圆是活动可能处于的状态。直角矩形是您可以实现的 在状态改变时执行操作的 回调方法。 The following table describes each of these methods in more detail and locates it within the activity's overall lifecycle:下面的表格描述了这些方法的详情,和在整个活动生命周期中的位置: Method——方法 Description——详情 Killable?——是否可结束? Next——下一步 onCreate() Called when the activity is first created. This is where you should do all of your normal static set up — create views, bind data to lists, and so on. This method is passed a Bundle object containing the activity's previous state, if that state was captured (see Saving Activity State, later).Always followed by onStart().活动启动时调用。在这个方法里您应完成所有静态的常规的设定——建立view、将数据绑定到列表等等。这个方法调用时如果有先前状态可用,会接受到一个包含这个活动之前的状态的Bundle对象。(参见后面的Saving Activity State)。下一步总是onStart()。 onStart() onRestart() Called after the activity has been stopped, just prior to it being started again.活动被停止之后,重新启动之前被调用。Always followed by onStart()下一步总是onStart() onStart() onStart() Called just before the activity becomes visible to the user.Followed by onResume() if the activity comes to the foreground, or onStop() if it becomes hidden.活动对用户可见之前被调用。如果活动切到前台,下一步是调用onResume();如果活动被隐藏,下一步是调用onStop()。 onResume() oronStop()onResume()或是onStop() onResume() Called just before the activity starts interacting with the user. At this point the activity is at the top of the activity stack, with user input going to it.Always followed by onPause().在活动和用户交互之前被调用。这是活动在栈顶,响应用户的输入。下一步总是 onPause()。 onPause() onPause() Called when the system is about to start resuming another activity. This method is typically used to commit unsaved changes to persistent data, stop animations and other things that may be consuming CPU, and so on. It should do whatever it does very quickly, because the next activity will not be resumed until it returns.系统要开始继续其他活动时被调用。这个方法典型的用法是将为保存的数据存入稳定存储,停止动画或其他占用CPU的动作等等。这个方法中动作应尽快完成,因为完成后,下一个活动才会开始。Followed either by onResume() if the activity returns back to the front, or by onStop() if it becomes invisible to the user.如果活动之后回到前台,下一步是onResume();如果他对用户不再可见,下一步是onStop()。 Yes可以 onResume() oronStop() onStop() Called when the activity is no longer visible to the user. This may happen because it is being destroyed, or because another activity (either an existing one or a new one) has been resumed and is covering it.活动对用户不再可见时被调用。这种情况一般发生在活动被销毁时或者另一个活动(已存在的或新启动的)被继续,并且覆盖了这个活动。Followed either by onRestart() if the activity is coming back to interact with the user, or by onDestroy() if this activity is going away.如果活动回到前台进行与用户的交互,下一步是onRestart();如果活动将被销毁,下一步是onDestroy()。 Yes可以 onRestart() oronDestroy() onDestroy() Called before the activity is destroyed. This is the final call that the activity will receive. It could be called either because the activity is finishing (someone called finish() on it), or because the system is temporarily destroying this instance of the activity to save space. You can distinguish between these two scenarios with the isFinishing() method.活动被销毁之前被调用。这是活动接到的最后一个调用。当活动完成之后(有些人调用finish())或系统为了节约空间将这个实例暂时销毁 时会被调用。您应该可以看出和调用isFinishing()方法之间的区别。 Yes可以 nothing无 Note the Killable column in the table above. It indicates whether or not the system can kill the process hosting the activity at any time after the method returns, without executing another line of the activity's code. Three methods (onPause(), onStop(), and onDestroy()) are marked "Yes." Because onPause() is the first of the three, it's the only one that's guaranteed to be called before the process is killed — onStop() and onDestroy() may not be. Therefore, you should use onPause() to write any persistent data (such as user edits) to storage.注意上边的表格中Killable?——是否可结束?一栏。它表示了系统会不会 在方法动作结束后不再执行活动的代码就 结束承载着这个活动的进程。有三个方法(onPause(),onStop()和onDestroy())标志着"Yes."。因为onPause()是这三个方法中第一个,所以他是唯一一个被允许在进程被结束之前调用的方法——onStop()和onDestroy()则可能不行。所以,将数据(例如用户修改信息)存入稳定存储的过程应在onPause()中完成。 Methods that are marked "No" in the Killable column protect the process hosting the activity from being killed from the moment they are called. Thus an activity is in a killable state, for example, from the time onPause() returns to the timeonResume() is called. It will not again be killable until onPause() again returns.Killable?——是否可结束?一栏标注着“No”的方法保护了其所在进程在这个方法调用之时起不被结束。就是说,例如在onPause()方法完成之后到onResume()被调用之前这段时间之内,进程是可能被结束的。然后到下一次onPause()完成之前,进程是不可结束的。 As noted in a later section, Processes and lifecycle, an activity that's not technically "killable" by this definition might still be killed by the system — but that would happen only in extreme and dire circumstances when there is no other recourse.像下边Processes and lifecycle章节提到的,上边定义的技术上讲不会被结束的活动还是有可能被系统结束的——但只是在极端的没有一点其他资源时才会发生。 Saving activity state——保存活动状态 When the system, rather than the user, shuts down an activity to conserve memory, the user may expect to return to the activity and find it in its previous state.当系统,而不是用户,为了释放内存而关闭这个活动时,用户当然希望再次进入这个活动时状态还是之前的状态。 To capture that state before the activity is killed, you can implement an onSaveInstanceState() method for the activity. Android calls this method before making the activity vulnerable to being destroyed — that is, before onPause() is called. It passes the method a Bundle object where you can record the dynamic state of the activity as name-value pairs. When the activity is again started, the Bundle is passed both to onCreate() and to a method that's called afteronStart(), onRestoreInstanceState(), so that either or both of them can recreate the captured state.截获活动被结束之前的状态,您需要实现活动的onSaveInstanceState()方法。Android会在使这个活动实例接近销毁条件之前调用这个方法——就是说,onPause()调用之前。Android传递给这个方法一个Bundle对象,从而您可以以 名称-值 对的形式记录这个活动的动态状态。当这个活动再次开启之时,Bundle对象会传递给 onCreate() 和 onStart()之后调用的一个方法——onRestoreInstanceState() ,然后,您可以用这两个中任一个重建 处于截获的状态的 活动。 Unlike onPause() and the other methods discussed earlier, onSaveInstanceState() and onRestoreInstanceState() are not lifecycle methods. They are not always called. For example, Android calls onSaveInstanceState() before the activity becomes vulnerable to being destroyed by the system, but does not bother calling it when the instance is actually being destroyed by a user action (such as pressing the BACK key). In that case, the user won't expect to return to the activity, so there's no reason to save its state.不像onPause()和前面讲的其他方法,onSaveInstanceState()和onRestoreInstanceState()不是生命周期方法。他们不是总会被调用。例如,Android在活动合乎有可能被系统销毁的条件的时候会调用onSaveInstanceState(),但是在用户发出动作(例如按下BACK键)将实例实际销毁时不会调用。但在这种情况下,用户也没有希望回到这个活动,因此也没有保存状态的必要。 Because onSaveInstanceState() is not always called, you should use it only to record the transient state of the activity, not to store persistent data. Use onPause() for that purpose instead. 因为onSaveInstanceState()不是一定会被调用,因此您只能用它保存过渡状态,而不能用来保存稳定数据。如要保存稳定数据,请使用onPause()。 Coordinating activities——协调活动 When one activity starts another, they both experience lifecycle transitions. One pauses and may stop, while the other starts up. On occasion, you may need to coordinate these activities, one with the other. 当一个活动开启了另一个活动时,这两个活动都经历了生命周期的变化。就一个活动暂停或停止,新一个活动开启。有些情况下,您可能需要协调这些活动。 The order of lifecycle callbacks is well defined, particularly when the two activities are in the same process: 生命周期回调方法的顺序已经定义好,特别是当这两个活动在同一个进程中: The current activity's onPause() method is called.——当前的活动的onPause()方法被调用·。 Next, the starting activity's onCreate(), onStart(), and onResume() methods are called in sequence.——然后,新一个活动的onCreate()、onStart()和onResume()方法依次被调用。 Then, if the starting activity is no longer visible on screen, its onStop() method is called.——然后,如果旧一个活动在屏幕上不再可见,他的onStop()方法会被调用。 Service lifecycle——服务的生命周期 A service can be used in two ways: 服务可用于以下两种方式: It can be started and allowed to run until someone stops it or it stops itself. In this mode, it's started by calling Context.startService() and stopped by calling Context.stopService(). It can stop itself by callingService.stopSelf() or Service.stopSelfResult(). Only one stopService() call is needed to stop the service, no matter how many times startService() was called.——他可以启动并运行指导什么将它停止或她自己停止。在这种模式中,他通过调用Context.startService()开启,通过调用Context.stopService()停止。服务能通过调用Service.stopSelf()或Service.stopSelfResult()停止自己。停止一个服务只需一个stopService()的调用,不管用了多少次startService()。 It can be operated programmatically using an interface that it defines and exports. Clients establish a connection to the Service object and use that connection to call into the service. The connection is established by callingContext.bindService(), and is closed by calling Context.unbindService(). Multiple clients can bind to the same service. If the service has not already been launched, bindService() can optionally launch it. ——对服务可以使用他定义和导出的接口进行 编程性的操作。客户端建立一个到Service对象的连接,然后使用这个连接对服务进行请求。连接使用Context.bindService()建立,使用Context.unbindService()断开。多个客户端可以绑定到一个服务。如果服务还未被启动,可用bindService()启动它。 The two modes are not entirely separate. You can bind to a service that was started with startService(). For example, a background music service could be started by calling startService() with an Intent object that identifies the music to play. Only later, possibly when the user wants to exercise some control over the player or get information about the current song, would an activity establish a connection to the service by calling bindService(). In cases like this,stopService() will not actually stop the service until the last binding is closed. 这两个方式不是完全分开的。您可以绑定到一个使用startService()启动的服务上。例如,一个后台的音乐播放服务可以调用startService()并传递一个包含识别要播放的音乐的意图来启动。片刻后,当 用户想对播放器进行控制或获取正在播放的音乐的信息 时,您可能就要需要一个活动调用bindService()来建立到这个服务的连接。像这个情况,stopService()不会真的停止服务,直到最后一个绑定解开。 Like an activity, a service has lifecycle methods that you can implement to monitor changes in its state. But they are fewer than the activity methods — only three — and they are public, not protected: 像活动一样,服务也有您可以对其进行实现以监视服务状态的生命周期方法。但是比活动的少——只有三个——而且是公共的,而不是受保护的: void onCreate() void onStart(Intent intent) void onDestroy() By implementing these methods, you can monitor two nested loops of the service's lifecycle: 实现后,您可以监控服务的这两个生命周期嵌套循环: The entire lifetime of a service happens between the time onCreate() is called and the time onDestroy() returns. Like an activity, a service does its initial setup in onCreate(), and releases all remaining resources in onDestroy(). For example, a music playback service could create the thread where the music will be played in onCreate(), and then stop the thread in onDestroy().——服务的整个存在时间是从onCreate()被调用到onDestroy()完成。像活动一样,服务在onCreate()被调用时进行初始化设置,在onDestroy()中完成占有的资源的释放。例如,音乐播放服务会在onCreate()中为音乐的播放建立新的线程,然后在onDestroy()中结束线程。 The active lifetime of a service begins with a call to onStart(). This method is handed the Intent object that was passed to startService(). The music service would open the Intent to discover which music to play, and begin the playback.——服务的动作时间是从onStart()的调用开始的。传递给startService()的Intent对象又被传递给这个方法。音乐播放服务会打开这个意图找到要播放哪个歌曲,然后开始播放。 There's no equivalent callback for when the service stops — no onStop() method. 服务没有像活动的停止时的回调方法——就是没有onStop()方法。 The onCreate() and onDestroy() methods are called for all services, whether they're started by Context.startService() or Context.bindService(). However, onStart() is called only for services started by startService(). 无论服务是否由Context.startService()或Context.bindService()启动,onCreate()和onDestroy()方法都会被调用。但是,系统只会为由startService()启动的服务调用onStart()。 If a service permits others to bind to it, there are additional callback methods for it to implement: 如果服务允许别人绑定到他,您还需要实现以下几个回调方法: IBinder onBind(Intent intent) boolean onUnbind(Intent intent) void onRebind(Intent intent) The onBind() callback is passed the Intent object that was passed to bindService and onUnbind() is handed the intent that was passed to unbindService(). If the service permits the binding, onBind() returns the communications channel that clients use to interact with the service. The onUnbind() method can ask for onRebind() to be called if a new client connects to the service. onBind()方法获得传递给bindService的Intent对象,onUnbind()方法获得传递unbindService()给的Intent对象如果服务允许绑定,onBind()返回客户端用以和服务交互的信道。onUnbind()方法可以在新的客户连接到服务时让系统调用onRebind()。 The following diagram illustrates the callback methods for a service. Although, it separates services that are created via startService from those created by bindService(), keep in mind that any service, no matter how it's started, can potentially allow clients to bind to it, so any service may receive onBind() and onUnbind() calls. 下面的图表演示了服务的回调方法。虽然,使用startService和bindService()开启的服务稍有不同,但是请记住,所有服务,无论是怎么启动的,一般都会允许服务绑定到他,因此所有的服务都有可能收到onBind()和onUnbind()调用。 Broadcast receiver lifecycle——广播接收器生命周期 A broadcast receiver has single callback method: 广播接收器只有一个回调方法: void onReceive(Context curContext, Intent broadcastMsg) When a broadcast message arrives for the receiver, Android calls its onReceive() method and passes it the Intent object containing the message. The broadcast receiver is considered to be active only while it is executing this method. When onReceive() returns, it is inactive. 广播消息到达接收器时,Android 调用他的onReceive()方法并且将一个包含有广播信息的Intent对象传递给这个方法。我们认为广播接收器只有在执行这个方法时是被激活的。onReceive()方法完成之后,他就不再活动。 A process with an active broadcast receiver is protected from being killed. But a process with only inactive components can be killed by the system at any time, when the memory it consumes is needed by other processes.一个承载着激活的广播接收者的进程是不会被结束的。但是只有不活动的接收者组件的进程可能被系统 在别的进程需要他所占用的内存时 随时结束。 This presents a problem when the response to a broadcast message is time consuming and, therefore, something that should be done in a separate thread, away from the main thread where other components of the user interface run. IfonReceive() spawns the thread and then returns, the entire process, including the new thread, is judged to be inactive (unless other application components are active in the process), putting it in jeopardy of being killed. The solution to this problem is for onReceive() to start a service and let the service do the job, so the system knows that there is still active work being done in the process.当对广播信息的响应是一个耗时的事,然后,因此应该单独给他一个线程运行,而不是在其他组件所在的与用户交互的线程中时,问题就出现了。如果创建了新的线程然后在新线程中运行,整个进程,包括新线程 就都被认为是不活动的了(除非进程中有其他活动的应用程序组件),而且有被结束的危险。解决的方法是,让启动一个服务,然后让服务做这个耗时的事,然后系统就知道了进程中还有活动的正在做的事。 The next section has more on the vulnerability of processes to being killed.下一节讲了更多有关进程被结束的容易程度。 Processes and lifecycles——进程和生命周期 The Android system tries to maintain an application process for as long as possible, but eventually it will need to remove old processes when memory runs low. To determine which processes to keep and which to kill, Android places each process into an "importance hierarchy" based on the components running in it and the state of those components. Processes with the lowest importance are eliminated first, then those with the next lowest, and so on. There are five levels in the hierarchy. The following list presents them in order of importance:Android系统试图将应用程序的进程保持最长的时间,但是最终当内存不足时,还是需要将不用的进程结束掉。为了判断哪些进程该结束,哪些不该, Android将每个进程按照当中运行的组件和组件的状态 排放到一个“重要程度排行表”中。最不重要的最先结束,然后是次不重要的,依此类推。“重要程度排行表”中有五层重要程度。下面的表按重要程度顺序列出了他们: A foreground process is one that is required for what the user is currently doing. A process is considered to be in the foreground if any of the following conditions hold:——前台进程是需要获取用户正在做得事情的进程。进程有下列情况的,即认定为前台进程: It is running an activity that the user is interacting with (the Activity object's onResume() method has been called).——正在运行一个用户正与之交互的活动(即活动的onResume()方法被调用了) It hosts a service that's bound to the activity that the user is interacting with.——承载了一个 用户正在与之交互的活动所属的 服务。 It has a Service object that's executing one of its lifecycle callbacks (onCreate(), onStart(), or onDestroy()).——承载着一个正在执行其生命周期回调方法(onCreate(),onStart()或onDestroy())的Service对象。 It has a BroadcastReceiver object that's executing its onReceive() method.——承载了一个正在运行其onReceive()方法的BroadcastReceiver对象。 Only a few foreground processes will exist at any given time. They are killed only as a last resort — if memory is so low that they cannot all continue to run. Generally, at that point, the device has reached a memory paging state, so killing some foreground processes is required to keep the user interface responsive.——任一时刻只会有很少的前台进程存在。结束他们是没有办法的办法——只有当内存不足到他们都没法运行了。通常情况下,到了这种状况,设备就到了内存分页状态,结束一些前台进程是为了保持用户界面的响应。 A visible process is one that doesn't have any foreground components, but still can affect what the user sees on screen. A process is considered to be visible if either of the following conditions holds:——可见进程是没有任何前台组件,但是依旧可以被用户看见的进程。进程有下列情况之一的,即认定为前台进程: It hosts an activity that is not in the foreground, but is still visible to the user (its onPause() method has been called). This may occur, for example, if the foreground activity is a dialog that allows the previous activity to be seen behind it.——承载着不在前台的活动,但是活动依旧能被用户看到(活动的onPause()方法被调用了)。例如当前台活动是一个没有完全盖住旧一个活动对话框时这种情况就可能发生。 It hosts a service that's bound to a visible activity.——承载了一个属于可见活动的服务。 A visible process is considered extremely important and will not be killed unless doing so is required to keep all foreground processes running.——可见进程也是极为重要的,除非结束可见进程是为了保持前台进程的运行,否则可见进程是不会被结束的。 A service process is one that is running a service that has been started with the startService() method and that does not fall into either of the two higher categories. Although service processes are not directly tied to anything the user sees, they are generally doing things that the user cares about (such as playing an mp3 in the background or downloading data on the network), so the system keeps them running unless there's not enough memory to retain them along with all foreground and visible processes.——服务进程是 运行着一个由startService()方法启动的并且不在前两种所述情况中的服务的 进程。虽然服务进程不与任何用户可见部分直接相连,但他们通常是在做一些用户关心的事(例如在后台播放音乐或从网上下载数据),所以除非内存不足以盛放前台和可见进程,系统是不会结束服务进程的。 A background process is one holding an activity that's not currently visible to the user (the Activity object's onStop() method has been called). These processes have no direct impact on the user experience, and can be killed at any time to reclaim memory for a foreground, visible, or service process. Usually there are many background processes running, so they are kept in an LRU (least recently used) list to ensure that the process with the activity that was most recently seen by the user is the last to be killed. If an activity implements its lifecycle methods correctly, and captures its current state, killing its process will not have a deleterious effect on the user experience.——后台进程是承载目前用户不可见的活动(Activity的onStop()方法被调用后)的进程。后台进程不直接影响用户体验,而且随时可以结束以回收供前台进程、可见进程和服务进程使用。通常同一时间会有很多正在运行的后台进程,因此他们被存放在LRU(最近使用过的)列表中以保证最后含有用户最后使用的活动的进程是最后被结束的。如果活动正确地实现了其生命周期方法截获了当前状态,那么结束他所在进程不会对用户体验有坏的影响。 An empty process is one that doesn't hold any active application components. The only reason to keep such a process around is as a cache to improve startup time the next time a component needs to run in it. The system often kills these processes in order to balance overall system resources between process caches and the underlying kernel caches.——空进程是不包含任何活动的进程。保留空进程的唯一原因是作为缓存以提升组件下次使需要用他的开始速度。系统经常会结束这样的进程以平衡 在进程缓存和底层内核缓存之间的 整体系统资源。 Android ranks a process at the highest level it can, based upon the importance of the components currently active in the process. For example, if a process hosts a service and a visible activity, the process will be ranked as a visible process, not a service process.Android依据在进程中正在活动的组件的重要程度 将进程排在他能处在的最高级。例如,如果一个进程承载了一个服务和一个可见进程,那么此进程就被排为可见进程,而不是服务进程。 In addition, a process's ranking may be increased because other processes are dependent on it. A process that is serving another process can never be ranked lower than the process it is serving. For example, if a content provider in process A is serving a client in process B, or if a service in process A is bound to a component in process B, process A will always be considered at least as important as process B.另外,如果有其他进程依赖于此进程,那此进程的排位会上升。一个正服务于其他进程 的进程的排位不会低于他服务于的进程的排位。例如,进程A中的一个内容提供者正在为一个进程B中的客户服务,或者如果进程A中的服务属于进程B的一个组件,那么认为进程A至少与B重要性相同。 Because a process running a service is ranked higher than one with background activities, an activity that initiates a long-running operation might do well to start a service for that operation, rather than simply spawn a thread — particularly if the operation will likely outlast the activity. Examples of this are playing music in the background and uploading a picture taken by the camera to a web site. Using a service guarantees that the operation will have at least "service process" priority, regardless of what happens to the activity. As noted in the Broadcast receiver lifecycle section earlier, this is the same reason that broadcast receivers should employ services rather than simply put time-consuming operations in a thread.因为运行着服务的进程排位比运行着后台活动的进程高,因此相比于只是简单的新建一个线程进行耗时的操作——特别是进行可能会超过活动生命时间的操作,新建一个服务进行那项操作使您更好的选择。例如在后台播放音乐和拍照后将照片上传到网上。使用服务确保了操作会持续“服务进程”那么长的时间,不管活动发生了什么。就像上边Broadcast receiver lifecycle章节提到的那样,这也是广播服务器应该借助一个服务进行耗时的操作,而不是将其简单地放到新的线程中。 From: developer.android.com/guide/components/fundamentals.html www.cnitblog.com/seedshopezhong/archive/2010/03/29/64914.html
本类的常量静态定义,首先为字体类型(typeface)名称 TypefaceDEFAULTTypeface DEFAULT_BOLDTypeface MONOSPACETypefaceSANS_SERIFTypeface SERIF 字体风格(style)名称 intBOLDint BOLD_ITALICint ITALICint NORMAL 设置TextView的字体可以通过TextView中的setTypeface方法来指定一个Typeface对象,因为Android的字体类比较简单,我们列出所有成员方法: staticTypeface create(Typeface family, int style)//静态方法,参数一为字体类型这里是Typeface的静态定义,如宋体,参数二风格,如粗体,斜体 staticTypeface create(String familyName, int style)//静态方法,参数一为字体名的字符串,参数二为风格同上,这里我们推荐使用上面的方法。 staticTypeface createFromAsset(AssetManager mgr, String path)//静态方法,参数一为AssetManager对象,主要用于从APK的assets文件夹中取出字体,参数二为相对于Android工程下 的assets文件夹中的外挂字体文件的路径。 staticTypeface createFromFile(File path)//静态方法,从文件系统构造一个字体,这里参数可以是sdcard中的某个字体文件 staticTypeface createFromFile(String path) //静态方法,从指定路径中构造字体 staticTypeface defaultFromStyle(int style) //静态方法,返回默认的字体风格 intgetStyle() //获取当前字体风格 finalboolean isBold() //判断当前是否为粗体 finalboolean isItalic() //判断当前风格是否为斜体 Android字体工作原理 android字体由android 2D图形引擎skia实现,并在Zygote的Preloading classes中对系统字体进行load。相关文件有:skTypeface.cpp和skFontHost_android.cpp,其中后者是skia针对android平台字体实现的port。 主要的变量有: struct FontInitRec {const char* fFileName;const char* const* fNames; // null-terminated list};struct FamilyRec {FamilyRec* fNext;SkTypeface* fFaces[5];};uint32_t gFallbackFonts[SK_ARRAY_COUNT(gSystemFonts)+1]; load_system_fonts()@skFontHost_android.cpp load系统中所有的字体并给每种字体分配唯一的ID,并将字体分为两种:FamilyFonts和FallbackFonts,skPaint通过应用程序设置的字体(Typeface)所对应的ID最终实现字符的显示。 替换Android默认的汉字字体 在android系统中,DroidSans是默认字体,只包含西方字符,应用程序默认情况下都会调用它, 而DroidSansFallback包含了东亚字符,当需要显示的字符在DroidSans字体中不存在(如:汉字)时,即没有对应编码的字符时,系统会到DroidSansFallback中去找相应编码的字符,如果找到,则使用DroidSansFallback字体来显示它,如果仍找不到该编码对应的字符,则无法在屏幕上显示该字符。更换默认中文字体的步骤为: 1、将幼圆字体库youyuan.ttf重命名为DroidSansFallback.ttf,覆盖Android源码中frameworks/base/data/fonts目录下的DroidSansFallback.ttf文件 2、重新编译Android系统 3、编译SDK。生成的SDK中,android默认的中文字体已更换为幼圆字体。该方法的不足是删除了Android系统原来的中文字体 为android系统添加一种默认字体 在android系统中,默认的中文字体只有一种:DroidSansFallback.ttf,如果想在android应用程序中随意设置想要的中文字体,除了在应用程序中通过assets目录引入字体文件外,还可以通过增加android默认字体的方式来实现。 添加步骤大致如下: 1、在frameworks/base/data/fonts目录下添加字体文件,例如Driod-kaishu.ttf; 2、在skia中增加楷书这一字体,需要修改的文件主要有skFontHost.cpp、skTypeface.cpp、Typeface.java等; 3、在java层添加楷书字体相关API,需要修改的文件主要有typeface.java和textview.java; 4、编译SDK 5、将新生成的sdk导入eclipse,在eclipse中即可通过setTypeface(Typeface.KAISHU)和android:typeface=(“kaishu”)两种方式设置自己添加的字体之所以会说到这几个特殊权限,是因为fastboot这个命令好像有点抽风,给人的感觉就是有时能用有时不能用,执行fastboot devices的错误提示: no permissions 为了找到问题的原因,就思考了一下。 这种问题一般主要发生在以下2种场景下: 1、复制并使用了新的fastboot可执行文件 2、改变了fastboot可执行文件的环境变量 为什么以上2种场景会产生这样的错误? which fastboot ll fastboot 执行这两条命令之后就可见端倪,原来由于复制或者使用了别人复制的fastboot,导致fastboot所属的用户和群组发生了改变,从而让SUID和SGID失效,因为原来fastboot所属的用户和群组都是root。 接下来我们就简单说了解一下SUID和SGID的失效导致no permissions背后的知识点。 Set UID,它会出现在文件拥有者权限的执行位上,只对二进制程序有效,执行者对于程序需要有x权限,在程序运行过程中,执行者拥有程序拥有者的权限。 例如:普通用户执行fastboot和passwd命令。 这里再多说一句,很多Android上的root工具就是通过一些漏洞(比如缓冲区溢出等)来攻击一些带有SUID或SGID权限,并且所属的用户或用户组是root的二进制程序来临时获取root权限,然后执行一段事先编译好的恶意目标平台代码来达到root的目的。 Set GID,它出现在文件所属组权限的执行位上面,对于文件和目录都有效,具体作用如下: 1、对于文件 对于二进制程序有用,程序执行者要有x权限,执行者在执行过程中会获得该程序用户组的权限(相当于临时加入了程序的用户组) 2、对于目录 用户对此目录有rx权限可以进入目录,用户进入此目录后,有效用户组会变成该目录的用户组,若用户在此目录有wx权限,则用户创建的文件的用户组与该目录用户组相同。 Sticky BIT,它出现在其他用户权限的执行位上,只对目录有效,当用户对目录拥有wx权限时,用户在该目录创建的文件或目录,只有用户自己和root才可以删除。 如何设置这些特殊权限 chmod的时候如果用数字表示,则比正常的3组数字多了一组,多的一组的含义具体如下: 4表示SUID 2表示SGID 1表示SBIT 来个简单的例子: chmod 4777 test1 上面的命令就是将test1加上了SUID权限,其他组合大家可以自由发挥。 另外如果chmod用字母的+和-来表示时要注意一下,如果因为没有x权限,直接+s或者+t,会显示为大写的S或T,出现这种情况不用担心,直接再给它+x权限就行了。 用字母表示时如果一定要设置正确的特殊权限可以参考以下几个简单的例子: chmod u+x+s xxxx chmod g+x+s xxxx chmod o+x+t xxxx 一个小问题背后也会隐藏一些比较重要的知识点,善于发现和总结,总能有一些收获。 最后,将此问题记录下来,希望能够帮到后续遇到类似问题的同学。
Job Description Android smartphone is in fact a very complex system, which include typically an AP (Application Processor) plus a modem (WCDMA/HSPA protocol stack / RTOS), drivers, system components, applications, framework, 3rd party applications, to assure that all blocks are working well together, as an Android Integration Engineer, you must have a good understanding of the global architecture, design smart test cases to assure good quality before official software release to Validation team. You must have a strong debugging skills and a spririt of trouble shooting, wide knowledge across the whole mobile system Job Requirements: 3+ years programming experience Strong interest in mobile phone architecture Strong interest in debugging and learn new stuff Strong interest in Linux and enjoy script programming to make integration more efficient (build/release automation by python for example) Self motivated, creative and initiative, quick learner Expert knowledge in Qualcomm/MediaTek platform is a plus Expert knowledge in GIT Job DescriptionMobile phone is a very complex consumer device, and as an Android System Engineer, you must have a "system" mind, a good view of mobile phone global architecture, expert knowledge on Linux kernel, memory management, how the system is bootup, power management, ARM/JTAG, analyze system crash, system optimisation, recovery mode, FOTA, security, etc. Requirements2+ years programming experience Strong interest in embedded system Strong interest in Linux Strong interest in AndroidSelf motivated, creative and initiative Expert knowledge in C, Linux, RTOS, ARM, JTAG, etc Expert knowledge in GIT Job Description To make a good phone, driver is a basic requirements, this is something which cannot be achieved by downloading some applications from Internet, instead, it requires Driver Engineer to have a spirit to fine tuning the system to the best condition, as an Android Linux Driver Engineer, you must be able to read hardware schematics, read the IC datasheet, and you will have an opportunity to work on boot loader, memory, Bluetooth, Wi-Fi, GPS, Audio, Power Consumption, eCompass, G Sensor, etc. Requirements - 3+ years’ experience in driver development on Linux - Strong interest in embedded system - Strong interest in Linux - Self-motivated, creative and initiative - Expert knowledge in C, Linux, RTOS, ARM, JTAG, etc - Knowledge in Qualcomm platform is a plus Desired Skills and Experience: - Ten years minimum related experience with five years program management experience - Seeking candidates who are result-driven, disciplined, and analytical, who demonstrates good problem solving skills, interpersonal skills, communications skills, and teamwork spirit - Good communication/presentation skills with Customers - Ability to take independently responsibility and pressure with proactive and pragmatic attitude - Excellent communication/coordination skills in a matrix management structure - Able to travel 30% of time in China and abroad - Bachelor or Master degree - Fluent in Mandarin and English (oral and written) Job responsibilities: Key Account Program Manager will manage the pre-sale, project execution and launch of a portfolio of products for a specific key account; the major activity covers: - Support RFP process and product proposals, meeting with customers - Communicate with the development and marketing teams to insure customer requirements are clear and taken into the product and development process - Project planning and development strategy; Responsible to align internal development process and Carrier process - Action follow up and regular report/dashboard, Risk/Requirement management, Cost/TTM/Quality follow-up - Support market launch Job Description: - Analyze and report customer security requirement. - Define embedded software security mechanism architecture. - Analyze and report off the shelf hacking application/tool. - Integrate different software modules together and test the integrated software for Mobile phone products. - Develop test plans and test cases align with product requirements and specifications. - Execute test cases and report test metrics-based results. - Embedded software debugging using emulators/test board. - Develop tools on PC and Linux linked to security tools. Job Requirement: - Strong C/C++ programming experience. - Familiar with QCT and MTK security mechanism architecture. - Familiar with cryptographic algorithms (RSA, AES, SHA-1, etc…). - Have strong capability of problem analysis and resolving. - Familiar with Real-time Operating systems, such as Android, Nucleas, Embedded Linux, etc. - Familiar Widows/Linux based development environment. - Familiarity with software development process (CMM, different models) - Self-motivated and ability to work independently in solving problem. - Team work and excellent communication skills, written and verbal. - BS/MS Degree in Telecom, Electronics Engineering or Computer Role Description: We are seeking Senior Software Engineer for developing Bluetooth 4.0 middleware on Wearable Device or Mobile Terminal Device. Your responsibility is for - Software module/driver development for BT IC - Provide BLE communication solution for Wearable Device on Android/iOS system - Optimize BLE performance for Wearable Device or Smart Phone - Tuning Connectivity (Bluetooth 4.0 and WIFI etc.) modules with Vendor - Unit test, investigation and debugging - Technical document writing, software architecture designing Requirements: - Master degree in Computer Science or Electronic Engineering, CET-6 is a must - At least 5+ years of experience on Bluetooth technology and understanding on Bluetooth standards, Bluetooth 4.0 is preferred - Strong knowledge of RTOS concepts (multi-threading, synchronization, device drivers etc.) - Good understanding and skill in C or C++ MCU programming experience - Outstanding problem-solving skills and excellent communication skills - Comfortable developing for Android/Linux or Window Phone environments, familiar with ARM architecture is preferred - Good in spoken and written English Role Description We are seeking senior iOS Software Engineer to define, design and support our Wearable Device applications. You need know lot of concepts of Wearable Device, like Pedometer and Heart Rate Testing etc. As a developer you will join the cutting-edge mobile app development from concept stage until deliver and post launch support and enhancement. In addition to delivering the product, you will also be involved in coding and reviewing outsourcing developed app and prototyping for new UI and concept. Basic Qualifications - Bachelor/Master degree in Computer Science or Electronic Engineering, CET-6 is a must - 5 years of programming work experience, at least 3 years’ experience in iOS app development - Solid understanding of Apple iPhone/iPad App development life cycle including App Store compliance policies and submissions requirements - Experience with iOS Frameworks and Memory Management - Experience with iOS BT4.0 is preferred - Strong programming ability in Objective-C and X-Code - Strong programming ability in C/C++ - Outstanding problem-solving skills and excellent communication skills - Good in spoken and written English Role Description: We are seeking Software Algorithm Expert to support our Wearable Device. You need familiar with three of below sensors at least: -HRM Sensor -Biometric Sensor -Accelerometer -Gyroscope -Magnetometer As an Algorithm Expert you will join the Wearable Device sensors selection from vendors. In addition to delivering the product, you will also be involved in optimization of Heart Rate, Respiration Rate, SPO2, Blood Pressure, Energy Expenditure, Step Counter etc. features’ software Algorithm. You need track and analyze the data that will be collected by our tester, coach consultants in optimization, and researching algorithms. Basic Qualifications: - Master/PhD degree in Applied Mathematics, Biomedicine, Kinematics, Computer Science - 5 years of programming work experience. (At least 3 years’ experience in Software Algorithm) - Strong knowledge in ECG and PPG etc. mechanism - Strong knowledge in Heart Rate, SPO2, Blood Pressure, Energy Expenditure, Step Counter etc. - Experience with RTOS and Software Optimization - Strong programming ability in C/C++ and Matlab - Strong communication skills, especially in an environment of top professionals - Fluent in spoken and written English Job Description Android framework is a very important layer for Android mobile phones, as an Android Framework Engineer, you must be experienced in framework architecture, and cautious for any change in framework and assure Google CTS is passed, you will work with Android application engineer and Android System Engineer closely, daily work includes requirement analysis, design, coding, peer review, unit test, integration test and bugfix. Requirements 2+ years Android programming experience Strong interest in Android Strong interest in embedded application development Self motivated, creative and initiative Expert knowledge in Java language Expert knowledge in embedded Java application development Expert knowledge in Android application framework Knowledge in C/C++ and JNI development is a plus Expert knowledge in GIT Job Description: 1. Designing and developing the Web application, documenting, testing it, deploying it; 2. Scripts and tools development and maintenance. 3. Version control system (Git) operation and maintenance; Job Requirement: 1. Major in Computer Science or related; 2. Familiar with of LAMP (Linux/Apache/MySQL/PHP) deployment; 3. Knowledge of Linux OS; 4. Knowledge of at least one database (MySQL, MS SQL Server, Oracle, ...); 5. CET-4, able to read and write technical documents in English; 6. Strong willing to learn, good communication skills, sense of teamwork, adaptability; 7. Familiar with Perl / Python/Shell script is preferred. 8. Familiar with C#/.Net is preferred. Job Description Android applications are software which interact with the end user directly, and there are huge number of interesting and fantastic Android applications in the mobile phones, as an Android Application Engineer, you have the opportunity to work on user experience improvements, design in new mobile internet services, responsibilites include requirement analysis, design, coding, peer review, unit test, integration test and bugfix. Requirements 1+ years Android programming experience Strong interest in Android Strong interest in embedded application development Self motivated, creative and initiative Expert knowledge in Java language Expert knowledge in embedded Java application development Expert knowledge in Android application framework Knowledge in C/C++ and JNI development is a plus Expert knowledge in GIT Job Description: -Participate in implementation of business strategy and in charge of project family realization -Lead team of project managers to manage family of projects execution -Coach junior project managers to improve the efficiency and performance -Act as Project Managers when required -Report to management regularly or upon request on project highlights and manage the risks with mitigation or contingency plan -Lead and implement company process changes and improvements Requirement: -Minimum 1 year working experience in some domain of R&D first before -Minimum 3 years experience on one of R&D functions of electron product -Minimum 5 years experience on project management of electron consumer product -Experience on people management -Excellent communication and influence skill both in Chinese and English -Mature and willing to work under pressure -Knowledge and experience on telecom is preferred -MBA is preferred Overall Responsibility: -Take charge of architecture, design, develop, test and support key modules on Microsoft SharePoint. -Responsible for Moss 2010 administrator. -Responsible for Moss 2010 and .Net development. -Daily support existing Moss and .Net application. -Unit Test, Integration Test for SharePoint application or workflow. -Support existing tools and libraries. Required Qualifications: -At least 3 years’ experience of Moss development. -At least 3 years’ experience with .NET development. -Familiar with SharePoint 2010 development and API. -Familiar with SharePoint 2010 deployment and configuration. -Familiar with Microsoft technology background and platform: AD, SQL Server, Linq to SharePoint. -Skilled with Visual Studio 2010 and SharePoint Designer 2010. -Experience with Workflow or InfoPath. -Patient and good Communication with end users. -English (both oral and written) is required for daily work. Job Description: 1. Linux (Ubuntu, Redhat, etc) OS installation, service configuration and remote deployment, user account management, etc; 2. Linux end user troubleshooting by phone call and Email; 3. Write technical document. Job Requirement: 1. Major in Computer Science or related; 2. 1 year experience in Linux (Ubuntu/Redhat) maintenance or technical support, familiar with Linux installation, maintenance, popular services configuration; 3. Knowledge of NIS client, NFS, Samba, Autofs, etc; 4. Knowledge of script, such as sh, bash, etc; 5. Able to read and write technical documents in English; 6. Strong willing to learn, good communication skills, sense of teamwork, adaptability. Responsibility: 1. Investigate product specification, analyze customer & market segments, trend & technologies. 2. Define individual product features sets and concepts according to product roadmap and strategy. 3. Work with project core team to realize the defined features and key selling points, making product decisions and trade off when it's necessary. 4. Coordinate with functions & departments. 5. Prioritize tasks & Find good balance between TTM (time to market), Cost and Features set. 6. Introduce product concept to global marketing and sales, and refine the product definition according to the market situation. 7. Manage the product lifecycle from concept to end of life. 8. Maintain & share knowledge & experience within the P&S team. Requirements: 1. Education: -BA degree above 2. Marketing and business background is preferred. 3. Experience: -At least 3 years 4. Product Development in the Consumer Electronic Business (Junior & Senior position) 5. Skills: -Chinese as native language, Fluent in oral and written English 6. Product concept creativity 7. Excellent ability to communicate & share ideas 8. Excellent influencing and negotiating skills (especially for senior profile) 9. Good multi-tasking and prioritization skills 10. Ability to communicate with technical teams with basic technology knowledge. 11. Ability to develop workable product solutions to address the strategic and marketing imperatives of the business 12. Teamwork oriented: effective and motivating team player Responsibilities:-User interface design of key features and applications. -Planning, conceptualisation and implementation of user interface designs. -Build relationships with product marketing, software and engineering. -Drive knowledge sharing and creative flow of new ideas between departments. -Build the formation of a user-centric point of view in key application services. Technical requirements: -Scenario planning, use-case development, project and design management. -Hand drawn concepting skills (pencil and paper illustrations) -Tools Knowledge: Illustrator, Photoshop and the Adobe platform -Background: Degree in Fine Art, User Experience, Industrial/ Interaction Design, Human-Computer Interaction, Communications, 3+ years related experience. -Ability to speak English, and read and write English documents. Job Description:-Ensure the coherence of each project with the design strategy of the Design Department. -Realise all product design studies needed by Product and Solutions (Projects), in respect of constraints and objectives given. -Guarantee the quality of services and ensure that delivery dates are met. -Assist the other departments in the realisation of their mission when linked to the product design. -Provide all design specifications needed by projects development (shapes, materials, marking, colours and textures). -Contribute to ensure reactive and proactive "out of projects" studies. -Ensure continuous competition analysis in term of trends and positioning, as well as surveying design trends. Job Requirements:-Specific Skills: Great ease in drawingsAbility to work with strong constraintsTeam working spirit, but also ability to self-government. Ease in CAD software environment. -Softwares requested : Pro-Engineer, Illustrator, Photoshop, Corel Draw. -A good English level is mandatory( CET6). -Diploma from a design schools/university mandatory. -3 years experience in design consultancy or in-house design centre, ideally on consumer electronics products. 工作职责: 1、能独立负责手机架构的总体方案设计,负责评估架构涉及到的新器件引入工作。 2、负责产品需求和方案设计,保证布局方案和ID方案能够满足公司需求。 3.、评估硬件平台布局,天线,声学,等设计的进度和合理性。 4、负责结构方案设计,并与后续结构开发设计人员交接。跟踪后续方案架构方面实现的情况,协助完成产品详细设计和细节评估。 任职要求: 1、机械或模具相关专业本科学历以上,熟悉机械和模具理论知识并有丰富的实践经验; 2、有2年以上手机架构设计工作经验, 3、独立负责过至少一款手机的结构设计; 4、能够熟练使用三维(PRO/E)和二维设计软件(CAD); 5、对塑胶模结构有比较深刻的了解,对五金、压铸、粉末冶金有一定了解 6、团队合作意识强,动手能力强,有一定创新能力 7、具有较强的学习、沟通能力,独立并积极主动并能承受压力 Description: Software Project Manager will manage the development of embedded software for a smartphone handset; the major activity covers functional definition until software validation. Requirements: -5+ years working experience in mobile phone industry -Working in a very challenging environment to achieve the balance of quality,cost, TTM -Excellent communication/coordination skills in a matrix management structure -Functional requirements analysis and clarification with marketing team -Communicate with the development teams and stakeholders the expectation clearly and effectively. -Working with department manager to allocate resource / review development strategy -Project planning (tools involved, Microsoft Excel, Project, Visio) and development strategy -Action follow up and regular report/dashboard, risk management -Change Request (CRQ) management -3rd party software supplier management -Software configuration management 工作职责: 1.负责所管辖项目的项目进展,项目推动 2.带领项目测试团队,实施全流程测试,与研发产品团队配合,帮助提高软件质量,改善用户体验 3.负责和客户的协调和沟通 4.全面负责团队业务、技术能力的提高,指导并帮助工程师发展和成长,促进团队建设 5.识别和发现项目运作中的不足,制定计划并推进实施加以改进,提高团队效率 职位要求: 1. 5年以上软件测试相关工作经验,其中1-2年手机测试管理经验 2. 有相关的手机平台的测试经验 3. 优秀的沟通能力和团队管理能力 4. 积极的心态和自我驱动力
mkdir memoryUsage adb shell dumpsys meminfo > memoryUsage/dumpsys_meminfo.txt adb shell cat /sys/kernel/debug/ion/ion_mm_heap > memoryUsage/ion_mm_heap.txt adb shell cat /proc/mali/memory_usage > memoryUsage/gpu_mali_memory_usage.txt adb shell ps > memoryUsage/ps.txt adb shell cat /proc/vmallocinfo > memoryUsage/vmallocinfo.txt adb shell cat /proc/zoneinfo > memoryUsage/zoneinfo.txt adb shell cat /proc/meminfo > memoryUsage/proc_meminfo.txt adb shell dumpsys procstats > memoryUsage/proc_stats.txt pause
•Android Anatomy•Linux Kernel •Native Libraries •Android Runtime •Native Libraries •Android Runtime •Application Framework •Android Physiology •Start-up Walkthrough •Layer Interaction 强烈推荐的Android系统机制、框架、架构剖析文档,来自Google I/O,感兴趣的同学请直接下载PDF文档: Android Anatomy and Physiology
二、Android4.4来电及IncallUI显示的流程 整个流程主要分为3个部分: 1、framework部分,其主要通过RIL与Modem进行通信,当有来电的时候Modem会通知RIL,RIL则将基础数据和信息进行解析和封装后继续向上通知,大概流程为:RIL->GsmCallTracker->GSMPhone->PhoneBase->CallManager,具体过程如下图: 2、TeleService部分,这部分是由android4.4之前的PhoneAPP改造而来的,其功能基本与之前的PhoneAPP相同,主要负责与framework层的telephony进行通信,并处理在应用层的与Phone相关的状态维护、功能实现和服务提供,其大概流程为:CallStateMonitor->CallNotifier->CallModeler->CallHandlerServiceProxy,具体过程如下图: 3、InCallUI部分,这部分是由android4.4之前的PhoneAPP中分离出来的,在源码目录结构中虽然独立了,但是最终编译完成之后却打包到了Dialer中,Dialer也是android4.4才有的,是由android4.4之前的Contacts中的Dalpad独立出来的,InCallUI的大概流程:CallHandlerService->CallList->Answerpresenter->InCallPresenter->CallButtonPresenter->CallCardPresenter->CallCardFragment,具体过程如下图: 三、问题分析 开机后不做任何操作的内存使用信息: Log分析: 如下为在TeleService的CallNotifier部分的时间点及log信息,从07-09 19:28:41.883接收到来电消息到07-09 19:28:42.594消息解析分发给IncallUI的CallHandlerService结束,因为这个分发是异步单向的,所以不会同步阻塞: 07-09 19:28:41.883 896 896 D CallNotifier: RINGING... (new) 07-09 19:28:41.884 896 896 D CallNotifier: onNewRingingConnection(): state = RINGING, conn = { * -> id: 1, num: 15026714486, MT: true, mDisconnected: false } 07-09 19:28:42.594 896 896 D CallNotifier: - onNewRingingConnection() done. 再看InCallUI的CallHandlerService部分的时间点和log信息,从07-09 19:28:42.583开始创建进程到07-09 19:28:43.396的CallHandlerService - onCreate 再到07-09 19:28:43.655CallHandlerService正式收到InComingCall的消息: 07-09 19:28:42.583 666 866 I ActivityManager: Start proc com.android.incallui for service com.android.dialer/com.android.incallui.CallHandlerService: pid=4126 uid=10053 gids={50053, 3003, 1028, 1015, 3002} 07-09 19:28:43.396 4126 4126 I InCall : CallHandlerService - onCreate 07-09 19:28:43.466 896 896 D CallHandlerServiceProxy: onIncoming: Call{mCallId=2, mState=INCOMING 07-09 19:28:43.655 4126 4138 I InCall : CallHandlerService - onIncomingCall: Call{mCallId=2, mState=INCOMING 最后是InCallActivity部分的时间点和log信息,从07-09 19:28:44.144开始onCreate到07-09 19:28:46.869的onResume最终把界面显示出来给用户: 07-09 19:28:44.144 4126 4126 D InCall : InCallActivity - onCreate()... this = com.android.incallui.InCallActivity@41e225e0 07-09 19:28:46.869 4126 4126 I InCall : InCallActivity - onResume()... Systrace分析: 从systrace的图中可以看到,从incallui的进程起来到incallui的activityResume总共花了4.3s左右,其中启动进程花费804ms左右,具体如下图: 另外进程启动之后,从CallHandlerService的onCreate到ActivityStart的时间为540ms左右,具体如下图: 四、解决方案 通过以上分析我们发现每当来电时,整个过程中InCallUI接受TeleService的来电请求的环节最为耗时,因为需要启动InCallUI这个进程。另外Framework中的telephony以及TeleService都是系统中常驻的进程,不会被杀掉,所以响应速度非常快,TeleService进程常驻的相关设置: <application android:name="PhoneApp" android:persistent="true" 而InCallUI这个进程没有以上persistent的设置,所以在执行完通话的操作后会经常被系统LMK回收掉,鉴于以上原因,为了能够让InCallUI像TeleService一样响应的够快,我们给出了以下解决方案: 1、InCallUI不再独立为一个进程,将InCallUI与Dialer合并为一个进程 2、修改Dialer进程的属性,增加persistent=ture,使其常驻,进而实现InCallUI的常驻,缩短响应和加载出界面的时间 3、取消Dialer拨号时到InCallUI的过渡图片,因为Dialer和InCallUI同属一个进程后在执行拨号时的中间过渡(黑屏)时间被缩短,所以不再需要过渡图片,进而能够进一步提升性能和缩短加载InCallUI界面的时间 五、应用解决方案之后的对比分析 开机后不做任何操作的内存使用信息: Dialer进程常驻之后开机会将它启动,Dialer进程所占内存信息如下: Log分析: 如下为在TeleService的CallNotifier部分的时间点及log信息,从07-09 20:45:01.848接收到来电消息到07-09 20:45:03.959消息解析分发给IncallUI的CallHandlerService结束 07-09 20:45:01.848 900 900 D CallNotifier: RINGING... (new) 07-09 20:45:01.848 900 900 D CallNotifier: onNewRingingConnection(): state = RINGING, 07-09 20:45:03.959 900 900 D CallNotifier: - onNewRingingConnection() done. 再看InCallUI的CallHandlerService部分的时间点和log信息,从07-09 20:45:03.995的CallHandlerService - onCreate到07-09 20:45:04.229CallHandlerService正式收到InComingCall的消息,其中少了创建进程的步骤: 07-09 20:45:03.995 965 965 I InCall : CallHandlerService - onCreate 07-09 20:45:04.190 900 900 D CallHandlerServiceProxy: onIncoming: Call{mCallId=3, mState=INCOMING 07-09 20:45:04.229 965 983 I InCall : CallHandlerService - onIncomingCall: Call{mCallId=3, mState=INCOMING 最后是InCallActivity部分的时间点和log信息,从07-09 20:45:04.445开始onCreate到07-09 20:45:05.742的onResume最终把界面显示出来给用户: 07-09 20:45:04.445 965 965 D InCall : InCallActivity - onCreate()... this = com.android.incallui.InCallActivity@41e894c0 07-09 20:45:05.742 965 965 I InCall : InCallActivity - onResume()... Systrace分析: 因为没有了创建进程的过程,所以InCallUI的整个过程只有service的create到activity的resume,由上图可以看到用时1.809s左右。 由以上的log分析、systrace分析以及应用解决方案之后的对比分析,我们可以发现不管是从用户的感觉还是实际的性能参数数据上看,整个过程的性能都得到了很大提升,时间上缩短了很多。另外虽然dialer进程常驻内存,但是其本身占用的内存比较少,基本上在10MB以下,所以对整个系统来讲影响比较小。 #analysed by jinshi.song from SWD2 Framework team. #jinshi.song@jrdcom.com #201407101558
一、问题现象 在contacts中添加一个新的联系人,为新的联系人选择一个icon,在弹出的documents窗口中选择drive,在drive中选择一个图片,然后出现一段时间的黑屏。 Platform:MT6572 Android版本:4.4KK BuildType:user 系统软件版本:SWC9G+UAG0 系统RAM:512M 二、关键log以及相关代码 三、问题初步分析 四、建议的问题解决方案 完整的分析流程请直接下载PDF文档: Drive_show_black_screen_issue_analysis_report
一、问题现象 下载正式版本软件,第一次开机做完SetupWizard之后无法锁屏,HOME键和MENU键无效,重启后恢复正常。 Platform:MT6589 Android版本:4.2JB BuildType:user 系统软件版本:SWL31+UM 系统RAM:1GB 二、第一次开机执行的流程 三、问题分析 四、解决方案 五、潜在问题与风险 详细分析流程请直接下载PDF文档: Device_Provisioned_issue_analysis_report
系统RAM:512M 二、Android4.4的POWER键处理流程 Android中对于输入事件的获取和分发分别由InputManagerService的InputReader以及InputDispatcher两个线程负责,按键属于输入事件的一种。 InputReader首先通过EventHub去获取各种类型的输入事件,当获取到之后会先通知InputDispatcher,并在进入输入事件队列之前先进行系统策略上的一些拦截,对于POWER键这样的特殊按键会进行提前的特殊处理,下图为具体的POWER按键处理流程: 三、问题初步分析 通过问题现象、log以及POWER键的处理过程,发现POWER键的处理流程之所以没有正常进行,是因为PowerManagerService没有及时获取到BOOT_COMPLETED广播,从而导致其相关状态没有被更新,关键代码为goToSleepNoUpdateLocked函数中关于判断当前状态以决定是否继续处理POWER键上下流程的代码,其中有关于mBootCompleted状态的判断,具体代码如下: mBootCompleted状态开机默认为false,在收到BOOT_COMPLETED广播之后更新为ture,PowerManagerService接收BOOT_COMPLETED并更新mBootCompleted状态的关键代码如下: 既然mBootCompleted没有被及时更新,那么就说明BOOT_COMPLETED广播没有被及时收到,没有收到的话就要看为什么BOOT_COMPLETED广播没有被及时发出。 四、Android4.4上Broadcast(广播)的注册以及发送流程 广播(Broadcast)是一种在组件之间进行消息传递的方式,这些组件可以在相同或者不同的进程中,整个机制是在Binder进程间通信的基础上实现的。 广播机制存在一个注册调度中心,就是AcitivityManagerService。广播接收者订阅消息的形式就是将自己注册到ActivityManagerService中,并且指定要接收的广播类型。当广播发送者发送一个广播时,会首先发送到ActivityManagerService中,然后ActivityManagerService根据这个广播类型找到相应的广播接收者,最后将这个广播发送给它们。 广播接收者的注册方式分为静态注册和动态注册两种。静态注册是通过在配置文件AndroidManifest.xml中声明所感兴趣的广播的类型,以便ActivityManagerService能够找到它们。动态注册时在程序的代码中调用指定的接口将广播接收者注册到ActivityManagerService中。在同等情况下动态注册的广播接收者会优先收到广播。 广播的发送方式分为有序和无序两种。我们在注册广播接收者时可以指定它们的优先级,当ActivityManagerService接收到有序广播时,它就会将这个有序广播发送给符合条件的、优先级较高的广播接收者处理,然后再发送给符合条件、优先较低的广播接收者处理。当ActivityManagerService接收到无序广播时,它就会忽略广播接收者的优先级,先发送给动态注册的广播接收者,然后再将静态注册的广播接收者放入有序广播的处理队列中进行处理,所以对于无序广播动态注册的接收者总是优先接收到广播。 由于本文分析的问题出在广播的发送过程中,所以这里着重介绍广播的发送过程,以下为KK4.4上ActivityManagerService中finishBooting函数发出BOOT_COMPLETED广播的关键代码: 从上图的代码中可以看到红色框住的参数,此参数为true的话即以有序的方式发送广播,false即为无序方式。具体的广播发送过程如下: 1、广播发送者将一个特定类型的广播发送给ActivityManagerService(这里是ActivityManagerService直接发送BOOT_COMPLETED广播) 2、ActivityManagerService接收到一个广播之后,首先找到与此广播对应的广播接收者,然后根据广播的类型区分有序还是无序,有序广播会将所有的广播接收者无论静态还是动态注册都按照优先级进行排序放置到一个列表中,然后加入到有序广播的调度队列中。无序广播会先将动态注册的广播接收者放置到一个列表中,然后加入到无序广播的调度队列并向ActivityManagerService所运行的线程的消息队列发送一个消息,最后会将静态注册的广播接收者放置到另外一个列表中,然后加入到有序广播的调度队列中,所以静态注册的广播接收者总是会按照有序的方式进行处理,然后同样会向ActivityManagerService所运行的线程的消息队列发送一个处理广播的消息。这个时候对于广播发送者来讲,一个广播就发送完成了。 3、当ActivityManagerService所运行的线程的消息队列中的处理广播的消息被处理时,ActivityManagerService就会从广播调度队列中找到需要接收广播的广播接收者,并将对应的广播发送给他们所运行在应用程序进程。在这里需要特别说明的是ActivityManagerService对于无序广播的动态注册的广播接收者没有处理时间以及顺序上的限制,不会主动产生ANR,只是异步的将广播发送给接收者处理就返回。而对于有序广播则会严格限制时间和处理顺序, 4、广播接收者所在的应用程序进程接收到ActivityManagerService发送过来的广播之后,并不是直接调用广播接收者进行处理,而是将接收到的广播封装成一个消息,发送到主线程的消息队列中。当这个消息被处理时,应用程序进程才会将它所描述的广播发送给相应的广播接收者处理。 发送过程中各个组件的调用关系如下图: 以上是对无序广播的动态注册的广播接收者的发送过程,对静态注册的无序广播接收者以及有序广播的动态和静态注册的广播接收者的发送过程则比上面更复杂一点,牵扯到发送超时机制、有序同步反馈机制、启动静态广播接收者所在进程的机制等,大致的原理及过程如下: 1、对无序以及有序广播的静态广播接收者的调用过程需要先判断其所附属的进程是否存在,如果不存在需要先启动其所附属的进程,然后进程启动之后就将广播发送给其所在的应用进程,其所在的进程接收到广播之后则继续分发给具体的接收者,当广播接收者执行完毕之后需要将结果反馈给ActivityManagerService,以便让ActivityManagerService继续将广播分发下一个接收者,如果在分发一个广播给接收者的过程中超时了则会产生ANR。 2、对有序广播的动态注册的广播接收者的分发过程与静态类似,只是少了启动应用进程的过程,同样有分发完成的同步反馈以及超时无响应的ANR机制。 五、进一步的分析问题 通过测试发现Android4.2不存在此问题,因此对比KK4.4以及JB4.2上同样的代码发现在发送BOOT_COMPLETED广播时有一个很关键的地方有区别,就是KK4.4上BOOT_COMPLETED广播由之前的无序发送变为有序发送,即上图中的红色框框圈住的位置,false即为无序发送,ture即为有序发送,首先通过查看android官方原生代码发现不是MTK修改,KK4.4原生即为true,然后查看android的提交历史,找到针对此修改的提交记录如下: 从以上历史记录中可以看到google为了解决calendar的一个问题将BOOT_COMPLETED的广播发送方式由之前的无序改为了有序,同时还进行了延时的发出处理,从而最终影响了整个BOOT_COMPLETED的发送和处理流程,导致BOOT_COMPLETED的处理被安排在无序广播之后,另外由于BOOT_COMPLETED之前还有其他有序广播在待发送队列,只有那些有序广播被顺序处理完才会处理BOOT_COMPLETED,最终在处理有序的BOOT_COMPLETED的时候还会按照优先级进行排序,如果此时静态注册的接收者的优先级大于动态注册的优先级则在发送时还会等待静态注册的接收者的进程起来,然后再继续发送,所以结果就导致系统动态注册的接收者迟迟接收不到BOOT_COMPLETED,影响系统组件的整体功能体验。 六、解决方案 通过以上分析,为了避免在BOOT_COMPLETED广播之前的其他有序广播以及设置了高优先级的静态注册的BOOT_COMPLETED广播的广播接收者占用时间,影响系统组件的接收和功能状态更新,我们给出以下解决方案: 1、将BOOT_COMPLETED广播的发送方式由有序变为无序,这样就是让动态注册的系统组件及时的接收到广播消息,从而能够正常的更新状态,解决因有序而产生的问题。 应用解决方案之后系统的行为恢复正常,第三方以及其他应用程序对系统开机的影响被消除,与JB4.2行为基本保持一致,最终达到要求和解决问题的目的。 #analysed by jinshi.song from SWD2 Framework team. #jinshi.song@jrdcom.com #201407161600Activity、Service、BroadcastReceiver、Content Provider是Android的四大应用程序组件,构成一个完整的应用程序的这些组件可以在同一个进程,也可以不在同一个进程,而当这些组件不在同一个进程,需要进行数据交互时就需要一种IPC(Inter-Process Communication)进程间通信机制来完成,而Binder就是提供了IPC功能的一个框架。实现IPC的整个Binder框架包含几个重要组成部分,它们分别是Binder Driver、Client、Service(此Service与应用程序的Service组件不同)、ServiceManager。 文章目录: 基于Binder的IPC框架分析... 1 目录... 2 一、Binder进程间通信机制... 3 二、Binder Driver. 3 三、ServiceManager. 3 四、Service(Server)组件... 4 五、Client(Proxy)组件... 6 六、Client组件与Service组件的数据交互过程... 6 七、IPC框架封装的通讯细节... 6 八、获取Service组件的代理对象并进行IPC通讯的过程... 7 九、Binder通讯中的对象生命周期管理... 8 完整内容请直接下载PDF文档阅读: Android系统中基于Binder的IPC流程框架分析
Activity是android的四大组件之一,它主要负责管理android应用程序的用户界面。 本文主要从framework的角度去分析activity的启动过程。activity的启动过程根据不同的情况会有一些差别,比如锁屏与非锁屏状态下的区别,activity依附的应用程序进程不存在与已经存在的区别等。 一、KK4.4从Launcher启动Activity的流程 二、应用内启动子Activity的流程 三、锁屏状态下Activity启动的流程 文章中含有大量的时序图,请直接下载PDF文档: Activity_boot_process_analysis
有些C/C++程序或者通过JNI调用了C/C++的APK程序可以通过编译, 但在运行时会出现错误,比如常见的signal 11 (SIGSEGV),这样的程序都是可以通过编译的,而且这样的错误一般情况下不会像编译错误一样告诉你具体在代码的那一行有问题,所以调试起来比较困难和麻烦。 因为上面的这种debug困难的情况,所以就产生了coredump这种机制(很多操作系统都支持这种机制,并不是Linux独有,也不是专门为Android而生),系统(或者标准库)在发现程序错误的异常退出时,就会把程序当时整个进程的状态dump 出来,生成一个coredump文件,通常情况下coredump包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息等,可以理解为把程序工作的当前状态存储成一个文件,很幸运,我们现在使用Android系统是基于Linux内核,Linux内核原生就支持这种机制。 二、Coredump产生的原因 造成程序coredump的原因很多,这里根据以往的经验总结一下: 1、 内存访问越界 a) 由于使用错误的下标,导致数组访问越界 b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符 c) 使用strcpy,strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy,strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。 2、 多线程程序使用了线程不安全的函数 第1类:不保护共享变量的函数 第2类:保持跨越多个调用的状态函数 第3类:返回指向静态变量指针的函数 第4类:调用线程不安全函数的函数 3、 多线程读写的数据未加锁保护 一个线程做完条件判断准备使用时发生了调度,另外一个线程将其释放并置空或者修改了内容,就会导致程序执行流错乱,要么指针异常,要么状态错乱发生另外的异常 4、 非法指针 a) 使用空指针 b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型 的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它 时就很容易因为bus error而core dump c) 使用未初始化或者已经释放或者状态不明的野指针 5、 堆栈溢出 不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误 三、如何控制产生Coredump 直接修改init.rc(system/core/rootdir)文件,在setrlimit13 40 40后面添加: setrlimit 4 -1 -1 mkdir /data/coredump 0777 system system write /proc/sys/kernel/core_pattern/data/coredump/core.%e.%p write /proc/sys/fs/suid_dumpable 1 这些命令的目的是将RLIMIT_CORE的大小修改为unlimited,具体参见:keywords.h中do_setrlimit函数。其中RLIMIT_CORE的定义在Resource.h中。在data目录下创建coredump文件夹,并给系统赋予可读可写权限。改写coredump文件的存储路径。使调用了seteuid()/setegid()的程序能够生成coredump,默认情况下系统不会为这些进程生成Coredump,需要设置suid_dumpable为1。 尽管是做了上面这些动作,但是仍然只能为native程序产生coredump,对于由zygotefork的apk进程则不会生成coredump,因为zygote在做初始化的时候会忽略Linux的配置,使用默认不生成coredump的配置,system server同样是由zygote fork的,所以也不会产生coredump,所以如果出现因为system server的thread crash引起的重启则会非常头疼,需要为system server配置生成coredump,如果其他apk进程需要产生coredump同样需要在开机初始化的时候为zygote做特殊的设置。 所谓的特殊设置就是将zygote的启动参数中应用rlimit的配置,system server也是同样。 四、使用Coredump的准备 通过设置在出问题时产生了coredump,接下来我们就要使用这个coredump为我们分析具体的问题。工欲善其事必先利其器,我们首先要准备好解析coredump的环境和工具。 1、GDB,由于我们是分析嵌入式Linux内核下的产生的coredump,所以我们需要专门的交叉编译工具链中的arm-linux-androideabi-gdb,大概位置: prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-gdb 2、有了GDB还不够,我们需要更加人性化、简单易操作的图形化工具—Eclipse+CDT来查看coredump 如何获得: a、Eclipse C/C++ IDE for Luna SR2.click to download bundle package. b、在ADT中更新你的CDT插件,help-》install newsoftware-》type url:http://download.eclipse.org/tools/cdt/releases/8.6 3、准备与coredump版本对应的源代码环境,最好与编译版本时的路径保持一致,以便定位具体的代码 4、准备与coredump版本对应的symbols 五、开始使用Coredump 这个章节含有大量指导配置环境的图片,请需要的同学直接下载文档: Coredump简介及使用
很久没有写过新的博客了,原因有很多,冠冕堂皇的理由就是工作忙,生活忙,各种累,直白一点其实就是变懒了,所以没有写。 在沉寂了这么长一段时间过后,终于又要重新出发了,对于自己当前的状态,觉得首先要有所沉淀,然后就是要放空自己,唯有放空自己方能继续进步。 以后一段时间更新的博客内容主体是与Android Framework相关的疑难问题分析、机制实现的原理、源代码调用分析,然后伴有一些常用的小算法,语言特性,程序原理等。 今天就先介绍LRU和LFU这两个在Android的Framework以及App中比较常用的缓存算法的思想原理,其实这些缓存算法的原理思想和Linux中的虚拟内存页面置换算法思想是一致的,只不过具体的不同。 LFU(Least Frequently Used):使用频率最少算法,思路和原则是如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小,这种算法的目的通俗直接一点理解就是:当发生缓存内容需要更新并且已经存满时,快速的找到并替换截止到当前整个缓存中使用频率也就是次数最少的内容。 LRU(Least Recently Used):最近最久未使用算法,思路和原则是如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小,这种算法的目的通俗直接一点理解就是:当发生缓存内容需要更新并且已经存满时,快速的找到截止到当前整个缓存中最长时间没有被使用的内容,然后将其删除并重构缓存的结构,将释放出的内存位置存入更新的内容。 参考链接: http://www.cnblogs.com/dolphin0520/p/3749259.html http://www.cnblogs.com/dolphin0520/p/3741519.html http://blog.sina.com.cn/s/blog_631d3a630101mhup.html http://qyappchentao.sinaapp.com/linux-mem-al/ http://blog.sina.com.cn/s/blog_50197c290101ft7a.html http://blog.csdn.net/summerhust/article/details/6867171 http://dennis-zane.iteye.com/blog/128278 http://blog.csdn.net/luoweifu/article/details/8297084/
Caches: Current memoryusage / total memory usage (bytes): TextureCache 2182188 /25165824 LayerCache 6553600 /16777216 RenderBufferCache 0/ 2097152 GradientCache 0/ 524288 PathCache 0 /10485760 TextDropShadowCache 225936/ 2097152 PatchCache 1408/ 131072 FontRenderer 0 A8 524288/ 524288 FontRenderer 0 RGBA 0/ 0 FontRenderer 0 total 524288 / 524288 Other: FboCache 15/ 16 Total memoryusage: 9487420 bytes, 9.05 MB 2、adb shelldumpsys SurfaceFlinger [HWC Compose State (0)] Total size: 0 bytes Allocated buffers: 0xb7809d40: 2040.00KiB | 540 ( 544) x 960 | 1 | 0x00001a00 0xb78328e8: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb784cc80: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00001a00 0xb7870308: 80.75 KiB | 540 ( 544) x 38 | 1 | 0x00000900 0xb787b168: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00001a00 0xb789a3d8: 80.75 KiB | 540 ( 544) x 38 | 1 | 0x00000900 0xb789b900: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb7997e08: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb79992c8: 80.75 KiB | 540 ( 544) x 38 | 1 | 0x00000900 0xb79b7770: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb79e09e8: 80.75 KiB | 540 ( 544) x 38 | 1 | 0x00000900 0xb79e5fa0: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb79ed170: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb79ee9a8: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb79ef168: 4080.00KiB | 1080 (1088) x 960 | 2 | 0x00000900 0xb7a24a68: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb7a2d580: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb7a34c48: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb7a885c8: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 0xb7c718c0: 2040.00 KiB | 540 ( 544) x 960 | 1 | 0x00000900 Totalallocated (estimate): 35003.00 KB last eglSwapBuffers() time: 40.539000 us last transaction time : 79.307000 us transaction-flags : 00000000 refresh-rate : 60.360004 fps x-dpi : 240.000000 y-dpi : 240.000000 EGL_NATIVE_VISUAL_ID : 1 gpu_to_cpu_unsupported : 0 eglSwapBuffers time: 0.000000 us transaction time: 0.000000 us VSYNC state:enabled soft-vsync: enabled 3、adb shell cat /sys/kernel/debug/ion/ion_mm_heap(Soul4TMO have no this node) client( dbg_name) pid size address ---------------------------------------------------- m.android.phone( gralloc) 1034 2359296 0xdcacfe80 ndroid.systemui( gralloc) 850 11059200 0xdd578b00 system_server( gralloc) 716 2359296 0xde0b3000 .android.dialer( gralloc) 4690 10715136 0xde348c00 surfaceflinger( gralloc) 222 35856384 0xdea49200 ---------------------------------------------------- orphanedallocations (info is from last known client): ---------------------------------------------------- total orphaned 0 total 38215680 deferred free 0 ---------------------------------------------------- 129 order 2highmem pages in pool = 2113536 total 0 order 2 lowmempages in pool = 0 total 70 order 2highmem pages in cached_pool = 1146880 total 0 order 2 lowmempages in cached_pool = 0 total 0 order 0highmem pages in pool = 0 total 0 order 0 lowmempages in pool = 0 total 2 order 0highmem pages in cached_pool = 8192 total 0 order 0 lowmempages in cached_pool = 0 total mm_heap_freelisttotal_size=0x0 ---------------------------------------------------- buffer size kmap ref hdl mod mva secflag pid comm(client) v1 v2 v3 v4 dbg_name 0xd1c37580 86016 0 3 2 0 f00000 0 0 222 surfaceflinger 0x0 0x00x0 0x0 nothing sf_info(-18771955 0 0 540 38 0 0 540 38 335478785 0 0 0 0 0 0 ) 0xd1c37f80 86016 0 3 2 0 900000 0 0 222 surfaceflinger 0x0 0x00x0 0x0 nothing sf_info(-18771955 0 0 540 38 0 0 540 38 67043329 0 0 0 0 0 0 ) 0xd1e28380 2088960 0 2 1 0 2100000 0 0 222 surfaceflinger 0x0 0x0 0x0 0x0 nothingsf_info(-18771955 0 0 540 119 0 0 540 119 134152193 0 0 0 0 0 0 ) 0xd702ed00 86016 0 3 2 0 1000000 0 0 222 surfaceflinger 0x0 0x00x0 0x0 nothing sf_info(-18771955 0 0 540 38 0 0 540 38 67043329 0 0 0 0 0 0 ) 0xd72ebd00 2088960 0 3 2 0 2700000 0 0 222 surfaceflinger 0x0 0x00x0 0x0 nothing sf_info(-18771955 0 0 540 960 0 0 540 960 67043329 0 0 0 0 0 0) 0xdc9a0a80 2088960 0 3 2 0 c00000 0 0 222 surfaceflinger 0x0 0x00x0 0x0 nothing sf_info(-18771955 0 38 540 922 0 38 540 922 67043329 0 0 0 0 00 )
相关命令: adb shell dumpsys meminfo > dumpsys_meminfo.txt adb shell cat /sys/kernel/debug/ion/ion_mm_heap > ion_mm_heap.txt adb shell cat /proc/mali/memory_usage > gpu_mali_memory_usage.txt adb shell ps > ps.txt adb shell cat /proc/vmallocinfo > vmallocinfo.txt adb shell cat /proc/zoneinfo > zoneinfo.txt adb shell cat /proc/meminfo > proc_meminfo.txtadb shell dumpsys procstats > proc_stats.txt adb shell procrank > proc_crank.txt(eng version only)
在Android中修改键盘布局或者按键映射时,除了在inputdevice中修改上报的SCANCODE之外,还需要修改相应的kl文件。 具体原理: 当一个inputdevice的driver将按键的SCANCODE上报给EventHub之后,EventHub还会用SCANCODE去获取具体的Keycode,这一步就是根据相应的inputdevice的kl文件中的配置得到的,所以如果仅仅上报SCANCODE,不修改kl文件,则会导致SCANCODE是正确的,但是找不到相应的mapkey,从而上报的最终keycode是UNKNOWN。