android系统关机流程分析

  • 1.android 系统的关机是从用户按power 键开始的,而每个按键都会在PhoneWindowManager里面被处理
frameworks\base\services\core\java\com\android\server\policy/PhoneWindowManager.java  @Override    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {        ...         case KeyEvent.KEYCODE_POWER: //按下power键         {                // Any activity on the power button stops the accessibility shortcut                cancelPendingAccessibilityShortcutAction();                result &= ~ACTION_PASS_TO_USER;                isWakeKey = false; // wake-up will be handled separately                SystemProperties.set("persist.sys.power","1");                if (down) {                    interceptPowerKeyDown(event, interactive); //按下,down 为true,走此方法                } else {                    interceptPowerKeyUp(event, interactive, canceled);                }                break;            }    ...}
    1. interceptPowerKeyDown()方法
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {        // Hold a wake lock until the power key is released.        if (!mPowerKeyWakeLock.isHeld()) {            mPowerKeyWakeLock.acquire();        }        // Cancel multi-press detection timeout.        if (mPowerKeyPressCounter != 0) {            mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);        }        // Detect user pressing the power button in panic when an application has        // taken over the whole screen.        boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,                SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags),                isNavBarEmpty(mLastSystemUiFlags));        if (panic) {            mHandler.post(mHiddenNavPanic);        }        // Abort possibly stuck animations.        mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe);        // Latch power key state to detect screenshot chord. //这段代码实现电源键和音量-键同时按截屏的功能        if (interactive && !mScreenshotChordPowerKeyTriggered                && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {            mScreenshotChordPowerKeyTriggered = true;            mScreenshotChordPowerKeyTime = event.getDownTime();            interceptScreenshotChord();            interceptRingerToggleChord();        }        // Stop ringing or end call if configured to do so when power is pressed.        TelecomManager telecomManager = getTelecommService();        boolean hungUp = false;        if (telecomManager != null) {            if (telecomManager.isRinging()) {                // Pressing Power while there's a ringing incoming                // call should silence the ringer.                telecomManager.silenceRinger();   // 如果来电时,则启动静音            } else if ((mIncallPowerBehavior                    & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0                    && telecomManager.isInCall() && interactive) {                // Otherwise, if "Power button ends call" is enabled,                // the Power button will hang up any current active call.                hungUp = telecomManager.endCall();            }        }        GestureLauncherService gestureService = LocalServices.getService(                GestureLauncherService.class);        boolean gesturedServiceIntercepted = false;        if (gestureService != null) {            gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,                    mTmpBoolean);            if (mTmpBoolean.value && mRequestedOrGoingToSleep) {                mCameraGestureTriggeredDuringGoingToSleep = true;            }        }        // Inform the StatusBar; but do not allow it to consume the event.        sendSystemKeyToStatusBarAsync(event.getKeyCode());        // If the power key has still not yet been handled, then detect short        // press, long press, or multi press and decide what to do.        mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered                || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted;        if (!mPowerKeyHandled) {            if (interactive) {                // When interactive, we're already awake.                // Wait for a long press or for the button to be released to decide what to do.                if (hasLongPressOnPowerBehavior() && fastBootMode) {                    if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {                        powerLongPress();   //重点来了,长按power 会走这个方法,下面发消息其实最终也会走这个方法                    } else {                        Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);                        msg.setAsynchronous(true);                        mHandler.sendMessageDelayed(msg,                                ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());                        if (hasVeryLongPressOnPowerBehavior()) {                            Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);                            longMsg.setAsynchronous(true);                            mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);                        }                    }                }            } else {                wakeUpFromPowerKey(event.getDownTime());                if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior() && fastBootMode) {                    if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {                        **powerLongPress();**   //重点来了,长按power 会走这个方法,下面发消息其实最终也会走这个方法                    } else {                        Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);                        msg.setAsynchronous(true);                        mHandler.sendMessageDelayed(msg,                                ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());                        if (hasVeryLongPressOnPowerBehavior()) {                            Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);                            longMsg.setAsynchronous(true);                            mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);                        }                    }                    mBeganFromNonInteractive = true;                } else {                    final int maxCount = getMaxMultiPressPowerCount();                    if (maxCount <= 1) {                        mPowerKeyHandled = true;                    } else {                        mBeganFromNonInteractive = true;                    }                }            }        }    }
    1. powerLongPress()方法
 private void powerLongPress() {        final int behavior = getResolvedLongPressOnPowerBehavior();//得到长按电源键的行为(例如是需要弹出确认对话框呢,还是不需要)        switch (behavior) {        case LONG_PRESS_POWER_NOTHING://0            break;        case LONG_PRESS_POWER_GLOBAL_ACTIONS://1  原生的会走这个case            mPowerKeyHandled = true;            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);            showGlobalActionsInternal();  // 所以接下来会走这个方法            break;        case LONG_PRESS_POWER_SHUT_OFF:        case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:            mPowerKeyHandled = true;            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);            mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);            break;        case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:            mPowerKeyHandled = true;            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);            final boolean keyguardActive = mKeyguardDelegate == null                    ? false                    : mKeyguardDelegate.isShowing();            if (!keyguardActive) {                Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);                if (mAllowStartActivityForLongPressOnPowerDuringSetup) {                    mContext.startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);                } else {                    startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);                }            }            break;        }    }
    1. 解析长按电源键行为的方法getResolvedLongPressOnPowerBehavior()
    private int getResolvedLongPressOnPowerBehavior() {        if (FactoryTest.isLongPressOnPowerOffEnabled()) {            return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;        }        return mLongPressOnPowerBehavior;  // 最后返回这个行为    }

mLongPressOnPowerBehavior 的初始化如下:

mLongPressOnPowerBehavior = mContext.getResources().getInteger(                com.android.internal.R.integer.config_longPressOnPowerBehavior);

由此可见 mLongPressOnPowerBehavior 读取的是 config 中的值。如下:

\frameworks\base\core\res\res\values\config.xml    <!-- Control the behavior when the user long presses the power button.            0 - Nothing   // 表示直接关机            1 - Global actions menu  //关机要显示Global actions            2 - Power off (with confirmation)  // 关机要弹出对话框再次确认            3 - Power off (without confirmation) // 关机不需要弹出对话框            4 - Go to voice assist    -->    <integer name="config_longPressOnPowerBehavior">1</integer> //默认为1 
    1. showGlobalActionsInternal() 方法
    void showGlobalActionsInternal() {        if (mGlobalActions == null) {            mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);        }  // 如果mGlobalActions 为空,则创建它        final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();        mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); //然后通过这个showDialog  的方法弹出关机的对话框。        if (keyguardShowing) {            // since it took two seconds of long press to bring this up,            // poke the wake lock so they have some time to see the dialog.            mPowerManager.userActivity(SystemClock.uptimeMillis(), false);        }    }
  • 6.showDialog() 方法
\frameworks\base\services\core\java\com\android\server\policy/GlobalActions.java    public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {        if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);        if (mGlobalActionsProvider != null && mGlobalActionsProvider.isGlobalActionsDisabled()) {            return;        }        mKeyguardShowing = keyguardShowing;        mDeviceProvisioned = deviceProvisioned;        mShowing = true;        if (mGlobalActionsAvailable) {            mHandler.postDelayed(mShowTimeout, 5000);            mGlobalActionsProvider.showGlobalActions();        } else {            // SysUI isn't alive, show legacy menu.            ensureLegacyCreated();            mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned);        }
\frameworks\base\services\core\java\com\android\server\policy/LegacyGlobalActions.java    /**     * Show the global actions dialog (creating if necessary)     * @param keyguardShowing True if keyguard is showing     */    public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {        mKeyguardShowing = keyguardShowing;        mDeviceProvisioned = isDeviceProvisioned;        if (mDialog != null) {            mDialog.dismiss();            mDialog = null;            // Show delayed, so that the dismiss of the previous dialog completes            mHandler.sendEmptyMessage(MESSAGE_SHOW);        } else {            handleShow();   // 如果Dialog 不为空,则创建它        }    }    private void handleShow() {        awakenIfNecessary();        mDialog = createDialog();        prepareDialog();        // If we only have 1 item and it's a simple press action, just do this action.        if (mAdapter.getCount() == 1                && mAdapter.getItem(0) instanceof SinglePressAction                && !(mAdapter.getItem(0) instanceof LongPressAction)) {            ((SinglePressAction) mAdapter.getItem(0)).onPress();        } else {            if (mDialog != null) {                WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();                attrs.setTitle("LegacyGlobalActions");                mDialog.getWindow().setAttributes(attrs);                mDialog.show();                mDialog.getWindow().getDecorView().setSystemUiVisibility(                        View.STATUS_BAR_DISABLE_EXPAND);            }        }    }
  1. 当点击 dialog 的power off 时,会调用 PowerAction的onPress方法
    @Override    public void onPress() {        // shutdown by making sure radio and power are handled accordingly.        mWindowManagerFuncs.shutdown(false /* confirm */);    }
  1. mWindowManagerFuncs实则为WindowManagerService的对象。故
frameworks\base\services\core\java\com\android\server\wm/WindowManagerService.java    // Called by window manager policy.  Not exposed externally.    @Override    public void shutdown(boolean confirm) {        // Pass in the UI context, since ShutdownThread requires it (to show UI).        ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),                PowerManager.SHUTDOWN_USER_REQUESTED, confirm);    }
  1. ShutdownThread的shutdown方法
frameworks\base\services\core\java\com\android\server\power\ShutdownThread.java   /**     * Request a clean shutdown, waiting for subsystems to clean up their     * state etc.  Must be called from a Looper thread in which its UI     * is shown.     *     * @param context Context used to display the shutdown progress dialog. This must be a context     *                suitable for displaying UI (aka Themable).     * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.     * @param confirm true if user confirmation is needed before shutting down.     */    public static void shutdown(final Context context, String reason, boolean confirm) {        mReboot = false;        mRebootSafeMode = false;        mReason = reason;        shutdownInner(context, confirm);//confirm表示是否弹出确认对话框    }
 private static void shutdownInner(final Context context, boolean confirm) {        // ShutdownThread is called from many places, so best to verify here that the context passed        // in is themed.        context.assertRuntimeOverlayThemable();        // ensure that only one thread is trying to power down.        // any additional calls are just returned        synchronized (sIsStartedGuard) {            if (sIsStarted) {                Log.d(TAG, "Request to shutdown already running, returning.");                return;            }        }        final int longPressBehavior = context.getResources().getInteger(                        com.android.internal.R.integer.config_longPressOnPowerBehavior);        final int resourceId = mRebootSafeMode                ? com.android.internal.R.string.reboot_safemode_confirm                : (longPressBehavior == 2                        ? com.android.internal.R.string.shutdown_confirm_question                        : com.android.internal.R.string.shutdown_confirm);        Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);        if (confirm) {// 弹出对话框            final CloseDialogReceiver closer = new CloseDialogReceiver(context);            if (sConfirmDialog != null) {                sConfirmDialog.dismiss();            }            sConfirmDialog = new AlertDialog.Builder(context, R.style.Theme_Material_Dialog_Alert)                    .setTitle(mRebootSafeMode                            ? com.android.internal.R.string.reboot_safemode_title                            : com.android.internal.R.string.power_off)                    .setMessage(resourceId)                    .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {                        public void onClick(DialogInterface dialog, int which) {SystemProperties.set("persist.sys.power","0");                            beginShutdownSequence(context);                        }                    })                    .setNegativeButton(com.android.internal.R.string.no, new DialogInterface.OnClickListener() {                        public void onClick(DialogInterface dialog, int which) { Log.d(TAG, "Notifying thread to cancle shutdown ");                            SystemProperties.set("persist.sys.power","0");                        }                    })                    .create();            closer.dialog = sConfirmDialog;            sConfirmDialog.setOnDismissListener(closer);            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);            sConfirmDialog.show();        } else {            beginShutdownSequence(context); // 最终都会走这个方法        }    }
    private static void beginShutdownSequence(Context context) {  //可以在此方法里面添加关机动画        synchronized (sIsStartedGuard) {            if (sIsStarted) {                Log.d(TAG, "Shutdown sequence already running, returning.");                return;            }            sIsStarted = true;        }        sInstance.mProgressDialog = showShutdownDialog(context);        sInstance.mContext = context;        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);        // make sure we never fall asleep again        sInstance.mCpuWakeLock = null;        try {            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");            sInstance.mCpuWakeLock.setReferenceCounted(false);            sInstance.mCpuWakeLock.acquire();        } catch (SecurityException e) {            Log.w(TAG, "No permission to acquire wake lock", e);            sInstance.mCpuWakeLock = null;        }        // also make sure the screen stays on for better user experience        sInstance.mScreenWakeLock = null;        if (sInstance.mPowerManager.isScreenOn()) {            try {                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");                sInstance.mScreenWakeLock.setReferenceCounted(false);                sInstance.mScreenWakeLock.acquire();            } catch (SecurityException e) {                Log.w(TAG, "No permission to acquire wake lock", e);                sInstance.mScreenWakeLock = null;            }        }        if (SecurityLog.isLoggingEnabled()) {            SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);        }        // start the thread that initiates shutdown        sInstance.mHandler = new Handler() {        };        sInstance.start();  //进入 ShutdownThread的run方法    }
 /**     * Makes sure we handle the shutdown gracefully.     * Shuts off power regardless of radio state if the allotted time has passed.     */    public void run() {        TimingsTraceLog shutdownTimingLog = newTimingsLog();        shutdownTimingLog.traceBegin("SystemServerShutdown");        metricShutdownStart();        metricStarted(METRIC_SYSTEM_SERVER);        BroadcastReceiver br = new BroadcastReceiver() {            @Override public void onReceive(Context context, Intent intent) {                // We don't allow apps to cancel this, so ignore the result.                actionDone();            }        };        /*         * Write a system property in case the system_server reboots before we         * get to the actual hardware restart. If that happens, we'll retry at         * the beginning of the SystemServer startup.         */        {            String reason = (mReboot ? "1" : "0") + (mReason != null ? mReason : "");            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason); //保存关机的原因        }        /*         * If we are rebooting into safe mode, write a system property         * indicating so.         */        if (mRebootSafeMode) {            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");        }        metricStarted(METRIC_SEND_BROADCAST);        shutdownTimingLog.traceBegin("SendShutdownBroadcast");        Log.i(TAG, "Sending shutdown broadcast...");        //Make ShutDownCamService to front        Intent shutdownIntent = new Intent();        shutdownIntent.setAction("com.mediatek.ShutDownCamService");        shutdownIntent.setPackage("mtktvapi.agent");        mContext.startService(shutdownIntent);        // First send the high-level shut down broadcast.        mActionDone = false;        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);        mContext.sendOrderedBroadcastAsUser(intent,                UserHandle.ALL, null, br, mHandler, 0, null, null);//发送关机广播        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;        synchronized (mActionDoneSync) {            while (!mActionDone) {                long delay = endTime - SystemClock.elapsedRealtime();                if (delay <= 0) {                    Log.w(TAG, "Shutdown broadcast timed out");                    break;                } else if (mRebootHasProgressBar) {                    int status = (int)((MAX_BROADCAST_TIME - delay) * 1.0 *                            BROADCAST_STOP_PERCENT / MAX_BROADCAST_TIME);                    sInstance.setRebootProgress(status, null);                }                try {                    mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));                } catch (InterruptedException e) {                }            }        }        if (mRebootHasProgressBar) {            sInstance.setRebootProgress(BROADCAST_STOP_PERCENT, null);        }        shutdownTimingLog.traceEnd(); // SendShutdownBroadcast        metricEnded(METRIC_SEND_BROADCAST);        Log.i(TAG, "Shutting down activity manager...");        shutdownTimingLog.traceBegin("ShutdownActivityManager");        metricStarted(METRIC_AM);        final IActivityManager am =                IActivityManager.Stub.asInterface(ServiceManager.checkService("activity"));        if (am != null) {            try {                am.shutdown(MAX_BROADCAST_TIME);  //关闭AMS服务            } catch (RemoteException e) {            }        }        if (mRebootHasProgressBar) {            sInstance.setRebootProgress(ACTIVITY_MANAGER_STOP_PERCENT, null);        }        shutdownTimingLog.traceEnd();// ShutdownActivityManager        metricEnded(METRIC_AM);        Log.i(TAG, "Shutting down package manager...");        shutdownTimingLog.traceBegin("ShutdownPackageManager");        metricStarted(METRIC_PM);        final PackageManagerService pm = (PackageManagerService)            ServiceManager.getService("package");        if (pm != null) {            pm.shutdown(); //关闭pms服务        }        if (mRebootHasProgressBar) {            sInstance.setRebootProgress(PACKAGE_MANAGER_STOP_PERCENT, null);        }        shutdownTimingLog.traceEnd(); // ShutdownPackageManager        metricEnded(METRIC_PM);        // Shutdown radios.        shutdownTimingLog.traceBegin("ShutdownRadios");        metricStarted(METRIC_RADIOS);        shutdownRadios(MAX_RADIO_WAIT_TIME);        if (mRebootHasProgressBar) {            sInstance.setRebootProgress(RADIO_STOP_PERCENT, null);        }        shutdownTimingLog.traceEnd(); // ShutdownRadios        metricEnded(METRIC_RADIOS);        if (mRebootHasProgressBar) {            sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);            // If it's to reboot to install an update and uncrypt hasn't been            // done yet, trigger it now.            uncrypt();        }        shutdownTimingLog.traceEnd(); // SystemServerShutdown        metricEnded(METRIC_SYSTEM_SERVER);        saveMetrics(mReboot, mReason);        // Remaining work will be done by init, including vold shutdown        rebootOrShutdown(mContext, mReboot, mReason); // 最后调用 这个方法进一步处理    }
    /**     * Do not call this directly. Use {@link #reboot(Context, String, boolean)}     * or {@link #shutdown(Context, String, boolean)} instead.     *     * @param context Context used to vibrate or null without vibration     * @param reboot true to reboot or false to shutdown     * @param reason reason for reboot/shutdown     */    public static void rebootOrShutdown(final Context context, boolean reboot, String reason) {        if (reboot) { //判断是否重启            Log.i(TAG, "Rebooting, reason: " + reason);            PowerManagerService.lowLevelReboot(reason);            Log.e(TAG, "Reboot failed, will attempt shutdown instead");            reason = null;        } else if (SHUTDOWN_VIBRATE_MS > 0 && context != null) {            // vibrate before shutting down            Vibrator vibrator = new SystemVibrator(context); // 关机之前震动一下            try {                vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);            } catch (Exception e)                 // Failure to vibrate shouldn't interrupt shutdown.  Just log it.                Log.w(TAG, "Failed to vibrate during shutdown.", e);            }            // vibrator is asynchronous so we need to wait to avoid shutting down too soon.            try {                Thread.sleep(SHUTDOWN_VIBRATE_MS);            } catch (InterruptedException unused) {            }        }        // Shutdown power        Log.i(TAG, "Performing low-level shutdown...");        PowerManagerService.lowLevelShutdown(reason); // 关闭电源    }
    /**     * Low-level function turn the device off immediately, without trying     * to be clean.  Most people should use {@link ShutdownThread} for a clean shutdown.     *     * @param reason code to pass to android_reboot() (e.g. "userrequested"), or null.     */    public static void lowLevelShutdown(String reason) {        if (reason == null) {            reason = "";        }        SystemProperties.set("sys.powerctl", "shutdown," + reason); // 最后kernel 会检查此值来 实现真正的关闭。    }
到此 android 系统的framework 层关机流程就分析完毕。

更多相关文章

  1. android标题栏进度圈使用方法
  2. Android重写onOreate,onPause,onStop等方法时需要注意的问题!
  3. XML解析
  4. android 比较有用的方法总结
  5. 运行Android项目,报错java.lang.IllegalAccessException: access
  6. System.EntryPointNotFoundException: Unable to find an entry
  7. android中的alertdialog及LayoutInflater
  8. 【Android(安卓)开发教程】WebView
  9. android 设置LinearLayout,RelativeLayout等等layout的高和宽

随机推荐

  1. so文件静态分析&&Android录屏技术
  2. Android MMS模块数据存取
  3. android ListView 使用
  4. 时间选择器和日期选择器
  5. ueventd.rc 处理硬件设备权限和android i
  6. Android 获取联系人姓名与电话号码
  7. [Android(安卓)Pro] service中显示一个di
  8. Android OpenGL 学习笔记
  9. 下拉刷新控件---使用第三方提供的源码
  10. Android中JavaScript与Java交互