在Android 2.3(Gingerbread) 系统的时候,我写过一篇关于“Android 震动马达系统“的文章,当时的Linux内核还是2.6版本的。写那篇文章的目的,是想彻底的了解从硬件到驱动,再到应用的运作流程。完成了之后,文章一直仍在草稿箱里面没发表;今天看到,决定整理一下,重新发表。目的是想和大家分享自己对Android系统的一点认识:以马达为代表,来考究“Android是如何一步步工作的。它从硬件设计,到Linux驱动,再到HAL,再到JNI,再到Framework,最后到被应用调用,这整套的流程到底是怎么样的!

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3404808.html

Part 1 马达的硬件设计

马达的震动原理很简单,给马达通电,马达就能震动。至于马达是如何工作,如何将电能转化为机械能,这不是我们关心的重点。但是,我们要需要了解如何控制马达的通电。在硬件上,我们是通过一个IO口(GPIO)去控制;对于马达而言,我们可以将IO理解为一个开关。当开关合上时,马达震动;开关断开,马达停止震动。

GPIO(General Purpose Input Output),称为通用输入/输出。它可以被配置为中断、输入、输出等类型,从而对各个IO进行控制。对于马达而已,GPIO就相当于一个开关。下面看看硬件原理图中的马达部分,如下图:

注:上面原理图对应CPU是“三星A8”。不同平台的马达,马达的接法和GPIO都不一样;但原理都是类似的。

原理图中红线标注部分的含义:GPH3_3是马达的GPIO。三星A8中有很多组GPIO,而马达对应和GPH3_3连接。

Part 2 马达的驱动代码

知道马达的硬件设计之后,我们就可以进行Linux Driver开发工作,也就是编写马达的驱动。Linux的一个非常重要的特点,一切都是文件!而我们进行Linux Driver开发的目的,就是将硬件设备映射成一个文件;然后,我们可以通过操作文件,来操作对应的硬件设备。

OK!理解了驱动的作用和原理之后,我们接下来开发讲解马达的驱动开发。

1. Datasheet中相关信息

我们知道,马达是通过GPIO去控制;接下来,我们就是找到马达对应的GPIO信息,然后控制该GPIO即可。

通过马达的原理图,我们知道马达和GPH3_3相连接。我们查阅“三星A8 的Datasheet”,查找GPH3_3的相关信息。

 所谓Datasheet,就是CPU芯片的数据手册。 上面记载了CPU的功能特性和操作方式等信息。任何一个厂家在发布它的芯片时,都会提供对应的Datasheet给它的客户;客户根据Datasheet上面所描述的CPU的特性,就可以进行相关的开发(当然,实际开发中可能还需要芯片厂商的支持)。例如,国内手机都是采用MTK平台,对于MTK方案开发商来说,它要开发MTK6577的产品。那么首先,MTK原厂会提供一份MTK6577的BSP包,BSP包中包括了MTK6577的Datasheet,也就是该芯片的数据手册。方案开发商有任何关于MTK6577的问题,都可以查阅该Datasheet。
三星A8的Datasheet中,关于GPH3_3的信息如下:

说明

(01) GPH3_3对应CPU中的寄存器是GPH3CON[3]。

(02) [15:12] 表示寄存器的第12~15位,一个寄存器共32 bits。而第三列的 0000, 0001, 0010, 0011, 1111表示“寄存器取不同值的时候,该GPIO的功能”。

例如, 0000表示将该GPIO作为输入,0001表示将GPIO作为输出,1111表示将该GPIO作为中断。

前面,我们已经说过,操作马达就是相当与将它作为一个开关操作。因此,我们需要将马达的GPIO设为“输入”类型;然后输入1,相当于开启马达;输入0,则是关闭马达!

下面,我们需要做的就是在Driver中将GPH3_3(也就是GPH3CON[3])映射为一个文件节点,并将它配置为“输入”类型,即将GPH3CON[3]的寄存器值设为0000。

2. 马达的驱动

