Android启动之bluetooth
2013-12-31 16:30
375 查看
前一段时间我们分析了kernel中bluetooth的初始化操作,从这一章起,晓东将会和大家一起正式进入到Android中bluetooth的分析。毫无疑问,我们依然需要知道在Android启动的时候,蓝牙究竟有做些什么。
首先我们来回顾一下Android启动的一般流程,有人把它归结为4个步骤,分别为1)init进程启动;2)Native服务启动;3)System
server,Android服务启动;4)Home启动。那么,我们来一个个看在这几个步骤中,都有哪些是和蓝牙有关的。
1、init中的bluetooth
[html] view
plaincopy
#首先是创建了两个文件夹和bluetooth相关
mkdir /data/misc/bluetoothd 0770 bluetooth bluetooth
mkdir /data/misc/bluetooth 0770 system system
#新建了一个dbus的service,dbus是用于bluez和jni层交互的
#细心的同学已经发了,他是没有disabled的,所以,会直接启动哦
#不过dbus只是一个通路而已,我们暂时并不准备去详细地分析
service dbus /system/bin/dbus-daemon --system --nofork
class main
socket dbus stream 660 bluetooth bluetooth
user bluetooth
group bluetooth net_bt_admin
#新建了bluetoothd的service,这个就是bluez了
service bluetoothd /system/bin/bluetoothd -n
class main
socket bluetooth stream 660 bluetooth bluetooth
socket dbus_bluetooth stream 660 bluetooth bluetooth
# init.rc does not yet support applying capabilities, so run as root and
# let bluetoothd drop uid to bluetooth with the right linux capabilities
group bluetooth net_bt_admin misc
disabled
#这里就是各个文件的权限配置了
chmod 666 /dev/ttyS0
chmod 666 /proc/bluetooth/sleep/proto
chmod 666 /sys/class/rfkill/rfkill0/state
chmod 666 /sys/class/rfkill/rfkill0/type
chown bluetooth bluetooth /sys/class/rfkill/rfkill0/state
chown bluetooth bluetooth /sys/class/rfkill/rfkill0/type
#一个hciattach的service,hciattach是bluetooth的一个工具,这里可以用来启动蓝牙
#涉及到一些vendor的信息后面就写成***了
service hciattach /system/bin/logwrapper /system/bin/hciattach -n /dev/ttyS0 ***
class main
user bluetooth
group bluetooth net_bt_admin
disabled
oneshot
总得来说,init的过程和蓝牙相关的就是启动了dbus service,然后新建了bluetoothd和hciattach的service,其它就只有一些文件和文件夹以及他们权限相关的内容修改了。
Native服务启动就和蓝牙没有什么大的关系了,我们直接去看system server中的Android服务启动了
2、system server中的bluetooth
system server会启动ServerThread,至于为什么会走到这里,应该会有无数的文章详细介绍这个吧,晓东就不献丑了。我们直接来看蓝牙相关的内容:(代码位于framework/base/services/java/com/android/server/systemserver.java)
[cpp] view
plaincopy
class ServerThread extends Thread {
……
@Override
public void run() {
……
// Skip Bluetooth if we have an emulator kernel
// TODO: Use a more reliable check to see if this product should
// support Bluetooth - see bug 988521
//若是模拟器,就不用bluetooth了
if (SystemProperties.get("ro.kernel.qemu").equals("1")) {
Slog.i(TAG, "No Bluetooh Service (emulator)");
//low level的工厂测试模式,也不启动bluetooth
} else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {
Slog.i(TAG, "No Bluetooth Service (factory test)");
} else {
//上面两种都是非正常的启动,我们可以忽略
Slog.i(TAG, "Bluetooth Service");
//新建bluetoothservice,详细见2.1
//加入到servicemanager中,是BluetoothAdapter.BLUETOOTH_SERVICE
bluetooth = new BluetoothService(context);
ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth);
//注册之后的init,详细见2.2
bluetooth.initAfterRegistration();
//新建a2dpservice,并同样加入到servicemanager中,详细见2.3
bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
bluetoothA2dp);
//a2dp的init
bluetooth.initAfterA2dpRegistration();
//得到是否是飞行模式
int airplaneModeOn = Settings.System.getInt(mContentResolver,
Settings.System.AIRPLANE_MODE_ON, 0);
//看bluetooth在上次关闭的时候是否on的
int bluetoothOn = Settings.Secure.getInt(mContentResolver,
Settings.Secure.BLUETOOTH_ON, 0);
//若是非飞行模式,上次关闭前是打开,则使能bt
//使能bt的过程这篇文章就不介绍了,后面会另开一篇介绍
if (airplaneModeOn == 0 && bluetoothOn != 0) {
bluetooth.enable();
}
}
2.1 bluetoothservice的构造
bluetoothservice可以说是bluetooth的一个核心组成了,很多初始化都是由它这里发起的,我们来详细阅读一下:(代码位于/framework/base/core/java/android/server/bluetoothservice.java)
[cpp] view
plaincopy
public BluetoothService(Context context) {
mContext = context;
// Need to do this in place of:
// mBatteryStats = BatteryStatsService.getService();
// Since we can not import BatteryStatsService from here. This class really needs to be
// moved to java/services/com/android/server/
//得到battery的状态
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
//是否是bt wifi 共存
if (SystemProperties.get("ro.btwifi.coexist", "true").equals("false")) {
supportBtWifiCoexit = false;
}
//jni层的nativedata的一些初始化。最主要的是检查一下dbus是否能够用
//这里就不详细介绍了,很简单,代码位于frameworks/base/core/jni/android_server_bluetoothservice.cpp中
initializeNativeDataNative();
//检查bt是否已经enable了,理论是不应该enable的
if (isEnabledNative() == 1) {
Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
disableNative();
}
//这个是用来表示配对的一些状态的
//主要是注册一个BluetoothDevice.ACTION_PAIRING_REQUEST的receiver,这样在有pair的请求的时候,他会把pair的device加入到mPairingRequestRcvd列表中
mBondState = new BluetoothBondState(context, this);
//主要就是建了一个mPropertiesMap的hash表,保存adapter的properties mAdapterProperties = new BluetoothAdapterProperties(context, this);
//新建了一个mPropertiesMap的hash表,用来保存device的properties
mDeviceProperties = new BluetoothDeviceProperties(this);
//新建一个deviceservice channel的hash表,还有别的hash表的建立,等用到的时候再一一详细介绍
mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
mUuidIntentTracker = new ArrayList<String>();
mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
mServiceRecordToPid = new HashMap<Integer, ServiceRecordClient>();
mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
//初始化两个profiles的状态机。BluetoothProfileState是用来统一管理profile的状态的,这里注册了A2DP和HFP两个profile
mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
//启动这两个状态机,开始接收各种message
mHfpProfileState.start();
mA2dpProfileState.start();
mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(DISCOVERABLE_ALARM);
mPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
IntentFilter filter = new IntentFilter();
//就是监听ACTION_AIRPLANE_MODE_CHANGED
registerForAirplaneMode(filter);
//加入DISCOVERABLE_ALARM,ACTION_DOCK_EVENT,ACTION_BOOT_COMPLETED的监听
filter.addAction(DISCOVERABLE_ALARM);
filter.addAction(Intent.ACTION_DOCK_EVENT);
//这里说明一下ACTION_BOOT_COMPLETED的处理,就是置一个mBootCompleted = true;所以,就不另外特别说明了
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
mContext.registerReceiver(mReceiver, filter);
//构造几个profile的处理,有hid,pan和health,没有什么好说的
mBluetoothInputProfileHandler = BluetoothInputProfileHandler.getInstance(mContext, this);
mBluetoothPanProfileHandler = BluetoothPanProfileHandler.getInstance(mContext, this);
mBluetoothHealthProfileHandler = BluetoothHealthProfileHandler.getInstance(mContext, this);
//再新建两个hash表
mIncomingConnections = new HashMap<String, Pair<Integer, String>>();
mProfileConnectionState = new HashMap<Integer, Pair<Integer, Integer>>();
}
2.2、initAfterRegistration
这个函数是用来在注册之后进行初始化的,所以,可以说是bluetoothservice中被调用的第一个函数了,我们来看看它做了一些什么:
[cpp] view
plaincopy
public synchronized void initAfterRegistration() {
/新建了一个bluetoothadapter,这很重要,adapter和bluetoothservice是绑定的,后期上层ui都是同adapter那边来找到bluetoothservice的
mAdapter = BluetoothAdapter.getDefaultAdapter();
/新建bluetoothadapter的状态机,后面我会另外开一篇文章来详细介绍状态机
mBluetoothState = new BluetoothAdapterStateMachine(mContext, this, mAdapter);
//start状态机
mBluetoothState.start();
//看是否支持qucik switch,若是支持,我们会在开机的时候就默认为hot状态
//这里就是4.0和2.3中最大的不同,就是其实默认支持quick switch的情况下,蓝牙是打开的,只是没有显示在ui界面上而已。关于这里,我会另开一篇文章来介绍
if (mContext.getResources().getBoolean
(com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
mBluetoothState.sendMessage(BluetoothAdapterStateMachine.TURN_HOT);
}
//得到bluetooth的eventloop,eventloop在adapterstatemachine构造的时候创建的,这个很重要,不过我们也放到另外一篇文章中去详细介绍
mEventLoop = mBluetoothState.getBluetoothEventLoop();
}
2.3 BluetoothA2dpService
这个是a2dp service的构造
[cpp] view
plaincopy
public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
mContext = context;
//得到audioservice
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
//和bluetoothservice关联
mBluetoothService = bluetoothService;
if (mBluetoothService == null) {
throw new RuntimeException("Platform does not support Bluetooth");
}
/和bluetoothservice类似,就是初始化native的data和检查dbus
if (!initNative()) {
throw new RuntimeException("Could not init BluetoothA2dpService");
}
mAdapter = BluetoothAdapter.getDefaultAdapter();
//几个action的处理
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, mIntentFilter);
mAudioDevices = new HashMap<BluetoothDevice, Integer>();
/看bluetooth是否on
if (mBluetoothService.isEnabled())
onBluetoothEnable();
mTargetA2dpState = -1;
/使得bluetoothservice那边能找到a2dpservice
mBluetoothService.setA2dpService(this);
}
至此,android启动时,bluetooth的一些操作就已经都比较明了了。在init过程中:“init的过程和蓝牙相关的就是启动了dbus
service,然后新建了bluetoothd和hciattach的service,其它就只有一些文件和文件夹以及他们权限相关的内容修改了。”
在system service过程中,启动了bluetooth service和a2dp service。这之中比较重要的是构造了bluetoothadapater的状态机,这个是后期蓝牙各种状态变化的基础,同时启动了eventloop用于处理从dbus过来的各种event,这两者晓东会另开篇幅和大家详细介绍。
首先我们来回顾一下Android启动的一般流程,有人把它归结为4个步骤,分别为1)init进程启动;2)Native服务启动;3)System
server,Android服务启动;4)Home启动。那么,我们来一个个看在这几个步骤中,都有哪些是和蓝牙有关的。
1、init中的bluetooth
[html] view
plaincopy
#首先是创建了两个文件夹和bluetooth相关
mkdir /data/misc/bluetoothd 0770 bluetooth bluetooth
mkdir /data/misc/bluetooth 0770 system system
#新建了一个dbus的service,dbus是用于bluez和jni层交互的
#细心的同学已经发了,他是没有disabled的,所以,会直接启动哦
#不过dbus只是一个通路而已,我们暂时并不准备去详细地分析
service dbus /system/bin/dbus-daemon --system --nofork
class main
socket dbus stream 660 bluetooth bluetooth
user bluetooth
group bluetooth net_bt_admin
#新建了bluetoothd的service,这个就是bluez了
service bluetoothd /system/bin/bluetoothd -n
class main
socket bluetooth stream 660 bluetooth bluetooth
socket dbus_bluetooth stream 660 bluetooth bluetooth
# init.rc does not yet support applying capabilities, so run as root and
# let bluetoothd drop uid to bluetooth with the right linux capabilities
group bluetooth net_bt_admin misc
disabled
#这里就是各个文件的权限配置了
chmod 666 /dev/ttyS0
chmod 666 /proc/bluetooth/sleep/proto
chmod 666 /sys/class/rfkill/rfkill0/state
chmod 666 /sys/class/rfkill/rfkill0/type
chown bluetooth bluetooth /sys/class/rfkill/rfkill0/state
chown bluetooth bluetooth /sys/class/rfkill/rfkill0/type
#一个hciattach的service,hciattach是bluetooth的一个工具,这里可以用来启动蓝牙
#涉及到一些vendor的信息后面就写成***了
service hciattach /system/bin/logwrapper /system/bin/hciattach -n /dev/ttyS0 ***
class main
user bluetooth
group bluetooth net_bt_admin
disabled
oneshot
总得来说,init的过程和蓝牙相关的就是启动了dbus service,然后新建了bluetoothd和hciattach的service,其它就只有一些文件和文件夹以及他们权限相关的内容修改了。
Native服务启动就和蓝牙没有什么大的关系了,我们直接去看system server中的Android服务启动了
2、system server中的bluetooth
system server会启动ServerThread,至于为什么会走到这里,应该会有无数的文章详细介绍这个吧,晓东就不献丑了。我们直接来看蓝牙相关的内容:(代码位于framework/base/services/java/com/android/server/systemserver.java)
[cpp] view
plaincopy
class ServerThread extends Thread {
……
@Override
public void run() {
……
// Skip Bluetooth if we have an emulator kernel
// TODO: Use a more reliable check to see if this product should
// support Bluetooth - see bug 988521
//若是模拟器,就不用bluetooth了
if (SystemProperties.get("ro.kernel.qemu").equals("1")) {
Slog.i(TAG, "No Bluetooh Service (emulator)");
//low level的工厂测试模式,也不启动bluetooth
} else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {
Slog.i(TAG, "No Bluetooth Service (factory test)");
} else {
//上面两种都是非正常的启动,我们可以忽略
Slog.i(TAG, "Bluetooth Service");
//新建bluetoothservice,详细见2.1
//加入到servicemanager中,是BluetoothAdapter.BLUETOOTH_SERVICE
bluetooth = new BluetoothService(context);
ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth);
//注册之后的init,详细见2.2
bluetooth.initAfterRegistration();
//新建a2dpservice,并同样加入到servicemanager中,详细见2.3
bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
bluetoothA2dp);
//a2dp的init
bluetooth.initAfterA2dpRegistration();
//得到是否是飞行模式
int airplaneModeOn = Settings.System.getInt(mContentResolver,
Settings.System.AIRPLANE_MODE_ON, 0);
//看bluetooth在上次关闭的时候是否on的
int bluetoothOn = Settings.Secure.getInt(mContentResolver,
Settings.Secure.BLUETOOTH_ON, 0);
//若是非飞行模式,上次关闭前是打开,则使能bt
//使能bt的过程这篇文章就不介绍了,后面会另开一篇介绍
if (airplaneModeOn == 0 && bluetoothOn != 0) {
bluetooth.enable();
}
}
2.1 bluetoothservice的构造
bluetoothservice可以说是bluetooth的一个核心组成了,很多初始化都是由它这里发起的,我们来详细阅读一下:(代码位于/framework/base/core/java/android/server/bluetoothservice.java)
[cpp] view
plaincopy
public BluetoothService(Context context) {
mContext = context;
// Need to do this in place of:
// mBatteryStats = BatteryStatsService.getService();
// Since we can not import BatteryStatsService from here. This class really needs to be
// moved to java/services/com/android/server/
//得到battery的状态
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
//是否是bt wifi 共存
if (SystemProperties.get("ro.btwifi.coexist", "true").equals("false")) {
supportBtWifiCoexit = false;
}
//jni层的nativedata的一些初始化。最主要的是检查一下dbus是否能够用
//这里就不详细介绍了,很简单,代码位于frameworks/base/core/jni/android_server_bluetoothservice.cpp中
initializeNativeDataNative();
//检查bt是否已经enable了,理论是不应该enable的
if (isEnabledNative() == 1) {
Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
disableNative();
}
//这个是用来表示配对的一些状态的
//主要是注册一个BluetoothDevice.ACTION_PAIRING_REQUEST的receiver,这样在有pair的请求的时候,他会把pair的device加入到mPairingRequestRcvd列表中
mBondState = new BluetoothBondState(context, this);
//主要就是建了一个mPropertiesMap的hash表,保存adapter的properties mAdapterProperties = new BluetoothAdapterProperties(context, this);
//新建了一个mPropertiesMap的hash表,用来保存device的properties
mDeviceProperties = new BluetoothDeviceProperties(this);
//新建一个deviceservice channel的hash表,还有别的hash表的建立,等用到的时候再一一详细介绍
mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
mUuidIntentTracker = new ArrayList<String>();
mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
mServiceRecordToPid = new HashMap<Integer, ServiceRecordClient>();
mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
//初始化两个profiles的状态机。BluetoothProfileState是用来统一管理profile的状态的,这里注册了A2DP和HFP两个profile
mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
//启动这两个状态机,开始接收各种message
mHfpProfileState.start();
mA2dpProfileState.start();
mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(DISCOVERABLE_ALARM);
mPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
IntentFilter filter = new IntentFilter();
//就是监听ACTION_AIRPLANE_MODE_CHANGED
registerForAirplaneMode(filter);
//加入DISCOVERABLE_ALARM,ACTION_DOCK_EVENT,ACTION_BOOT_COMPLETED的监听
filter.addAction(DISCOVERABLE_ALARM);
filter.addAction(Intent.ACTION_DOCK_EVENT);
//这里说明一下ACTION_BOOT_COMPLETED的处理,就是置一个mBootCompleted = true;所以,就不另外特别说明了
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
mContext.registerReceiver(mReceiver, filter);
//构造几个profile的处理,有hid,pan和health,没有什么好说的
mBluetoothInputProfileHandler = BluetoothInputProfileHandler.getInstance(mContext, this);
mBluetoothPanProfileHandler = BluetoothPanProfileHandler.getInstance(mContext, this);
mBluetoothHealthProfileHandler = BluetoothHealthProfileHandler.getInstance(mContext, this);
//再新建两个hash表
mIncomingConnections = new HashMap<String, Pair<Integer, String>>();
mProfileConnectionState = new HashMap<Integer, Pair<Integer, Integer>>();
}
2.2、initAfterRegistration
这个函数是用来在注册之后进行初始化的,所以,可以说是bluetoothservice中被调用的第一个函数了,我们来看看它做了一些什么:
[cpp] view
plaincopy
public synchronized void initAfterRegistration() {
/新建了一个bluetoothadapter,这很重要,adapter和bluetoothservice是绑定的,后期上层ui都是同adapter那边来找到bluetoothservice的
mAdapter = BluetoothAdapter.getDefaultAdapter();
/新建bluetoothadapter的状态机,后面我会另外开一篇文章来详细介绍状态机
mBluetoothState = new BluetoothAdapterStateMachine(mContext, this, mAdapter);
//start状态机
mBluetoothState.start();
//看是否支持qucik switch,若是支持,我们会在开机的时候就默认为hot状态
//这里就是4.0和2.3中最大的不同,就是其实默认支持quick switch的情况下,蓝牙是打开的,只是没有显示在ui界面上而已。关于这里,我会另开一篇文章来介绍
if (mContext.getResources().getBoolean
(com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
mBluetoothState.sendMessage(BluetoothAdapterStateMachine.TURN_HOT);
}
//得到bluetooth的eventloop,eventloop在adapterstatemachine构造的时候创建的,这个很重要,不过我们也放到另外一篇文章中去详细介绍
mEventLoop = mBluetoothState.getBluetoothEventLoop();
}
2.3 BluetoothA2dpService
这个是a2dp service的构造
[cpp] view
plaincopy
public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
mContext = context;
//得到audioservice
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
//和bluetoothservice关联
mBluetoothService = bluetoothService;
if (mBluetoothService == null) {
throw new RuntimeException("Platform does not support Bluetooth");
}
/和bluetoothservice类似,就是初始化native的data和检查dbus
if (!initNative()) {
throw new RuntimeException("Could not init BluetoothA2dpService");
}
mAdapter = BluetoothAdapter.getDefaultAdapter();
//几个action的处理
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, mIntentFilter);
mAudioDevices = new HashMap<BluetoothDevice, Integer>();
/看bluetooth是否on
if (mBluetoothService.isEnabled())
onBluetoothEnable();
mTargetA2dpState = -1;
/使得bluetoothservice那边能找到a2dpservice
mBluetoothService.setA2dpService(this);
}
至此,android启动时,bluetooth的一些操作就已经都比较明了了。在init过程中:“init的过程和蓝牙相关的就是启动了dbus
service,然后新建了bluetoothd和hciattach的service,其它就只有一些文件和文件夹以及他们权限相关的内容修改了。”
在system service过程中,启动了bluetooth service和a2dp service。这之中比较重要的是构造了bluetoothadapater的状态机,这个是后期蓝牙各种状态变化的基础,同时启动了eventloop用于处理从dbus过来的各种event,这两者晓东会另开篇幅和大家详细介绍。
相关文章推荐
- Kernel中uart接口的bluetooth driver初始化分析
- Activity com.hotel.ui.LoginActivity has leaked window com.android.internal.policy.impl.PhoneWindow
- Semantic UI 中文参考手册
- kernel中bluetooth的初始化
- Kernel中bluetooth相关的config选项
- Android4.0中Bluetooth的代码结构
- EasyUI中combogrid的代码实例
- Android中bluetooth的架构
- Android Bluetooth蓝牙开发\蓝牙协议\蓝牙通信例子_Android支持蓝牙4.0版本_BLE开发
- Abuilder
- Querying the Composition Container
- UIWindow 介绍:概述、作用、主要属性及方法
- easyUI DataGrid基础
- iOS :UIPickerView reloadAllComponets not work
- Android中UI设计的技巧
- ios7中不支持自定义UIAlertView的样式
- 【Windows Phone 8】 Progressbar相关的UI阻塞的问题
- Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'sqlMapClient' is required的解决办法及详细介绍
- 用Ultraedit编写Java程序(UE工具栏的配置)
- ISE中使用notepad++ 的设置及quicktext插件的安装方法