WindowManagerService的启动过程


Android系统的输入管理服务同其他服务一样,也是驻留在SystemServer进程中,该服务是随WindowManagerService服务启动而启动。在frameworks\base\services\java\com\android\server\SystemServer.java中的ServerThread线程中启动WindowManagerService,WindowManagerService是Android的Java服务,也是遵循Java层Binder通信框架设计的,其类结构关系如下图所示:


WindowManagerService启动过程如下所示:

public static WindowManagerService main(Context context,PowerManagerService pm, boolean haveInputMethods, boolean allowBootMsgs,boolean onlyCore) {//创建并启动WMThread线程,WMThread线程用于创建WindowManagerService对象WMThread thr = new WMThread(context, pm, haveInputMethods, allowBootMsgs, onlyCore);thr.start();    //当WMThread的成员变量mService为空时,WindowManagerService主线程睡眠等待,直到WMThread线程成功创建WindowManagerService对象//WindowManagerService主线程的运行依赖于WindowManagerService对象,因此只有在WindowManagerService对象创建成功时,线程才往下//执行synchronized (thr) {while (thr.mService == null) {try {thr.wait();} catch (InterruptedException e) {}}return thr.mService;}}
该函数通过创建线程WMThread来构造WindowManagerService对象并做初始化,同时主线程ServerThread阻塞等待WindowManagerService对象的构造完成。在Android中,一个线程阻塞等待另一个线程创建完自己运行所依赖的对象的写法非常普遍。WMThread线程执行过程:

public void run() {//WMThread线程启动消息循环Looper.prepare();//构造WindowManagerService对象WindowManagerService s = new WindowManagerService(mContext, mPM,mHaveInputMethods, mAllowBootMessages, mOnlyCore);//设置WMThread线程优先级android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY);android.os.Process.setCanSelfBackground(false);//将构造的WindowManagerService对象保存到WMThread的成员变量mService中,同时唤醒WindowManagerService主线程,synchronized (this) {mService = s;notifyAll();}// For debug builds, log event loop stalls to dropbox for analysis.if (StrictMode.conditionallyEnableDebugLogging()) {Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper");}Looper.loop();}
在WMThread线程中就只是简单地构造了WindowManagerService对象,并唤醒ServerThread线程。

private WindowManagerService(Context context, PowerManagerService pm,boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {//变量初始化...//构造InputManagerService对象mInputManager = new InputManagerService(context, mInputMonitor);mAnimator = new WindowAnimator(this, context, mPolicy);//创建并启动PolicyThread线程,PolicyThread线程用于初始化窗口管理策略,PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);thr.start();    //在窗口管理策略初始化完成前WindowManagerService主线程睡眠等待synchronized (thr) {while (!thr.mRunning) {try {thr.wait();} catch (InterruptedException e) {}}}//启动输入管理服务mInputManager.start();//将WindowManagerService服务添加到Android看门狗中监控Watchdog.getInstance().addMonitor(this);mFxSession = new SurfaceSession();Surface.openTransaction();createWatermark();Surface.closeTransaction();}
在构造WindowManagerService对象过程中,首先构造输入管理服务对象InputManagerService,并启动输入管理服务。同时采用前面构造WindowManagerService对象的写法来初始化WindowManagerPolicy,即创建PolicyThread线程来初始化mPolicy。最后将WindowManagerService添加到Watdog中监控,关于Android系统中的Watchdog的运行机制请参考 Android软Watchdog源码分析。最后获取SurfaceFlinger的远程Binder代理对象,并完成一些显示相关的工作,关于这部分内容请参看Android显示模块中的相关内容。PolicyThread线程运行过程如下:

public void run() {Looper.prepare();WindowManagerPolicyThread.set(this, Looper.myLooper());android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND);android.os.Process.setCanSelfBackground(false);mPolicy.init(mContext, mService, mService, mPM);synchronized (this) {mRunning = true;notifyAll();}// For debug builds, log event loop stalls to dropbox for analysis.if (StrictMode.conditionallyEnableDebugLogging()) {Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper");}Looper.loop();}
到此WindowManagerService服务对象就算构造完成。以下就是整个WindowManagerService服务构造过程中的线程模型:


InputManagerService的启动过程


在前面已经介绍到了,输入管理服务InputManagerService是在窗口管理服务WindowManagerService构造过程中构造并启动的,接下来就分别介绍InputManagerService对象的构造过程,然后分析InputManagerService服务的启动过程。InputManagerService也是基于Binder通信机制框架设计的Java服务,因此其类继承关系如下图所示:

InputManagerService对象的构造过程

public InputManagerService(Context context, Callbacks callbacks) {this.mContext = context;this.mCallbacks = callbacks;this.mHandler = new InputManagerHandler();//进入C++层初始化输入管理服务Slog.i(TAG, "Initializing input manager");//mPtr保存C++层的NativeInputManager对象的指针mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());}

在构造InputManagerService对象时,通过调用nativeInit函数转入到C++层初始化输入管理相关类


frameworks\base\services\jni\com_android_server_input_InputManagerService.cpp

static jint nativeInit(JNIEnv* env, jclass clazz,        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {//通过Java层的消息队列,查找其对应的C++层的消息队列对象    sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);//在C++层构造一个NativeInputManager对象    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,messageQueue->getLooper());    im->incStrong(serviceObj);    return reinterpret_cast(im);}
serviceObj为InputManagerService对象,contextObj为Context对象,messageQueueObj为InputManagerHandler连接的消息队列对象,函数最后将NativeInputManager对象指针返回给Java层的InputManagerService对象的成员变量mPtr。

NativeInputManager::NativeInputManager(jobject contextObj,jobject serviceObj, const sp& looper) :        mLooper(looper) {    JNIEnv* env = jniEnv();//创建Context对象的全局引用    mContextObj = env->NewGlobalRef(contextObj);//创建InputManagerService对象的全局引用    mServiceObj = env->NewGlobalRef(serviceObj);    {//初始化成员变量mLocked        AutoMutex _l(mLock);        mLocked.displayWidth = -1;        mLocked.displayHeight = -1;        mLocked.displayOrientation = DISPLAY_ORIENTATION_0;        mLocked.displayExternalWidth = -1;        mLocked.displayExternalHeight = -1;        mLocked.displayExternalOrientation = DISPLAY_ORIENTATION_0;        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;        mLocked.pointerSpeed = 0;        mLocked.pointerGesturesEnabled = true;        mLocked.showTouches = false;    }//在C++层构造一个EventHub对象    sp eventHub = new EventHub();//利用EventHub对象和NativeInputManager对象来构造InputManager对象    mInputManager = new InputManager(eventHub, this, this);}
在构造NativeInputManager对象时,会创建一个EventHub对象及InputManager对象。EventHub类用于监控并读取输入事件。

EventHub::EventHub(void) :        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1),        mOpeningDevices(0), mClosingDevices(0),        mNeedToSendFinishedDeviceScan(false),        mNeedToReopenDevices(false), mNeedToScanDevices(true),        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);//创建epoll文件描述符,epoll监听的文件描述符大小为8    mEpollFd = epoll_create(EPOLL_SIZE_HINT);    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);    //创建inotify实例,并得到该实例的文件描述符    mINotifyFd = inotify_init();//向inotify中添加一个watch,该watch用于监听目录下的文件创建,删除    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);    LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s.  errno=%d",DEVICE_PATH, errno);    //初始化数据结构epoll_event    struct epoll_event eventItem;    memset(&eventItem, 0, sizeof(eventItem));//epoll监听文件描述符可读事件    eventItem.events = EPOLLIN;    eventItem.data.u32 = EPOLL_ID_INOTIFY;//将inotify实例的文件描述符添加到epoll中监控,即使用epoll监听inotify下的所有watch事件    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);    //创建无名管道    int wakeFds[2];    result = pipe(wakeFds);    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);    mWakeReadPipeFd = wakeFds[0];    mWakeWritePipeFd = wakeFds[1];    //设置管道读端为非阻塞模式    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",errno);    //设置管道写端为非阻塞模式    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",errno);    //将管道读端描述符添加到epoll中监控    eventItem.data.u32 = EPOLL_ID_WAKE;    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",errno);}
相信阅读了 Linux文件系统Inotify机制, Linux进程间通信方式之管道(pipe), Linux系统IO复用接口(select、poll、epoll)这三篇文章之后应该很容易理解EventHub对象的构造过程。整个过程可以用下图来描述:


接着利用创建好的EventHub对象及NativeInputManager对象来构造C++层的InputManager:mInputManager = new InputManager(eventHub, this, this),注意this指针指向的是NativeInputManager对象。

InputManager::InputManager(        const sp& eventHub,        const sp& readerPolicy,        const sp& dispatcherPolicy) {//利用NativeInputManager对象构造一个InputDispatcher对象    mDispatcher = new InputDispatcher(dispatcherPolicy);//利用NativeInputManager,EventHub,InputDispatcher对象构造一个InputReader对象    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);//初始化InputManager    initialize();}
该构造函数中分别创建InputDispatcher对象及InputReader对象,然后调用initialize()函数创建InputReaderThread,InputDispatcherThread线程。

void InputManager::initialize() {//创建输入消息读取线程    mReaderThread = new InputReaderThread(mReader);//创建消息分发线程    mDispatcherThread = new InputDispatcherThread(mDispatcher);}

InputReaderThread线程用于读取输入事件,而InputDispatcherThread线程用于将读取到的输入事件分发到当前激活的窗口中。输入管理整个模型如下图所示:


WindowManagerService是在WMThread线程中创建,而InputManagerService是在WindowManagerService对象构造过程中创建并启动的,此时的线程模型如下:

InputDispatcher构造过程


InputDispatcher::InputDispatcher(const sp& policy) :    mPolicy(policy),    mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),    mNextUnblockedEvent(NULL),    mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false),    mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {//构造消息循环Loop对象    mLooper = new Looper(false);    mKeyRepeatState.lastKeyEntry = NULL;//调用NativeInputManager对象的getDispatcherConfiguration读取消息分发配置    policy->getDispatcherConfiguration(&mConfig);}
参数policy指向的是NativeInputManager对象,NativeInputManager继承了InputDispatcherPolicyInterface类,因此NativeInputManager负责消息分发相关策略配置,通过NativeInputManager类的getDispatcherConfiguration函数来读取相关配置信息到InputDispatcher的成员变量mConfig中。

void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) {    JNIEnv* env = jniEnv();//调用Java层的getKeyRepeatTimeout方法获取按键重复超时时间    jint keyRepeatTimeout = env->CallIntMethod(mServiceObj,gServiceClassInfo.getKeyRepeatTimeout);    if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatTimeout")) {        outConfig->keyRepeatTimeout = milliseconds_to_nanoseconds(keyRepeatTimeout);    }//调用Java层的getKeyRepeatDelay方法获取按键重复时延时间    jint keyRepeatDelay = env->CallIntMethod(mServiceObj,gServiceClassInfo.getKeyRepeatDelay);    if (!checkAndClearExceptionFromCallback(env, "getKeyRepeatDelay")) {        outConfig->keyRepeatDelay = milliseconds_to_nanoseconds(keyRepeatDelay);    }}

InputReader构造过程


InputReader::InputReader(const sp& eventHub,        const sp& policy,        const sp& listener) :        mContext(this), mEventHub(eventHub), mPolicy(policy),        mGlobalMetaState(0), mGeneration(1),        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),        mConfigurationChangesToRefresh(0) {//使用InputDispatcher来构造QueuedInputListener对象    mQueuedListener = new QueuedInputListener(listener);    { // acquire lock        AutoMutex _l(mLock);        refreshConfigurationLocked(0);        updateGlobalMetaStateLocked();    } // release lock}
参数policy传入的是NativeInputManager对象,该类继承了InputReaderPolicyInterface类

参数listener传入的是InputDispatcher对象,InputDispatcher类继承了InputListenerInterface类

该函数通过NativeInputManager对象读取消息读取相关策略信息,然后使用读取到的策略信息来配置各个输入设备。

InputReader::InputReader(const sp& eventHub,        const sp& policy,        const sp& listener) :        mContext(this), mEventHub(eventHub), mPolicy(policy),        mGlobalMetaState(0), mGeneration(1),        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),        mConfigurationChangesToRefresh(0) {//使用InputDispatcher来构造QueuedInputListener对象    mQueuedListener = new QueuedInputListener(listener);    { // acquire lock        AutoMutex _l(mLock);//读取策略信息并配置输入设备        refreshConfigurationLocked(0);//更新mGlobalMetaState变量        updateGlobalMetaStateLocked();    } // release lock}
refreshConfigurationLocked用于读取策略信息来配置输入设备

void InputReader::refreshConfigurationLocked(uint32_t changes) {//读取策略信息    mPolicy->getReaderConfiguration(&mConfig);//设置排除监控的输入设备    mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);//输入设备变更,重新配置输入设备    if (changes) {        ALOGI("Reconfiguring input devices.  changes=0x%08x", changes);        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);//如果输入设备需要重新打开        if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {            mEventHub->requestReopenDevices();        } else {//配置输入设备            for (size_t i = 0; i < mDevices.size(); i++) {                InputDevice* device = mDevices.valueAt(i);                device->configure(now, &mConfig, changes);            }        }    }}
mPolicy为NativeInputManager类型变量,调用其getReaderConfiguration函数来读取策略信息

void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {    JNIEnv* env = jniEnv();//调用InputManagerService的getVirtualKeyQuietTimeMillis方法获取虚拟按键静音时间    jint virtualKeyQuietTime = env->CallIntMethod(mServiceObj,gServiceClassInfo.getVirtualKeyQuietTimeMillis);    if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {        outConfig->virtualKeyQuietTime = milliseconds_to_nanoseconds(virtualKeyQuietTime);    }    outConfig->excludedDeviceNames.clear();//调用InputManagerService的getExcludedDeviceNames方法获取排除监控的输入设备名称    jobjectArray excludedDeviceNames = jobjectArray(env->CallObjectMethod(mServiceObj,gServiceClassInfo.getExcludedDeviceNames));    if (!checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && excludedDeviceNames) {        jsize length = env->GetArrayLength(excludedDeviceNames);        for (jsize i = 0; i < length; i++) {            jstring item = jstring(env->GetObjectArrayElement(excludedDeviceNames, i));            const char* deviceNameChars = env->GetStringUTFChars(item, NULL);            outConfig->excludedDeviceNames.add(String8(deviceNameChars));            env->ReleaseStringUTFChars(item, deviceNameChars);            env->DeleteLocalRef(item);        }        env->DeleteLocalRef(excludedDeviceNames);    }//调用InputManagerService的getHoverTapTimeout方法获取HoverTap超时时间    jint hoverTapTimeout = env->CallIntMethod(mServiceObj,gServiceClassInfo.getHoverTapTimeout);    if (!checkAndClearExceptionFromCallback(env, "getHoverTapTimeout")) {//调用InputManagerService的getDoubleTapTimeout方法获取DoubleTap超时时间        jint doubleTapTimeout = env->CallIntMethod(mServiceObj,gServiceClassInfo.getDoubleTapTimeout);        if (!checkAndClearExceptionFromCallback(env, "getDoubleTapTimeout")) {//调用InputManagerService的getLongPressTimeout方法获取LongPress超时时间            jint longPressTimeout = env->CallIntMethod(mServiceObj,gServiceClassInfo.getLongPressTimeout);            if (!checkAndClearExceptionFromCallback(env, "getLongPressTimeout")) {                outConfig->pointerGestureTapInterval = milliseconds_to_nanoseconds(hoverTapTimeout);                jint tapDragInterval = max(min(longPressTimeout - 100,doubleTapTimeout), hoverTapTimeout);                outConfig->pointerGestureTapDragInterval =milliseconds_to_nanoseconds(tapDragInterval);            }        }    }//调用InputManagerService的getHoverTapSlop方法    jint hoverTapSlop = env->CallIntMethod(mServiceObj,gServiceClassInfo.getHoverTapSlop);    if (!checkAndClearExceptionFromCallback(env, "getHoverTapSlop")) {        outConfig->pointerGestureTapSlop = hoverTapSlop;    }    { // acquire lock        AutoMutex _l(mLock);        outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed* POINTER_SPEED_EXPONENT);        outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;        outConfig->showTouches = mLocked.showTouches;        outConfig->setDisplayInfo(0, false,mLocked.displayWidth, mLocked.displayHeight, mLocked.displayOrientation);        outConfig->setDisplayInfo(0, true ,mLocked.displayExternalWidth, mLocked.displayExternalHeight,mLocked.displayExternalOrientation);    } // release lock}
策略信息读取过程和前面InputDispatcher的策略信息读取过程类似,这里不在重复介绍。接下来介绍一下排除监控设备信息读取过程:

private String[] getExcludedDeviceNames() {ArrayList names = new ArrayList();XmlPullParser parser = null;//读取并解析/system/etc/excluded-input-devices.xml文件File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH);FileReader confreader = null;try {confreader = new FileReader(confFile);parser = Xml.newPullParser();parser.setInput(confreader);XmlUtils.beginDocument(parser, "devices");while (true) {XmlUtils.nextElement(parser);if (!"device".equals(parser.getName())) {break;}String name = parser.getAttributeValue(null, "name");if (name != null) {names.add(name);}}} catch (FileNotFoundException e) {// It's ok if the file does not exist.} catch (Exception e) {Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e);} finally {try { if (confreader != null) confreader.close(); } catch (IOException e) { }}return names.toArray(new String[names.size()]);}
到此输入管理服务从Java层到C++层所需的所有对象构造过程已经介绍完成,接下来就可以启动输入管理服务,以开始监控输入设备的输入事件。

InputManagerService服务注册过程


InputManagerService服务仍然是在SystemServer中注册的,和其他服务注册类似:

inputManager = wm.getInputManagerService();ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
这里调用WindowManagerService类型变量wm的getInputManagerService()函数获取前面在WindowManagerService中构造的InputManagerService对象,然后注册到ServerManager进程中,关于服务的的注册完成过程请参看 Android服务注册完整过程源码分析。

InputManagerService服务查询过程


和Android其他服务类似,同样定义了Manager类来对外提供访问InputManagerService服务的接口。

frameworks\base\core\java\android\hardware\input\InputManager.java

public static InputManager getInstance() {synchronized (InputManager.class) {if (sInstance == null) {//根据服务名称从ServerManger进程中查询Binder代理对象IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);//将查询到的Binder代理对象转换为和输入服务业务相关代理对象,并利用该对象来构造InputManager对象,此处使用Java的代理设计模式sInstance = new InputManager(IInputManager.Stub.asInterface(b));}return sInstance;}}
这样其他进程就可以通过InputManager对象来实现跨进程访问InputManagerService了。

InputManagerService服务启动过程


通过调用InputManagerService对象的start()函数来启动InputManagerService服务
public void start() {Slog.i(TAG, "Starting input manager");//启动C++层的输入管理服务nativeStart(mPtr);//将InputManagerService服务添加到Watchdog中监控Watchdog.getInstance().addMonitor(this);//注册PointerSpeed设置变更ObserverregisterPointerSpeedSettingObserver();//注册ShowTouch设置变更ObserverregisterShowTouchesSettingObserver();//从数据库中更新PointerSpeed和ShowTouchesupdatePointerSpeedFromSettings();updateShowTouchesFromSettings();}
函数调用nativeStart()启动C++层的输入服务,参数mPtr保存的是C++层的NativeInputManager对象指针,然后注册各种Observer以感知相关设置的变更,关于Android系统的文件Observer相关知识,在以后介绍。
static void nativeStart(JNIEnv* env, jclass clazz, jint ptr) {    NativeInputManager* im = reinterpret_cast(ptr);    status_t result = im->getInputManager()->start();    if (result) {        jniThrowRuntimeException(env, "Input manager could not be started.");    }}
nativeStart()函数通过JNI进入到C++层,首先将Java层传过来的参数ptr强制转换为NativeInputManager对象指针,这是因为NativeInputManager对象创建后,将其对象地址保存到了InputManagerService的成员变量mPtr中了。得到NativeInputManager对象后,通过其getInputManager()函数得到C++层的InputManager对象,最后调用InputManager的start()函数来启动输入管理服务。
status_t InputManager::start() {//启动消息分发线程    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);    if (result) {        ALOGE("Could not start InputDispatcher thread due to error %d.", result);        return result;    }//启动输入事件读取线程    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);    if (result) {        ALOGE("Could not start InputReader thread due to error %d.", result);        mDispatcherThread->requestExit();        return result;    }    return OK;}
InputManager::start()函数真正启动了InputDispatcherThread和InputReaderThread线程,到此系统就可以接收输入事件了。

更多相关文章

  1. Android(安卓)中如何关闭线程
  2. android onTouchEvent和setOnTouchListener中onTouch的区别
  3. Android基础篇之Android快速入门--你必须要知道的基础
  4. Android属性动画Property Animation系列二之ObjectAnimator
  5. Android中WebView使用规范
  6. Android-Jni线程(一)— 创建线程
  7. android中listview的setAdapter()和getAdapter()
  8. Android面试总结--Android篇
  9. Android(安卓)使用View类绘图

随机推荐

  1. android stagefright 框架
  2. Android Socket 编程
  3. android实现事件监听的2种常用方式
  4. 我的android绘图学习笔记(二)
  5. android拼接多张bitmap图片
  6. Android 读取安装的非系统应用程序
  7. Task和Activity相关-转帖
  8. android 手势识别(一)
  9. Android SqlLite的简单实用
  10. Android 短信验证码自动填写