我们编写马达驱动(drivers/misc/misc_sysfs.c),将马达(vibrator)注册道platform总线上。源码如下:

  1 #include <linux/kernel.h>  2 #include <linux/types.h>  3 #include <linux/module.h>  4 #include <linux/device.h>  5 #include <linux/platform_device.h>  6 #include <linux/delay.h>  7 #include <linux/irq.h>  8 #include <linux/interrupt.h>  9 #include <linux/sysfs.h> 10 #include <linux/input.h> 11 #include <mach/gpio.h> 12  13 // vibrator 对应的GPIO 14 #define  VIBRATOR_POWER_PORT (S5PV210_GPH3(3)) 15  16 typedef struct combo_module__t    { 17     unsigned char            status_vibrator; 18 }    combo_module_t    ; 19  20 static combo_module_t combo_module; 21  22 /* 23  * vibrator初始化函数:申请GPIO,并初始化vibrator状态。 24  */ 25 static void combo_module_init(void) 26 { 27     if(gpio_request(VIBRATOR_POWER_PORT, "vibrator power"))    { 28         printk("misc_sysfs.c request vibrator gpio failse.\n"); 29     } 30     gpio_pull_updown(VIBRATOR_POWER_PORT, PullDisable); 31       gpio_direction_output(VIBRATOR_POWER_PORT, GPIO_LOW);     32  33     combo_module.status_vibrator  = 0; 34 } 35  36 /* 37  * vibrator控制函数 38  */ 39 staticvoid combo_module_control(void) 40 { 41     if(combo_module.status_vibrator) 42     { 43         gpio_direction_output(VIBRATOR_POWER_PORT, GPIO_HIGH); 44     } 45     else     46     { 47         gpio_direction_output(VIBRATOR_POWER_PORT, GPIO_LOW); 48     } 49  50 } 51  52  53 /////////////////////////////////////////////////////////////////////////////////////////////////////////// 54  55 static ssize_t show_vibrator_onoff (struct device *dev, struct device_attribute *attr, char *buf) 56 { 57     return    sprintf(buf, "%d\n", combo_module.status_vibrator); 58 } 59  60 static ssize_t set_vibrator_onoff (struct device *dev, struct device_attribute *attr, const char *buf, size_t count) 61 { 62      unsigned int    val; 63  64     if(!(sscanf(buf, "%u\n", &val)))     return    -EINVAL; 65  66     //printk("set_vibrator_onoff:%d\n",val); 67  68     if(!val )     69     { 70         combo_module.status_vibrator = 0; 71         combo_module_control(); 72     } 73     else         74     { 75         combo_module.status_vibrator = 1; 76         combo_module_control(); 77  78         msleep(val); 79  80         combo_module.status_vibrator = 0; 81         combo_module_control(); 82     } 83      84     return count; 85 } 86  87 static    ssize_t show_vibrator_onoff    (struct device *dev, struct device_attribute *attr, char *buf); 88 static     ssize_t set_vibrator_onoff    (struct device *dev, struct device_attribute *attr, const char *buf, size_t count); 89 // 将vibrator注册到sysfs文件系统。 90 // 参数说明: 91 //       vibrator_onoff      : vibrator对应在sysfs下的文件节点名称 92 //       S_IRWXUGO           : 文件节点的属性 93 //       show_vibrator_onoff : 对应的读函数 94 //       set_vibrator_onoff  : 对应的写函数 95 static DEVICE_ATTR(vibrator_onoff, S_IRWXUGO, show_vibrator_onoff, set_vibrator_onoff); 96  97  98 static struct attribute *control_sysfs_entries[] = { 99     &dev_attr_vibrator_onoff.attr,100     NULL101 };102 103 static struct attribute_group control_sysfs_attr_group = {104     .name   = NULL,105     .attrs  = control_sysfs_entries,106 };107 108 static int control_sysfs_probe(struct platform_device *pdev)    109 {110     printk("vibrator probe");111     combo_module_init();112     combo_module_control();113     return    sysfs_create_group(&pdev->dev.kobj, &control_sysfs_attr_group);114 }115 116 staticint control_sysfs_remove(struct platform_device *pdev)    117 {118     sysfs_remove_group(&pdev->dev.kobj, &control_sysfs_attr_group);119     120     return    0;121 }122 123 #ifdef CONFIG_PM124 static int control_sysfs_resume(struct platform_device *dev)125 {126 127     combo_module_control();128 129     return  0;130 }131 132 static int control_sysfs_suspend(struct platform_device *dev, pm_message_t state)133 {134     135     combo_module_control();136     137     return  0;138 }139 #else140 #define control_sysfs_suspend NULL141 #define control_sysfs_resume NULL142 #endif143 144 145 static struct platform_driver control_sysfs_driver = {146     .driver = {147         .name = "misc_ctl",148         .owner = THIS_MODULE,149     },150     .probe         = control_sysfs_probe,151     .remove     = control_sysfs_remove,152     .suspend    = control_sysfs_suspend,153     .resume        = control_sysfs_resume,154 };155 156 static int __init control_sysfs_init(void)157 {    158     // 将vibrator注册到platform总线159     printk("vibrator init");160     return platform_driver_register(&control_sysfs_driver);161 }162 163 static void __exit control_sysfs_exit(void)164 {165    platform_driver_unregister(&control_sysfs_driver);166 }167 168 169 module_init(control_sysfs_init);170 module_exit(control_sysfs_exit);171 172 173 MODULE_DESCRIPTION("misc control driver");174 MODULE_AUTHOR("other");175 MODULE_LICENSE("GPL");
View Code

说明

若您熟悉驱动开发,应该很容易理解上面的代码。不熟悉也不要紧,您只需要了解“Linux系统中,一切都是文件”,上面代码的作用是,

将马达(vibrator)映射到“/sys/devices/platform/misc_ctl/vibrator_onoff”文件上,我们可以通过读写vibrator_onoff来操作马达的开启和关闭。

有了马达的源码之后,我们还需要将该源码编译到Linux内核中。这就是通过Kconfig和Makefile来完成的,关于Kconfig和Makefile的知识,这里就不过多说明了。目前您只需要了解,通过Kconfig和Makefile,我们能将马达驱动编译到内核中,该驱动会在驱动加载的时候自动运行就可以了!

马达对应的Kconfig(driver/misc/Kconfig)内容如下:

config MISC_VIBRATOR       tristate"misc vabrator"       default y

马达对应的Makefile(driver/misc/Makefile)内容如下:

obj-$(CONFIG_MISC_VIBRATOR)   += misc_sysfs.o

至此,我们已经完成马达的驱动开发了!也就是说,我们已经成功的将马达映射到文件节点上;接下来,我们通过操作文件节点,就可以操作马达了。下面从HAL层到Framework曾,都是基于Android4.2系统进行说明的。

Part 3 马达的HAL实现

HAL (Hardware Abstraction Layer), 又称为“硬件抽象层”。在Linux驱动中,我们已经将马达设为映射为文件了;而该HAL层的存在的意义,就是“对设备文件进行操作,从而相当于硬件进行操作”。HAL层的作用,一是操作硬件设备,二是操作接口封装,外界能方便的使用HAL提供的接口直接操作硬件设备。

理解了HAL之后,我们看看Android中如何在HAL层对马达进行操作。

在Android系统中,我们在libhardware_legacy中,实现马达的HAL层控制。
马达在HAL中的代码路径:hardware/libhardware_legacy/vibrator/vibrator.c

vibrator.c的代码如下:

 1 /* 2  * Copyright (C) 2008 The Android Open Source Project 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); 5  * you may not use this file except in compliance with the License. 6  * You may obtain a copy of the License at 7  * 8  *      http://www.apache.org/licenses/LICENSE-2.0 9  *10  * Unless required by applicable law or agreed to in writing, software11  * distributed under the License is distributed on an "AS IS" BASIS,12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13  * See the License for the specific language governing permissions and14  * limitations under the License.15  */16 #include <hardware_legacy/vibrator.h>17 #include "qemu.h"18 19 #include <stdio.h>20 #include <unistd.h>21 #include <fcntl.h>22 #include <errno.h>23 24 #define THE_DEVICE "/sys/devices/platform/misc_ctl/vibrator_onoff"25 26 int vibrator_exists()27 {28     int fd;29 30 #ifdef QEMU_HARDWARE31     if (qemu_check()) {32         return 1;33     }34 #endif35 36     fd = open(THE_DEVICE, O_RDWR);37     if(fd < 0)38         return 0;39     close(fd);40     return 1;41 }42 43 static int sendit(int timeout_ms)44 {45     int nwr, ret, fd;46     char value[20];47 48 #ifdef QEMU_HARDWARE49     if (qemu_check()) {50         return qemu_control_command( "vibrator:%d", timeout_ms );51     }52 #endif53 54     fd = open(THE_DEVICE, O_RDWR);55     if(fd < 0)56         return errno;57 58     nwr = sprintf(value, "%d\n", timeout_ms);59     ret = write(fd, value, nwr);60 61     close(fd);62 63     return (ret == nwr) ? 0 : -1;64 }65 66 int vibrator_on(int timeout_ms)67 {68     /* constant on, up to maximum allowed time */69     return sendit(timeout_ms);70 }71 72 int vibrator_off()73 {74     return sendit(0);75 }
View Code

在kernel的驱动中,我们已经将马达注册到sys文件系统中(/sys/devices/platform/misc_ctl/vibrator_onoff)。在vibrator.c中,我们就是通过读写“vibrator_onoff文件节点”来实现对马达的操作。

Part 4 马达的JNI部分

1 马达的JNI实现

JNI(Java Native Interface),中文是“Java本地接口”。

JNI是Java中一种技术,它存在的意义,是保证本地代码(C/C++代码)能在任何Java虚拟机下工作。简单点说,Java通过JNI接口,能够调用到C/C++代码。 关于“JNI的更多内容”,请参考“Android JNI和NDK学习系列文章”。

在了解了vibrator的HAL层实现之后,我们再来看看android是如何通过JNI将震动马达注册到android系统中。马达对应的JNI层代码路径如下:frameworks/base/services/jni/com_android_server_VibratorService.cpp

com_android_server_VibratorService.cpp的源码如下:

 1 /* 2  * Copyright (C) 2009 The Android Open Source Project 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); 5  * you may not use this file except in compliance with the License. 6  * You may obtain a copy of the License at 7  * 8  *      http://www.apache.org/licenses/LICENSE-2.0 9  *10  * Unless required by applicable law or agreed to in writing, software11  * distributed under the License is distributed on an "AS IS" BASIS,12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13  * See the License for the specific language governing permissions and14  * limitations under the License.15  */16 17 #define LOG_TAG "VibratorService"18 19 #include "jni.h"20 #include "JNIHelp.h"21 #include "android_runtime/AndroidRuntime.h"22 23 #include <utils/misc.h>24 #include <utils/Log.h>25 #include <hardware_legacy/vibrator.h>26 27 #include <stdio.h>28 29 namespace android30 {31 32 static jboolean vibratorExists(JNIEnv *env, jobject clazz)33 {34     return vibrator_exists() > 0 ? JNI_TRUE : JNI_FALSE;35 }36 37 static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms)38 {39     // ALOGI("vibratorOn\n");40     vibrator_on(timeout_ms);41 }42 43 static void vibratorOff(JNIEnv *env, jobject clazz)44 {45     // ALOGI("vibratorOff\n");46     vibrator_off();47 }48 49 static JNINativeMethod method_table[] = {50     { "vibratorExists", "()Z", (void*)vibratorExists },51     { "vibratorOn", "(J)V", (void*)vibratorOn },52     { "vibratorOff", "()V", (void*)vibratorOff }53 };54 55 int register_android_server_VibratorService(JNIEnv *env)56 {57     return jniRegisterNativeMethods(env, "com/android/server/VibratorService",58             method_table, NELEM(method_table));59 }60 61 };
View Code

下面,对这部分的JNI代码进行简单说明。

(01) 通过 jniRegisterNativeMethods(),我们将method_table中的方法注册到 com.android.server.VibratorService.java 中。配对表格如下:

---------------------------------------------------++++------------------------------------------- VibratorService.java                          com_android_server_VibratorService.cpp   native static boolean vibratorExists();                static jboolean vibratorExists(JNIEnv *env, jobject clazz)native static void vibratorOn(long milliseconds);      static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms)native static void vibratorOff();                      static void vibratorOff(JNIEnv *env, jobject clazz)

通过JNI,我们就能将Java层和HAL层的代码联系起来。
以vibratorOff()来说,我们在VibratorService.java中调用vibratorOff();实际上会调用到com_android_server_VibratorService.cpp中的vibratorOff()函数;进一步会调用到vibrator_off()函数,而vibrator_off()是我们在 “HAL层的vibrator.c中的接口”。


2 马达的JNI如何和HAL关联方式

在继续接下来的研究之前,我们先搞清楚:JNI如何和HAL层代码关联起来的。即com_android_server_VibratorService.cpp是如何调用到vibrator.c中的代码的。
实际上道理很简单,我们先将vibrator.c封装成.so库;然后在com_android_server_VibratorService.cpp中导入该库,就可以调用vibrator.c的接口了。下面,看看Android中具体是如何做到的。

(01) vibrator.c封装到libhardware_legacy.so中的步骤

在hardware/libhardware_legacy/vibrator/Android.mk中,会将vibrator.c添加到 LOCAL_SRC_FILES 变量中。
hardware/libhardware_legacy/vibrator/Android.mk源码如下:

LOCAL_SRC_FILES += vibrator/vibrator.c

在hardware/libhardware_legacy/Android.mk中,它会调用子目录的Android.mk并将它们导入当前的Android.mk中。
hardware/libhardware_legacy/Android.mk源码如下:

legacy_modules := power uevent vibrator wifi qemu qemu_tracingSAVE_MAKEFILES := $(call all-named-subdir-makefiles,$(legacy_modules))LEGACY_AUDIO_MAKEFILES := $(call all-named-subdir-makefiles,audio)include $(SAVE_MAKEFILES)...LOCAL_MODULE:= libhardware_legacyinclude $(BUILD_SHARED_LIBRARY)

在“我们编译Android系统”或“通过 mmm hardware/libhardware_legacy进行模块编译”的时候,就会生成库libhardware_legacy.so;而且vibrator.c被包含在该库中。

(02) 在 com_android_server_VibratorService.cpp 对应的Android.mk中,会导入libhardware_legacy.so。
com_android_server_VibratorService.cpp 对应的frameworks/base/services/jni/Android.mk 的源码如下:

LOCAL_SRC_FILES:= \com_android_server_VibratorService.cpp \...LOCAL_SHARED_LIBRARIES := \libhardware_legacy \...LOCAL_MODULE:= libandroid_serversinclude $(BUILD_SHARED_LIBRARY)

Part 5 马达的Framework层实现

应用层操作马达,是通过马达服务进行操作的。而马达服务是通过aidl实现的,aidl是Android进程间的通信方式。关于aidl的更多说明可以参考“Android Service总结06 之AIDL”。

马达服务涉及的主要文件如下:

1 frameworks/base/services/java/com/android/server/SystemServer.java2 frameworks/base/services/java/com/android/server/VibratorService.java3 frameworks/base/core/java/android/os/IVibratorService.aidl4 frameworks/base/core/java/android/os/Vibrator.java5 frameworks/base/core/java/android/os/SystemVibrator.java

下面,对这几个文件的功能进行简要说明。

文件1: SystemServer.java
它是系统服务,作用是启动、管理系统服务,包括“马达服务、Wifi服务、Activity管理服务”等等。
SystemServer是通过Zygote启动的,而Zygote又是在init中启动的,init则是kernel加载完毕之后启动的第一个进程。在这里,我们只需要知道“SystemServer是用来启动/管理马达服务即可。”

文件2: IVibratorService.aidl
它是马达服务对应的aidl配置文件。我们在aidl中定义了其它进程可以访问的外部接口;然后再通过VibratorService.java实现这些接口。

文件3: VibratorService.java
它是马达服务对应的aidl接口的实现程序。它实现IVibratorService.aidl的接口,从而实现马达服务;它的函数接口,是通过调用JNI层对应的马达控制函数来实现的。

文件4: Vibrator.java
它是马达服务开放给应用层的调用类。理论上讲,我们完全可以通过aidl直接调用马达服务,而不需要Vibrator.java类。但是!既然它存在,就肯定有它的理由。事实的确如此,Google之所以这么做。有以下几个原因:
第一,提供统一而且方便的服务调用方式。这里的“统一”,是指和所有其它的系统服务一样,我们调用服务时,需先通过getSystemService()获取服务,然后再调用服务的函数接口。这里的“方便”,是指若我们直接通过aidl调用,操作比较繁琐(若你用过aidl就会知道,需要先实现ServiceConnection接口以获取IBinder对象,然后再通过IBinder对象调用aidl的接口); 而Vibrator.java封装之后的接口,将许多细节都隐藏了,非常便于应用者调用!
第二,基于安全的考虑。Vibrator.java封装隐藏了许多细节,而这些都是应用开发者不必要知道的。
第三,Vibrator是抽象类。它便于我们支持不同类型的马达:包括“将马达直接映射到文件”以及“将马达注册到输入子系统”中。

文件5: SystemVibrator.java
它是Vibrator.java的子类,实现了马达的服务接口。

下面,我们继续Read The Fucking Source Code,加深对上面知识的理解。

1 SystemServer.java

在frameworks/base/services/java/com/android/server/SystemServer.java中关于马达的代码如下:

 1 { 2     VibratorService vibrator = null; 3  4     Slog.i(TAG, "Vibrator Service"); 5     vibrator = new VibratorService(context); 6     ServiceManager.addService("vibrator", vibrator); 7  8     ... 9 10     try {11         vibrator.systemReady();12     } catch (Throwable e) {13         reportWtf("making Vibrator Service ready", e);14     }15 }

从中,我们知道:
(01) SystemServer中会通过VibratorService()新建马达服务,并将其添加到ServiceManager中。
(02) 在Android系统启动完成之后,SystemServer会调用vibrator.systemReady()。

2 IVibratorService.aidl

在查看VibratorService.java之前,我们先看看它对应的aidl文件。frameworks/base/core/java/android/os/IVibratorService.aidl源码如下:

 1 package android.os; 2  3 /** {@hide} */ 4 interface IVibratorService 5 { 6     boolean hasVibrator(); 7     void vibrate(long milliseconds, IBinder token); 8     void vibratePattern(in long[] pattern, int repeat, IBinder token); 9     void cancelVibrate(IBinder token);10 }

3 VibratorService.java

frameworks/base/services/java/com/android/server/VibratorService.java源码如下:

  1 /*  2  * Copyright (C) 2008 The Android Open Source Project  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package com.android.server; 18  19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.pm.PackageManager; 24 import android.database.ContentObserver; 25 import android.hardware.input.InputManager; 26 import android.os.Handler; 27 import android.os.IVibratorService; 28 import android.os.PowerManager; 29 import android.os.Process; 30 import android.os.RemoteException; 31 import android.os.IBinder; 32 import android.os.Binder; 33 import android.os.SystemClock; 34 import android.os.UserHandle; 35 import android.os.Vibrator; 36 import android.os.WorkSource; 37 import android.provider.Settings; 38 import android.provider.Settings.SettingNotFoundException; 39 import android.util.Slog; 40 import android.view.InputDevice; 41  42 import java.util.ArrayList; 43 import java.util.LinkedList; 44 import java.util.ListIterator; 45  46 public class VibratorService extends IVibratorService.Stub 47         implements InputManager.InputDeviceListener { 48     private static final String TAG = "VibratorService"; 49  50     private final LinkedList<Vibration> mVibrations; 51     private Vibration mCurrentVibration; 52     private final WorkSource mTmpWorkSource = new WorkSource(); 53     private final Handler mH = new Handler(); 54  55     private final Context mContext; 56     private final PowerManager.WakeLock mWakeLock; 57     private InputManager mIm; 58  59     volatile VibrateThread mThread; 60  61     // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are 62     // to be acquired 63     private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>(); 64     private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators 65     private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators 66  67     native static boolean vibratorExists(); 68     native static void vibratorOn(long milliseconds); 69     native static void vibratorOff(); 70  71     private class Vibration implements IBinder.DeathRecipient { 72         private final IBinder mToken; 73         private final long    mTimeout; 74         private final long    mStartTime; 75         private final long[]  mPattern; 76         private final int     mRepeat; 77         private final int     mUid; 78  79         Vibration(IBinder token, long millis, int uid) { 80             this(token, millis, null, 0, uid); 81         } 82  83         Vibration(IBinder token, long[] pattern, int repeat, int uid) { 84             this(token, 0, pattern, repeat, uid); 85         } 86  87         private Vibration(IBinder token, long millis, long[] pattern, 88                 int repeat, int uid) { 89             mToken = token; 90             mTimeout = millis; 91             mStartTime = SystemClock.uptimeMillis(); 92             mPattern = pattern; 93             mRepeat = repeat; 94             mUid = uid; 95         } 96  97         public void binderDied() { 98             synchronized (mVibrations) { 99                 mVibrations.remove(this);100                 if (this == mCurrentVibration) {101                     doCancelVibrateLocked();102                     startNextVibrationLocked();103                 }104             }105         }106 107         public boolean hasLongerTimeout(long millis) {108             if (mTimeout == 0) {109                 // This is a pattern, return false to play the simple110                 // vibration.111                 return false;112             }113             if ((mStartTime + mTimeout)114                     < (SystemClock.uptimeMillis() + millis)) {115                 // If this vibration will end before the time passed in, let116                 // the new vibration play.117                 return false;118             }119             return true;120         }121     }122 123     VibratorService(Context context) {124         // Reset the hardware to a default state, in case this is a runtime125         // restart instead of a fresh boot.126         vibratorOff();127 128         mContext = context;129         PowerManager pm = (PowerManager)context.getSystemService(130                 Context.POWER_SERVICE);131         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");132         mWakeLock.setReferenceCounted(true);133 134         mVibrations = new LinkedList<Vibration>();135 136         IntentFilter filter = new IntentFilter();137         filter.addAction(Intent.ACTION_SCREEN_OFF);138         context.registerReceiver(mIntentReceiver, filter);139     }140 141     public void systemReady() {142         mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);143 144         mContext.getContentResolver().registerContentObserver(145                 Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), true,146                 new ContentObserver(mH) {147                     @Override148                     public void onChange(boolean selfChange) {149                         updateInputDeviceVibrators();150                     }151                 }, UserHandle.USER_ALL);152 153         mContext.registerReceiver(new BroadcastReceiver() {154             @Override155             public void onReceive(Context context, Intent intent) {156                 updateInputDeviceVibrators();157             }158         }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);159 160         updateInputDeviceVibrators();161     }162 163     public boolean hasVibrator() {164         return doVibratorExists();165     }166 167     public void vibrate(long milliseconds, IBinder token) {168         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)169                 != PackageManager.PERMISSION_GRANTED) {170             throw new SecurityException("Requires VIBRATE permission");171         }172         int uid = Binder.getCallingUid();173         // We're running in the system server so we cannot crash. Check for a174         // timeout of 0 or negative. This will ensure that a vibration has175         // either a timeout of > 0 or a non-null pattern.176         if (milliseconds <= 0 || (mCurrentVibration != null177                 && mCurrentVibration.hasLongerTimeout(milliseconds))) {178             // Ignore this vibration since the current vibration will play for179             // longer than milliseconds.180             return;181         }182 183         Vibration vib = new Vibration(token, milliseconds, uid);184         synchronized (mVibrations) {185             removeVibrationLocked(token);186             doCancelVibrateLocked();187             mCurrentVibration = vib;188             startVibrationLocked(vib);189         }190     }191 192     private boolean isAll0(long[] pattern) {193         int N = pattern.length;194         for (int i = 0; i < N; i++) {195             if (pattern[i] != 0) {196                 return false;197             }198         }199         return true;200     }201 202     public void vibratePattern(long[] pattern, int repeat, IBinder token) {203         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)204                 != PackageManager.PERMISSION_GRANTED) {205             throw new SecurityException("Requires VIBRATE permission");206         }207         int uid = Binder.getCallingUid();208         // so wakelock calls will succeed209         long identity = Binder.clearCallingIdentity();210         try {211             if (false) {212                 String s = "";213                 int N = pattern.length;214                 for (int i=0; i<N; i++) {215                     s += " " + pattern[i];216                 }217                 Slog.i(TAG, "vibrating with pattern: " + s);218             }219 220             // we're running in the server so we can't fail221             if (pattern == null || pattern.length == 0222                     || isAll0(pattern)223                     || repeat >= pattern.length || token == null) {224                 return;225             }226 227             Vibration vib = new Vibration(token, pattern, repeat, uid);228             try {229                 token.linkToDeath(vib, 0);230             } catch (RemoteException e) {231                 return;232             }233 234             synchronized (mVibrations) {235                 removeVibrationLocked(token);236                 doCancelVibrateLocked();237                 if (repeat >= 0) {238                     mVibrations.addFirst(vib);239                     startNextVibrationLocked();240                 } else {241                     // A negative repeat means that this pattern is not meant242                     // to repeat. Treat it like a simple vibration.243                     mCurrentVibration = vib;244                     startVibrationLocked(vib);245                 }246             }247         }248         finally {249             Binder.restoreCallingIdentity(identity);250         }251     }252 253     public void cancelVibrate(IBinder token) {254         mContext.enforceCallingOrSelfPermission(255                 android.Manifest.permission.VIBRATE,256                 "cancelVibrate");257 258         // so wakelock calls will succeed259         long identity = Binder.clearCallingIdentity();260         try {261             synchronized (mVibrations) {262                 final Vibration vib = removeVibrationLocked(token);263                 if (vib == mCurrentVibration) {264                     doCancelVibrateLocked();265                     startNextVibrationLocked();266                 }267             }268         }269         finally {270             Binder.restoreCallingIdentity(identity);271         }272     }273 274     private final Runnable mVibrationRunnable = new Runnable() {275         public void run() {276             synchronized (mVibrations) {277                 doCancelVibrateLocked();278                 startNextVibrationLocked();279             }280         }281     };282 283     // Lock held on mVibrations284     private void doCancelVibrateLocked() {285         if (mThread != null) {286             synchronized (mThread) {287                 mThread.mDone = true;288                 mThread.notify();289             }290             mThread = null;291         }292         doVibratorOff();293         mH.removeCallbacks(mVibrationRunnable);294     }295 296     // Lock held on mVibrations297     private void startNextVibrationLocked() {298         if (mVibrations.size() <= 0) {299             mCurrentVibration = null;300             return;301         }302         mCurrentVibration = mVibrations.getFirst();303         startVibrationLocked(mCurrentVibration);304     }305 306     // Lock held on mVibrations307     private void startVibrationLocked(final Vibration vib) {308         if (vib.mTimeout != 0) {309             doVibratorOn(vib.mTimeout);310             mH.postDelayed(mVibrationRunnable, vib.mTimeout);311         } else {312             // mThread better be null here. doCancelVibrate should always be313             // called before startNextVibrationLocked or startVibrationLocked.314             mThread = new VibrateThread(vib);315             mThread.start();316         }317     }318 319     // Lock held on mVibrations320     private Vibration removeVibrationLocked(IBinder token) {321         ListIterator<Vibration> iter = mVibrations.listIterator(0);322         while (iter.hasNext()) {323             Vibration vib = iter.next();324             if (vib.mToken == token) {325                 iter.remove();326                 unlinkVibration(vib);327                 return vib;328             }329         }330         // We might be looking for a simple vibration which is only stored in331         // mCurrentVibration.332         if (mCurrentVibration != null && mCurrentVibration.mToken == token) {333             unlinkVibration(mCurrentVibration);334             return mCurrentVibration;335         }336         return null;337     }338 339     private void unlinkVibration(Vibration vib) {340         if (vib.mPattern != null) {341             // If Vibration object has a pattern,342             // the Vibration object has also been linkedToDeath.343             vib.mToken.unlinkToDeath(vib, 0);344         }345     }346 347     private void updateInputDeviceVibrators() {348         synchronized (mVibrations) {349             doCancelVibrateLocked();350 351             synchronized (mInputDeviceVibrators) {352                 mVibrateInputDevicesSetting = false;353                 try {354                     mVibrateInputDevicesSetting = Settings.System.getIntForUser(355                             mContext.getContentResolver(),356                             Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;357                 } catch (SettingNotFoundException snfe) {358                 }359 360                 if (mVibrateInputDevicesSetting) {361                     if (!mInputDeviceListenerRegistered) {362                         mInputDeviceListenerRegistered = true;363                         mIm.registerInputDeviceListener(this, mH);364                     }365                 } else {366                     if (mInputDeviceListenerRegistered) {367                         mInputDeviceListenerRegistered = false;368                         mIm.unregisterInputDeviceListener(this);369                     }370                 }371 372                 mInputDeviceVibrators.clear();373                 if (mVibrateInputDevicesSetting) {374                     int[] ids = mIm.getInputDeviceIds();375                     for (int i = 0; i < ids.length; i++) {376                         InputDevice device = mIm.getInputDevice(ids[i]);377                         Vibrator vibrator = device.getVibrator();378                         if (vibrator.hasVibrator()) {379                             mInputDeviceVibrators.add(vibrator);380                         }381                     }382                 }383             }384 385             startNextVibrationLocked();386         }387     }388 389     @Override390     public void onInputDeviceAdded(int deviceId) {391         updateInputDeviceVibrators();392     }393 394     @Override395     public void onInputDeviceChanged(int deviceId) {396         updateInputDeviceVibrators();397     }398 399     @Override400     public void onInputDeviceRemoved(int deviceId) {401         updateInputDeviceVibrators();402     }403 404     private boolean doVibratorExists() {405         // For now, we choose to ignore the presence of input devices that have vibrators406         // when reporting whether the device has a vibrator.  Applications often use this407         // information to decide whether to enable certain features so they expect the408         // result of hasVibrator() to be constant.  For now, just report whether409         // the device has a built-in vibrator.410         //synchronized (mInputDeviceVibrators) {411         //    return !mInputDeviceVibrators.isEmpty() || vibratorExists();412         //}413         return vibratorExists();414     }415 416     private void doVibratorOn(long millis) {417         synchronized (mInputDeviceVibrators) {418             final int vibratorCount = mInputDeviceVibrators.size();419             if (vibratorCount != 0) {420                 for (int i = 0; i < vibratorCount; i++) {421                     mInputDeviceVibrators.get(i).vibrate(millis);422                 }423             } else {424                 vibratorOn(millis);425             }426         }427     }428 429     private void doVibratorOff() {430         synchronized (mInputDeviceVibrators) {431             final int vibratorCount = mInputDeviceVibrators.size();432             if (vibratorCount != 0) {433                 for (int i = 0; i < vibratorCount; i++) {434                     mInputDeviceVibrators.get(i).cancel();435                 }436             } else {437                 vibratorOff();438             }439         }440     }441 442     private class VibrateThread extends Thread {443         final Vibration mVibration;444         boolean mDone;445 446         VibrateThread(Vibration vib) {447             mVibration = vib;448             mTmpWorkSource.set(vib.mUid);449             mWakeLock.setWorkSource(mTmpWorkSource);450             mWakeLock.acquire();451         }452 453         private void delay(long duration) {454             if (duration > 0) {455                 long bedtime = duration + SystemClock.uptimeMillis();456                 do {457                     try {458                         this.wait(duration);459                     }460                     catch (InterruptedException e) {461                     }462                     if (mDone) {463                         break;464                     }465                     duration = bedtime - SystemClock.uptimeMillis();466                 } while (duration > 0);467             }468         }469 470         public void run() {471             Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);472             synchronized (this) {473                 int index = 0;474                 long[] pattern = mVibration.mPattern;475                 int len = pattern.length;476                 int repeat = mVibration.mRepeat;477                 long duration = 0;478 479                 while (!mDone) {480                     // add off-time duration to any accumulated on-time duration481                     if (index < len) {482                         duration += pattern[index++];483                     }484 485                     // sleep until it is time to start the vibrator486                     delay(duration);487                     if (mDone) {488                         break;489                     }490 491                     if (index < len) {492                         // read on-time duration and start the vibrator493                         // duration is saved for delay() at top of loop494                         duration = pattern[index++];495                         if (duration > 0) {496                             VibratorService.this.doVibratorOn(duration);497                         }498                     } else {499                         if (repeat < 0) {500                             break;501                         } else {502                             index = repeat;503                             duration = 0;504                         }505                     }506                 }507                 mWakeLock.release();508             }509             synchronized (mVibrations) {510                 if (mThread == this) {511                     mThread = null;512                 }513                 if (!mDone) {514                     // If this vibration finished naturally, start the next515                     // vibration.516                     mVibrations.remove(mVibration);517                     unlinkVibration(mVibration);518                     startNextVibrationLocked();519                 }520             }521         }522     };523 524     BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {525         public void onReceive(Context context, Intent intent) {526             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {527                 synchronized (mVibrations) {528                     doCancelVibrateLocked();529 530                     int size = mVibrations.size();531                     for(int i = 0; i < size; i++) {532                         unlinkVibration(mVibrations.get(i));533                     }534 535                     mVibrations.clear();536                 }537             }538         }539     };540 }
View Code

其中,VibratorService实际上是通过“本地方法”去控制马达的。例如,hasVibratora()最终是通过vibratorExists()来判断马达是否存在的。

4 Vibrator.java

frameworks/base/core/java/android/os/Vibrator.java源码如下:

 1 package android.os; 2  3 import android.content.Context; 4  5 public abstract class Vibrator { 6  7     public Vibrator() { 8     } 9 10     public abstract boolean hasVibrator();11     12     public abstract void vibrate(long milliseconds);13 14     public abstract void vibrate(long[] pattern, int repeat);15 16     public abstract void cancel();17 }

5 SystemVibrator.java

frameworks/base/core/java/android/os/SystemVibrator.java源码如下:

 1 /* 2  * Copyright (C) 2012 The Android Open Source Project 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); 5  * you may not use this file except in compliance with the License. 6  * You may obtain a copy of the License at 7  * 8  *      http://www.apache.org/licenses/LICENSE-2.0 9  *10  * Unless required by applicable law or agreed to in writing, software11  * distributed under the License is distributed on an "AS IS" BASIS,12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13  * See the License for the specific language governing permissions and14  * limitations under the License.15  */16 17 package android.os;18 19 import android.util.Log;20 21 /**22  * Vibrator implementation that controls the main system vibrator.23  *24  * @hide25  */26 public class SystemVibrator extends Vibrator {27     private static final String TAG = "Vibrator";28 29     private final IVibratorService mService;30     private final Binder mToken = new Binder();31 32     public SystemVibrator() {33         mService = IVibratorService.Stub.asInterface(34                 ServiceManager.getService("vibrator"));35     }36 37     @Override38     public boolean hasVibrator() {39         if (mService == null) {40             Log.w(TAG, "Failed to vibrate; no vibrator service.");41             return false;42         }43         try {44             return mService.hasVibrator();45         } catch (RemoteException e) {46         }47         return false;48     }49 50     @Override51     public void vibrate(long milliseconds) {52         if (mService == null) {53             Log.w(TAG, "Failed to vibrate; no vibrator service.");54             return;55         }56         try {57             mService.vibrate(milliseconds, mToken);58         } catch (RemoteException e) {59             Log.w(TAG, "Failed to vibrate.", e);60         }61     }62 63     @Override64     public void vibrate(long[] pattern, int repeat) {65         if (mService == null) {66             Log.w(TAG, "Failed to vibrate; no vibrator service.");67             return;68         }69         // catch this here because the server will do nothing.  pattern may70         // not be null, let that be checked, because the server will drop it71         // anyway72         if (repeat < pattern.length) {73             try {74                 mService.vibratePattern(pattern, repeat, mToken);75             } catch (RemoteException e) {76                 Log.w(TAG, "Failed to vibrate.", e);77             }78         } else {79             throw new ArrayIndexOutOfBoundsException();80         }81     }82 83     @Override84     public void cancel() {85         if (mService == null) {86             return;87         }88         try {89             mService.cancelVibrate(mToken);90         } catch (RemoteException e) {91             Log.w(TAG, "Failed to cancel vibration.", e);92         }93     }94 }
View Code

说明
(01) 在构造函数SystemVibrator()中,我们通过 IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator")) 获取马达服务,实际上获取的是VibratorService对象。
(02) SystemVibrator的接口都是调用VibratorService接口实现的。

在讲解“应用层如何通过getSystemService(VIBRATOR_SERVICE)获取马达服务,然后进一步的操作马达”之前,我们先看看应用层的马达操作示例!

Part 6 马达的应用示例

1 权限

调用马达服务,需要在manifest中添加相应的权限:

<!-- 震动马达权限 --><uses-permission android:name="android.permission.VIBRATE"/>

2 源码

源码如下:

 1 package com.test; 2  3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.os.Vibrator; 6 import android.view.View; 7 import android.view.View.OnClickListener; 8 import android.widget.Button; 9 import android.widget.ToggleButton;10 import android.util.Log;11 12 public class VibratorTest extends Activity {13     private static final String TAG = "skywang-->VibratorTest";14 15     private Vibrator mVibrator;16     private Button mOnce = null;17     private ToggleButton mEndless = null;18 19     @Override20     protected void onCreate(Bundle savedInstanceState) {21         super.onCreate(savedInstanceState);22         setContentView(R.layout.main);23 24         // 获取震动马达服务25         mVibrator= (Vibrator) getSystemService(VIBRATOR_SERVICE);26 27         mOnce = (Button) findViewById(R.id.vib_once);28         mOnce.setOnClickListener(new View.OnClickListener() {29             30             @Override31             public void onClick(View view) {32                 //震动指定时间33                 mVibrator.vibrate(100);34             }35         });36 37         mEndless = (ToggleButton) findViewById(R.id.vib_endless);38         mEndless.setOnClickListener(new OnClickListener() {39             @Override40             public void onClick(View v) {41                 if (mEndless.isChecked()) {42                     //等待100ms后,按数组所给数值间隔震动;其后为重复次数,-1为不重复,0一直震动43                     mVibrator.vibrate(new long[]{100,20,100,40,100,60}, 0);44                 } else {45                     // 取消震动 46                     mVibrator.cancel();47                 }48             }49         });50 51     }52 53     @Override54     protected void onStop() {55         super.onStop();56         if (mVibrator != null)57             mVibrator= null;58     }59 }
点击下载:Android马达应用代码

Part 7 马达的应用如何调用到马达服务的

接下来,我们分析一下如何获取马达服务的:即mVibrator= (Vibrator) getSystemService(VIBRATOR_SERVICE)的工作原理。

1. Context.java中的getSystemService()

getSystemService()定义在frameworks/base/core/java/android/content/Context.java中,源码如下:

public abstract Object getSystemService(String name);

Context.java中的getSystemService() 是个抽象方法,它的实现在ContextImpl.java中。

2. ContextImpl.java中的getSystemService()

frameworks/base/core/java/android/app/ContextImpl.java中的 getSystemService() 源码如下:

1 @Override2 public Object getSystemService(String name) {3     ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);4     return fetcher == null ? null : fetcher.getService(this);5 }

3. ContextImpl.java中的SYSTEM_SERVICE_MAP

SYSTEM_SERVICE_MAP是一个HashMap对象,它的相关代码如下:

 1 private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP = 2         new HashMap<String, ServiceFetcher>(); 3  4 SYSTEM_SERVICE_MAP的初始化,是在ContextImpl.java通过static静态模块完成的。源码如下: 5 static { 6  7     ... 8  9     // 注册“传感器服务”10     registerService(SENSOR_SERVICE, new ServiceFetcher() {11             public Object createService(ContextImpl ctx) {12                 return new SystemSensorManager(ctx.mMainThread.getHandler().getLooper());13             }});14 15     // 注册其它服务 ...16 17     // 注册马达服务18     registerService(VIBRATOR_SERVICE, new ServiceFetcher() {19             public Object createService(ContextImpl ctx) {20                 return new SystemVibrator();21             }});22 23     ...24 }

说明:在上面的static静态模块中,会通过registerService()注册一系列的服务,包括马达服务。注册服务是通过registerService()实现的,下面我们看看registerService()的定义。

1 private static int sNextPerContextServiceCacheIndex = 0;2 private static void registerService(String serviceName, ServiceFetcher fetcher) {3     if (!(fetcher instanceof StaticServiceFetcher)) {4         fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;5     }6     SYSTEM_SERVICE_MAP.put(serviceName, fetcher);7 }

从中,我们知道,在registerService()中,会通过 SYSTEM_SERVICE_MAP.put(serviceName, fetcher) 将serviceName和fetcher添加到哈希表SYSTEM_SERVICE_MAP中。
对马达服务而言,添加到哈希表SYSTEM_SERVICE_MAP中的key-value中的key是VIBRATOR_SERVICEvalue则是ServiceFetcher对象;而且该匿名ServiceFetcher对象的createService()方法会“通过new SystemVibrator()”返回SystemVibrator对象。而SystemVibrator我们在前面已经介绍过了,它是马达服务对外提供接口的类。

OK,接着往下看。

3. ContextImpl.java中的fetcher.getService(this)

1 public Object getSystemService(String name) {2     ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);3     return fetcher == null ? null : fetcher.getService(this);4 }

我们已经知道SYSTEM_SERVICE_MAP是哈希表,通过SYSTEM_SERVICE_MAP.get(name)返回的是ServiceFetcher对象。
由于fetcher不为null,所以,getSystemService()会返回fetcher.getService(this)。我们看看ServiceFetcher中getService()源码:

 1 static class ServiceFetcher { 2     int mContextCacheIndex = -1; 3  4     public Object getService(ContextImpl ctx) { 5         ArrayList<Object> cache = ctx.mServiceCache; 6         Object service; 7         synchronized (cache) { 8             if (cache.size() == 0) { 9 10                 // “服务对象”缓冲11                 for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {12                     cache.add(null);13                 }14             } else {15                 service = cache.get(mContextCacheIndex);16                 if (service != null) {17                     return service;18                 }19             }20             service = createService(ctx);21             cache.set(mContextCacheIndex, service);22             return service;23         }24     }25 26     public Object createService(ContextImpl ctx) {27         throw new RuntimeException("Not implemented");28     }29 }

从中,我们发现,getService()实际上返回的是“通过createService(ctx)创建的service对象”。
而在registerService()注册马达服务时,我们匿名实现了createService()方法:它实际上是通过 new SystemVibrator() 返回SystemVibrator对象。

至此,我们知道:getSystemService(VIBRATOR_SERVICE) 返回的是 SystemVibrator对象!SystemVibrator前面已经分析过,这里就不再说明了。

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. 【攻克Android(安卓)(37):XML解析之二】SAX方式解析XML
  6. Android文件访问权限
  7. Android线程安全
  8. 引擎设计跟踪(九.9) 文件包系统(Game Package System)
  9. 理解UI线程——swt, Android, 和Swing的UI机理

随机推荐

  1. Android的消息机制
  2. 绿色守护简单使用教程
  3. Android(安卓)IPC(进程间通信)
  4. android 实现欢迎界面
  5. Android(安卓)10、11 存储完全适配(上)
  6. Android周报第二十期
  7. Android(安卓)在百度地图上显示自己的实
  8. 简单了解XML 树结构
  9. 简单了解XML 树结构
  10. 板绘线稿如何入门?板绘线稿入门教程