Android6.0關機流程
Android系統關機有如下方式:1.定時關機、2.指令行輸入reboot重新開機、3.長按電源鍵出現關機對畫框等,本文以長按電源鍵為例來分析基于Android 6.0的高通源碼。
長按電源鍵會啟動Android系統的按鍵消息處理機制。每個activity具有一個phonewindow對象,每個phonewindow對象具有一個DecorView對象,每個DecorVier又被設定到一個ViewRoot對象中(如圖一).每個activity建立的時候會通過ViewRoot注冊一個inputmanager,然後建立一組對應的事件擷取和分發的線程:InputReader 和InputDispatch。當沒有事件發生的時候Reader處于輪訓狀态,Dispatch處于休眠狀态。當Reader擷取到event的時候,Reader将會喚醒Dispatch,讓其分發這個event到目前focus 的activity,然在activity的dispatchkeyEvent中進行處理。我們将關機流程分為PowerKey事件擷取、Java層的關機流程分析、Native層的關機流程分析和kernel層的關機流程分析四部分來分析。
Kernel層事件處理
kernel層的事件上傳,是指PowerKey按下的事件傳遞到Framework之前的流程,然後Framework層進行關機的相關操作。
1.pmic8xxx-pwrkey.c-input_report_key
static irqreturn_t pwrkey_press_irq(int irq, void*_pwrkey)
{
struct pmic8xxx_pwrkey*pwrkey = _pwrkey;
if (pwrkey->press== true) {
pwrkey->press = false;
return IRQ_HANDLED;
} else {
pwrkey->press = true;
}
input_report_key(pwrkey->pwr,KEY_POWER, 1);
//PowerKey按下事件,進入input.c中的inputEvent函數
input_sync(pwrkey->pwr);
return IRQ_HANDLED;
}
檢測powerkey的按下,開始處理
2.input.c-inputEvent
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, intvalue)
{
unsigned long flags;
if (is_event_supported(type,dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock,flags);
input_handle_event(dev, type, code, value);
//進入input_handle_event,上下文進行中斷鎖的操作
spin_unlock_irqrestore(&dev->event_lock,flags);
}
}
進入input_handle_event,然後注冊該事件到device,交給Android的消息處理服務進行處理,其中我們開始說的reader讀取到該事件。
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code,int value)
{
int disposition;
disposition = input_get_disposition(dev,type, code, value);
if ((disposition &INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code,value);
//event 處理
if (!dev->vals)
return;
if (disposition &INPUT_PASS_TO_HANDLERS) {
struct input_value *v;
if (disposition &INPUT_SLOT) {
v =&dev->vals[dev->num_vals++];
v->type = EV_ABS;
v->code = ABS_MT_SLOT;
v->value =dev->mt->slot;
}
v = &dev->vals[dev->num_vals++];
v->type = type;
v->code = code;
v->value = value;
}
if (disposition &INPUT_FLUSH) {
if (dev->num_vals >= 2)
input_pass_values(dev,dev->vals, dev->num_vals);
dev->num_vals = 0;
} else if (dev->num_vals >=dev->max_vals - 2) {
dev->vals[dev->num_vals++]= input_value_sync;
input_pass_values(dev,dev->vals, dev->num_vals);
dev->num_vals = 0;
}
}
inputread在讀取到有keyboard事件上報後,會調用到keydispatch的notifykey,緊接着調用interceptKeyBeforeQueueing,最終傳回到phonewindowmanager. interceptKeyBeforeQueueing(kernel相關的内容待更新、消息服務内容待更新)
kernel層工作 總結:
1.powerkey事件存儲
2.消息服務讀取powerkey事件
3.消息服務将keyevent進行傳遞
Java層關機
3.Phonewindowmanager.java-interceptKeyBeforeQueueing
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
…
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
final int keyCode = event.getKeyCode();
…
// Handle special keys.
switch (keyCode) {
…
case KeyEvent.KEYCODE_POWER: {
result &=~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-upwill be handled separately
if (down) {
interceptPowerKeyDown(event, interactive);
//進入interceptPowerKeyDown,上文處理各種消息來源,以及特殊按鍵功能
} else {
interceptPowerKeyUp(event,interactive, canceled);
}
break;
}
if (useHapticFeedback) {
performHapticFeedbackLw(null, HapticFeedbackConstants.VIRTUAL_KEY, false);
}
if (isWakeKey) {
wakeUp(event.getEventTime(),mAllowTheaterModeWakeFromKey,android.policy:KEY");
}
return result;
}
該方法主要完成各種按鍵消息的處理,包括按下電源鍵與音量下鍵進行抓屏、來電時按電源鍵啟動靜音模式等。在該方法根據條件處理按下電源鍵後的特殊任務,然後調用interceptPowerKeyDown進行進一步的處理。
4. interceptPowerKeyDown方法
private voidinterceptPowerKeyDown(KeyEvent event, boolean interactive) {
// 持有鎖
if(!mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.acquire();
}
// 多次按下,隻處理一次
if(mPowerKeyPressCounter != 0) {
mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
}
// Detect userpressing the power button in panic when an application has
// taken overthe whole screen.
boolean panic =mImmersiveModeConfirmation.onPowerKeyDown(interactive,
SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags));
if (panic) {
mHandler.post(mHiddenNavPanic);
}
// Latch power key stateto detect screenshot chord.
if (interactive&& !mScreenshotChordPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mScreenshotChordPowerKeyTriggered = true;
mScreenshotChordPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
}
//來電時powerKey,停止響鈴
TelecomManagertelecomManager = 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" isenabled,
// the Power button will hang up any current active call.
hungUp = telecomManager.endCall();
}
}
mPowerKeyHandled =hungUp || mScreenshotChordVolumeDownKeyTriggered
||mScreenshotChordVolumeUpKeyTriggered;
if (!mPowerKeyHandled) {
if (interactive) {
// When interactive, we're already awake.
// Wait for a long press or for the buttonto be released to decide what to do.
if(hasLongPressOnPowerBehavior()) {
Message msg =mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
}
} else{
wakeUpFromPowerKey(event.getDownTime());
if(mSupportLongPressPowerWhenNonInteractive &&hasLongPressOnPowerBehavior()) {
Message msg =mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
//PowerKey 的事件傳遞到handler進行處理
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
mBeganFromNonInteractive = true;
}else {
final int maxCount = getMaxMultiPressPowerCount();
if (maxCount <= 1) {
mPowerKeyHandled = true;
} else {
mBeganFromNonInteractive = true;
}
}
}
}
}
該方法中主要處理電源鍵按下後的一下情況,比如電源鍵松開之前一直亮屏、取消多次按鍵時的逾時消息、當設定後按下電源鍵後結束響鈴,挂斷電話等。如果電源鍵按下的處理情況還沒有處理時,解會分長按、短按和多次按來進行處理,此處我們隻關心長按電源鍵,是以會進入mHandler.sendMessageDelayed的方法,而mHandler的類型是PolicyHandler,在初始化PhoneWindowManager時對其進行了指派。調用sendMessageDelayed方法發送消息,最終會在PolicyHandler的handleMessage進行處理該消息。
5.PolicyHandler 函數
private class PolicyHandler extends Handler {
@Override
public voidhandleMessage(Message msg) {
switch(msg.what) {
…
case MSG_POWER_LONG_PRESS:
powerLongPress();
//處理函數進入powerLongPress,上下文中處理不同按鍵的邏輯
break;
…
}
}
}
該函數很簡單,就是處理各種消息,這裡大部分都是鍵盤按鍵消息,我們關心的長按電源鍵的消息處理。由源碼可知,當PolicyHandler接收到MSG_POWER_LONG_PRESS消息之後,将會進入powerLongPress方法做進一步的處理。
6.powerLongPress 函數
private void powerLongPress() {
final intbehavior = getResolvedLongPressOnPowerBehavior();
switch(behavior) {
…
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
if(!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
performAuditoryFeedbackForAccessibilityIfNeed();
}
showGlobalActionsInternal();
//進入showGlobalActionsInternal,上下文針對不同的powerkey事件進行處理
break;
caseLONG_PRESS_POWER_SHUT_OFF:
caseLONG_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;
}
}
在getResolvedLongPressOnPowerBehavior方法中,如果是工廠模式測試時,傳回LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM,否則傳回mLongPressOnPowerBehavior成員變量,而該成員變量是在PhoneWindowManager初始化時進行設定
mLongPressOnPowerBehavior =mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
由mLongPressOnPowerBehavior成員變量初始化可知,getResolvedLongPressOnPowerBehavior方法中最終傳回的是config_longPressOnPowerBehavior的值,而該值是在/framework/base/core/res/values/config.xml中進行配置的,在該配置檔案中将config_longPressOnPowerBehavior屬性值配為1代表是全局的動作;0代表不做任何動作;2表示确認後關機;3表示不确認,直接關機,這就是LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM的值,在工廠模式測試時,不需要确認直接關機。此處我們将會進入全局動作中,即會進入showGlobalActionsInternal方法,進行關機對話框的顯示。
7. showGlobalActionsInternal函數
void showGlobalActionsInternal() {
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
if(mGlobalActions == null) {
mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
}
final booleankeyguardShowing = isKeyguardShowingAndNotOccluded();
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
//進入showdialog函數,顯示關機對話框
if(keyguardShowing) {
// since it took two seconds of long press tobring this up,
// poke the wake lock so they have sometime to see the dialog.
mPowerManager.userActivity(SystemClock.uptimeMillis(),false);
}
}
該函數中首先調用sendCloseSystemWindows函數,發送由于全局關機動作的原因,最終會調用ActivityManagerService類的closeSystemDialogs函數關閉其他的系統對話框。利用單例模式建立GlobalActions對象,并儲存到其成員變量mGlobalActions中,最終會調用GlobalActions的showDialog方法進行顯示關機對話框。
8.GlobalActions.java-showdialog 函數
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 previousdialog completes
mHandler.sendEmptyMessage(MESSAGE_SHOW);
} else {
handleShow();
//進入handleshow,上文處理keyguard是否顯示,顯示則推遲處理。
}
}
該方法中主要是判斷是否會顯示keyguard,如果之前已經有全局對話框顯示,則發生延遲消息,以便其顯示完後最終關閉,如果是第一次啟動全局對話框,則會進入handleShow方法中進行處理。
9.handleshow 函數
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 {
WindowManager.LayoutParams attrs =mDialog.getWindow().getAttributes();
attrs.setTitle("GlobalActions");
mDialog.getWindow().setAttributes(attrs);
mDialog.show();
mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
}
}
在該方法中,首先調用awakenIfNecessary方法進行了螢幕喚醒,然後調用createDialog()建立全局關機對話框,當對話框建立完成後,調用prepareDialog方法進行keyguard視窗風格樣式的設定,最後會進行我們全局關機對匡樣式進行判斷,如果是隻有一個item,則會通過onPress方法進行處理,否則會進行系統UI顯示的設定。此處我們進入creatDialog方法進行建立全局關機對話框。
10.PowerAction 點選函數
private final class PowerAction extends SinglePressAction
implementsLongPressAction {
…
public void onPress() {
// shutdownby making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown(false );
//mWindowManagerFuncs實際上是windowmanagerservice的對象,進入shutdown
}
}
如果長按會進入過PowerAction的onLongPress函數,最後會進入到mWindowManagerFuncs.rebootSafeMode函數中;如果是短按關機Aciton會進入到PowerAction的onPress函數,最後進入到mWindowManagerFuncs.shutDown方法進行處理。在PhoneWindowManager的初始化過程可知,mWindowManagerFuncs被指派為WindowManagerService,是以會調用WindowManagerService的shutdown方法。
11.windowmanagerservice.java-shutdown 函數
public void shutdown(boolean confirm) {
ShutdownThread.shutdown(mContext,confirm);
//調用shutdownThread的shutdown方法
}
12.shutdownThread.java-shutdown 方法
public static void shutdown(final Context context,boolean confirm) {
mReboot =false;
mRebootSafeMode = false;
Log.d(TAG,"!!! Request to shutdown !!!");
if (mSpew){
StackTraceElement[] stack = new Throwable().getStackTrace();
for(StackTraceElement element : stack)
{
Log.d(TAG, " |----" +element.toString());
}
}
if(SystemProperties.getBoolean("ro.monkey", false)) {
Log.d(TAG, "Cannot request to shutdown when Monkey is running,returning.");
return;
}
shutdownInner(context, confirm);
//進一步調用shutdowninner,上文判斷,若在monkey,不進行關機操作
}
在該方法中隻是初始化了一些參數,最後由shutdownInner方法進行處理,參數confirm來設定是否要彈出關機對話框。
13.shutdowninner 方法
static void shutdownInner(final Context context, booleanconfirm) {
…
if(confirm) {
…
} else {
beginShutdownSequence(context);
//開始進行關機準備,進入beginShutdownSequence
}
}
在該方法中,會通過傳進來的confirm參數來判斷是否要顯示關機對話框,如果顯示會設定關鍵對話框,如果不顯示直接關機,無論顯示與否,最後會進入到beginShutdownSequence方法,做進一步的關機處理。
14.beginshutdownsequence 函數
private static void beginShutdownSequence(Contextcontext) {
// Throw up a system dialog to indicate thedevice is rebooting / shutting down.
ProgressDialog pd = new ProgressDialog(context);
if(PowerManager.REBOOT_RECOVERY.equals(mRebootReason)) {
mRebootUpdate = new File(UNCRYPT_PACKAGE_FILE).exists();
if(mRebootUpdate) {
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_update_title));
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_update_prepare));
pd.setMax(100);
pd.setProgressNumberFormat(null);
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.setProgress(0);
pd.setIndeterminate(false);
} else{
//Factory reset path. Set the dialog message accordingly.
pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
pd.setMessage(context.getText(
com.android.internal.R.string.reboot_to_reset_message));
pd.setIndeterminate(true);
}
} else {
pd.setTitle(context.getText(com.android.internal.R.string.power_off));
pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
pd.setIndeterminate(true);
}
pd.setCancelable(false);
pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
// startthe thread that initiates shutdown
sInstance.mHandler = new Handler() {
};
beginAnimationTime = 0;
booleanmShutOffAnimation = configShutdownAnimation(context);
intscreenTurnOffTime = getScreenTurnOffTime(context);
synchronized (mEnableAnimatingSync) {
if(mEnableAnimating) {
if(mShutOffAnimation) {
Log.d(TAG, "mIBootAnim.isCustBootAnim() is true");
bootanimCust();//播放動畫
}else {
pd.show();
sInstance.mProgressDialog = pd;
}
sInstance.mHandler.postDelayed(mDelayDim, screenTurnOffTime);
}
}
if(sInstance.getState() != Thread.State.NEW || sInstance.isAlive()) {
…
} else {
sInstance.start();//進入線程run方法
}
}
該方法主要初始化一些關機操作,比如擷取audio,停止啟動應用程式播放music等,最後調用ShutdownThread的start函數來啟動關機線程,進入到ShutdownThread線程的run方法中。
15.run 函數
public void run() {
BroadcastReceiver br = new BroadcastReceiver() {
@Overridepublic void onReceive(Context context, Intent intent) {
// We don'tallow apps to cancel this, so ignore the result.
actionDone();
}
};
{
Stringreason = (mReboot ? "1" : "0") + (mRebootReason != null ?mRebootReason : "");
SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
//記錄關機原因
}
if(mRebootSafeMode) {
SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
}
Log.i(TAG,"Sending shutdown broadcast...");
// Firstsend the high-level shut down broadcast.
mActionDone= false;
Intentintent = new Intent(Intent.ACTION_SHUTDOWN);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
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 timedout");
break;
}
try{
mActionDoneSync.wait(delay);
}catch (InterruptedException e) {
}
}
}
Log.i(TAG,"Shutting down activity manager...");
//關閉activitymanager
finalIActivityManager am =
ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
if (am !=null) {
try {
am.shutdown(MAX_BROADCAST_TIME);
} catch(RemoteException e) {
}
}
Log.i(TAG,"Shutting down package manager...");
//關閉packagemanager
finalPackageManagerService pm = (PackageManagerService)
ServiceManager.getService("package");
if (pm !=null) {
pm.shutdown();
}
String shutDownFile = null;
//showShutdownAnimation() is called from here to sync
//music andanimation properly
if(checkAnimationFileExist()) {
lockDevice();
showShutdownAnimation();
if(!isSilentMode()
&& (shutDownFile = getShutdownMusicFilePath()) != null) {
isShutdownMusicPlaying= true;
shutdownMusicHandler.obtainMessage(0, shutDownFile).sendToTarget();
}
}
Log.i(TAG,"wait for shutdown music");
final longendTimeForMusic = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
synchronized (mActionDoneSync) {
while(isShutdownMusicPlaying) {
long delay = endTimeForMusic - SystemClock.elapsedRealtime();
if(delay <= 0) {
Log.w(TAG, "play shutdown music timeout!");
break;
}
try{
mActionDoneSync.wait(delay);
}catch (InterruptedException e) {
}
}
if(!isShutdownMusicPlaying) {
Log.i(TAG, "play shutdown music complete.");
}
}
//關閉通信相關内容
// Shutdownradios.
shutdownRadios(MAX_RADIO_WAIT_TIME);
// ShutdownMountService to ensure media is in a safe state
IMountShutdownObserver observer = newIMountShutdownObserver.Stub() {
public void onShutDownComplete(int statusCode) throwsRemoteException {
Log.w(TAG, "Result code " + statusCode + " fromMountService.shutdown");
actionDone();
}
};
Log.i(TAG,"Shutting down MountService");
//關閉挂載服務
// Setinitial variables and time out time.
mActionDone= false;
final longendShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
synchronized (mActionDoneSync) {
try {
final IMountService mount = IMountService.Stub.asInterface(
ServiceManager.checkService("mount"));
if(mount != null) {
mount.shutdown(observer);
}else {
Log.w(TAG, "MountService unavailable for shutdown");
}
} catch(Exception e) {
Log.e(TAG, "Exception during MountService shutdown", e);
}
while(!mActionDone) {
long delay = endShutTime - SystemClock.elapsedRealtime();
if(delay <= 0) {
Log.w(TAG, "Shutdown wait timed out");
break;
}
try{
mActionDoneSync.wait(delay);
}catch (InterruptedException e) {
}
}
}
//進入rebootorshutdown函數
rebootOrShutdown(mReboot, mRebootReason);
}
該函數中主要完成建立接收關機的廣播,設定關機原因,關閉ActivityManagerService、PackagerManagerService、MountServie等,最後進入rebootOrshutDown進行關機操作。
16.rebootorshutdown 函數
public staticvoid rebootOrShutdown(boolean reboot, String reason) {
deviceRebootOrShutdown(reboot, reason);
//檢查廠商的關機處理
if (reboot){
Log.i(TAG, "Rebooting, reason: " + reason);
PowerManagerService.lowLevelReboot(reason);
Log.e(TAG, "Reboot failed, will attempt shutdown instead");
} else if(SHUTDOWN_VIBRATE_MS > 0) {
//vibrate before shutting down
//關機震動
Vibrator vibrator = new SystemVibrator();
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) {
}
}
// Shutdownpower
Log.i(TAG,"Performing low-level shutdown...");
PowerManagerService.lowLevelShutdown();
//進入lowlevelshutdown函數
}
在該方法中,首先調用deviceRebootOrShutdown方法來查找OEM關機相關類,然後判斷是否是重新開機,如果重新開機PowerManagerService.lowLevelReboot方法進行重新開機;否則如果是關機前有震動,則會建立Vibrator對象,并調用其vibrate方法執行震動操作,最後進入到PowerManagerService.lowLevelShutdown方法中執行關機操作。
17.powermanagerservice.java- lowLevelShutdown 函數
public static void lowLevelShutdown() {
SystemProperties.set("sys.powerctl", "shutdown");
//設定關機屬性值,進入systemproperties.set函數
}
在該方法中調用,SystemProperties.set方法,修改sys.powerctl的屬性值為shutdown
18. SystemProperties.java-set 函數
public static void set(String key, String val) {
//判斷傳入值的合法性
if(key.length() > PROP_NAME_MAX) {
throw newIllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
if (val !=null && val.length() > PROP_VALUE_MAX) {
thrownew IllegalArgumentException("val.length > " +
PROP_VALUE_MAX);
}
native_set(key, val);
//調用底層native_set函數
}
該方法比較簡單,隻是判斷了一下key和value的長度後,就進入本地方法native_set中。
Java層工作總結:
1.PhoneWindowManger負責接收由InputManagerService的發放的按鍵資訊,本例中将會擷取長
按Power鍵資訊。
2.PolicyHandler發送長按Power鍵資訊,并在其handleMessage方法中做相應的處理。
3.PhoneWindowManger的powerLongPress處理長按Power鍵的不同情況,1代表是全局的動作;
0代表不做任動作;2表示确認後關機;3表示不确認,直接關機。
4.GlobalActionsDialog調用createDialog建立關鍵對話框,将關機選擇、重新開機選擇、飛行模
式選擇以封裝的Action對象添加在擴充卡清單中。封裝完成Action後就是建立關機對話框,采用MyAdapter擴充卡儲存這些比對。
5.WindowManagerService的shutDown方法,最終會進入到shutDownThread線程的rebootOrs
hutDown方法成關機操作。
6.最後進入到PowerManagerServie的lowLevelShutdown方法,進入到SystemProperties.se
t方法,将“sys.powerctl”屬性設定為“shutdown”最後進入native層完成關機操作。
Native層關機
19.android_os_properties_set.cpp-native_set 函數
static void SystemProperties_set(JNIEnv *env, jobjectclazz,
jstringkeyJ, jstring valJ)
{
int err;
const char*key;
const char*val;
if (keyJ ==NULL) {
jniThrowNullPointerException(env, "key must not be null.");
return ;
}
key = env->GetStringUTFChars(keyJ, NULL);
if (valJ ==NULL) {
val ="";
} else {
val =env->GetStringUTFChars(valJ, NULL);
}
err = property_set(key, val);
//調用perperty_set方法,進入native關機
env->ReleaseStringUTFChars(keyJ, key);
if (valJ !=NULL) {
env->ReleaseStringUTFChars(valJ, val);
}
if (err < 0){
jniThrowException(env, "java/lang/RuntimeException",
"failed to set system property");
}
}
20.properties_set.cpp-property_set 函數
int property_set(const char *key, const char *value)
{
return__system_property_set(key, value);
//進一步調用 __system_property_set函數
}
21.system_properties.cpp-__system_property_set 函數
int __system_property_set(constchar *key, const char *value)
{
if (key == 0) return -1;
if (value == 0) value = "";
if (strlen(key) >= PROP_NAME_MAX) return-1;
if (strlen(value) >= PROP_VALUE_MAX)return -1;
prop_msg msg;
//将關機屬性的name,value,size 放入到msg中
memset(&msg, 0, sizeof msg);
msg.cmd = PROP_MSG_SETPROP;
strlcpy(msg.name, key, sizeof msg.name);
strlcpy(msg.value, value, sizeofmsg.value);
const int err =send_prop_msg(&msg);
//将msg發送到服務端,property的服務程序在init.c中運作
//觸發 關機屬性值的設定 調用dopwrctrl
if (err < 0) {
return err;
}
return 0;
}
22.init.rc
on property:sys.powerctl=*
//表示當sys.powerctl被設定成任意值時觸發下面的動作
powerctl${sys.powerctl}
init程序是Android系統的第一個程序,是由Linux核心啟動。init程序主要作用是兩個:一個是解析init.rc以及init{hardware}.rc等rc檔案;
onproperty:sys.powerctl=*
powerctl${sys.powerctl}
在init.rc檔案主要由以on開頭和service開頭,分别代表動作和服務;init程序會調用init_parser.c的parse_config函數來解析這些rc檔案,最終會生成動作清單和服務清單,動作清單的定義在keywords.h中
int do_powerctl(int nargs, char**args);
#endif
……
KEYWORD(powerctl, COMMAND, 1,do_powerctl)
……
#ifdef __MAKE_KEYWORD_ENUM__
KEYWORD_COUNT,
};
Init程序通過drain_action_queue函數來解析動作清單和服務清單;通過設定init.rc中動作參數進而執行動作清單中對應的函數。另一個是初始化系統屬性,init程序可以通過property_init函數初始化系統屬性,并通過property_set函數來設定系統屬性。并将設定的參數作為參數傳入動作清單對應函數的參數中。是以在關機過程Step17中,通過property_set函數,将sys.powerctl屬性值設定為“shutdown”,并交個動作清單中對應的函數do_powerctl來處理。
23.bulltins.c –do_powerctl 函數
int do_powerctl(int nargs, char **args)
{
charcommand[PROP_VALUE_MAX];
int res;
int len = 0;
int cmd = 0;
char*reboot_target;
res =expand_props(command, args[1], sizeof(command));
if (res) {
ERROR("powerctl: cannot expand '%s'\n", args[1]);
return-EINVAL;
}
if(strncmp(command, "shutdown", 8) == 0) {
cmd =ANDROID_RB_POWEROFF;
len = 8;
} else if(strncmp(command, "reboot", 6) == 0) {
cmd =ANDROID_RB_RESTART2;
len = 6;
} else {
ERROR("powerctl: unrecognized command '%s'\n", command);
return-EINVAL;
}
if(command[len] == ',') {
reboot_target = &command[len + 1];
} else if(command[len] == '\0') {
reboot_target = "";
} else {
ERROR("powerctl: unrecognized reboot target '%s'\n",&command[len]);
return-EINVAL;
}
returnandroid_reboot(cmd, 0, reboot_target);
//進入android_reboot 函數
}
該函數中首先讀取Java層設定的該屬性的值,我們設定為“shutdown”,是以會将cmd設定為ANDROID_RB_POWEROFF,長度為8;如果為重新開機,則cmd設定為ANDROID_RB_RESTART2。否則就是顯示指令錯誤。是以我們知道sys.powerctl屬性隻有兩個值,一個是shutdown(關機);一個是reboot(重新開機)。最後将cmd指令作為參數傳入android_reboot函數,做最後的關機操作。
24.Android_reboot.c-android_reboot 函數
int android_reboot(int cmd, int flags UNUSED, char *arg)
{
int ret;
sync();
remount_ro();
switch (cmd) {
caseANDROID_RB_RESTART:
ret =reboot(RB_AUTOBOOT);
break;
caseANDROID_RB_POWEROFF:
ret =reboot(RB_POWER_OFF);
//進入reboot 函數
break;
case ANDROID_RB_RESTART2:
ret =syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
LINUX_REBOOT_CMD_RESTART2, arg);
break;
default:
ret =-1;
}
return ret;
}
該函數首先是做了一些關機重新開機前的預處理工作,sync()作用是将緩存中的資訊寫入磁盤,以免程式異常結束導緻檔案被損壞,linux系統關機前會做幾次這樣的動作;而remount_ro()作用是強制将檔案系統挂載為隻讀,不再允許任何寫入操作,同時會通過檢查/proc/mounts的裝置狀态來确認是否目前的所有寫入工作已經完成,這個檢查過程是阻塞操作。
cmd參數中ANDROID_RB_RESTART為普通關機,reason為RB_AUTOBOOT;ANDROID_RB_POWEROFF,無需reason,直接調用reboot進行關機;帶參數的特殊重新開機ANDROID_RB_RESTART2,reason 将為預設值-1。本例中是reboot “shutdown”,則在Step18中傳入的cmd參數為ANDROID_RB_POWEROFF,則會進入reboot(RB_POWER_OFF)函數進行處理。這些cmd值是在/bionic/libc/include/sys/Reboot.h中
#define RB_AUTOBOOT LINUX_REBOOT_CMD_RESTART
#define RB_HALT_SYSTEM LINUX_REBOOT_CMD_HALT
#define RB_ENABLE_CAD LINUX_REBOOT_CMD_CAD_ON
#define RB_DISABLE_CAD LINUX_REBOOT_CMD_CAD_OFF
#define RB_POWER_OFF LINUX_REBOOT_CMD_POWER_OFF
而LINUX_REBOOT_CMD_XXXX是在/binoic/libc/kernel/uapi/linux/Reboot.h中:我們直接進入到reboot(RB_POWER_OFF)函數中做關機操作。
#define LINUX_REBOOT_CMD_RESTART0x01234567
#define LINUX_REBOOT_CMD_HALT0xCDEF0123
#define LINUX_REBOOT_CMD_CAD_ON0x89ABCDEF
#define LINUX_REBOOT_CMD_CAD_OFF0x00000000
#define LINUX_REBOOT_CMD_POWER_OFF0x4321FEDC
#define LINUX_REBOOT_CMD_RESTART20xA1B2C3D4
#defineLINUX_REBOOT_CMD_SW_SUSPEND 0xD000FCE2
#define LINUX_REBOOT_CMD_KEXEC0x45584543
#endif
25.reboot.cpp – reboot 函數
include <unistd.h>
#include <sys/reboot.h>
extern "C" int __reboot(int, int, int, void*);
int reboot(int mode) {
return__reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, mode, NULL);
//表明這是外部定義實作的一個C函數 進入kernel層關機
}
該函數中,調用Kernel層的_reboot函數做進一步分析。該函數在/bionic/libc/bionic/Reboot.cpp檔案中。另外,在該檔案中有extern"C" int __reboot,該表示在外部檔案定義的__reboot函數,并且是以C語言方式進行編譯連接配接,是以__reboot函數是c語言函數。
Native層工作總結 :
1.采用property_set函數來設定系統屬性的值,通過init程序解析init.rc檔案的生成的動作
的清單,最終根據傳進去的屬性值(“shutdown”),最終調用do_powerctl函數做關機的操作。
2.Android_reboot通cmd指令值做各種操作,cmd參數是在這些cmd值是在/bionic/libc/incl
ude/sys/Reboot.h中
3. 通過reboot中C++函數,最終調用到kernel層__reboot函數做kernel層的關機操作。
Kernel層關機
26.__reboot.s-reboot 函數
#include <private/bionic_asm.h>
ENTRY(__reboot)
mov ip, r7
ldr r7, =__NR_reboot
swi #0
mov r7, ip
cmn r0, #(MAX_ERRNO + 1)
bxls lr
neg r0, r0
b __set_errno_internal
END(__reboot)
發現__reboot函數最終映射到__NR_reboot,而__NR_reboot在檔案/development/ndk/platforms/android-19/arch-mips/include/sys/Linux-syscalls.h中定義。
#define__NR_reboot (__NR_SYSCALL_BASE + 88)
其被指定了一個固定的偏移量,其被指定了一個固定的偏移量,在被調用的時候就是通過這個偏移量去核心中尋找對應的入口的,由此可見,核心中一定有着相同的定義,否則将不能成功調用。核心中對syscall偏移量的定義在核心源碼中的arch/arm/include/asm/unistd.h,相關資訊完全一緻。已經找到了核心中的對應映射,那麼下一步就要去找尋真正的實作函數了,/Include/asm-generic/unistd.h中可以找到核心對__NR_reboot的syscall函數映射。
#define __NR_setpriority 140
__SYSCALL(__NR_setpriority,sys_setpriority)
#define __NR_getpriority 141
__SYSCALL(__NR_getpriority,sys_getpriority)
#define __NR_reboot 142
__SYSCALL(__NR_reboot, sys_reboot)
由定義可知,__NR_reboot被映射到sys_reboot函數,該函數的定義在/kernel/inlude/linux/syscalls.h中
asmlinkagelong sys_reboot(int magic1, int magic2, unsigned int cmd,void __user *arg);
我們在檔案sys.c中沒有找到sys_reboot函數,但是發現有這樣一個函數SYSCALL_DEFINE4(reboot,int, magic1, int, magic2, unsigned int, cmd, void __user *, arg)參數和sys_reboot函數基本相同,我們/kernel/inlude/linux/syscalls.h檔案中有這樣的定義:
#define SYSCALL_DEFINE4(name, ...)SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname,...)
#define __SYSCALL_DEFINEx(x, name,...) asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));
整合後的結果如下:
#defineSYSCALL_DEFINE4(name, ...) asmlinkagelong sys##_name(__SC_DECL##4(__VA_ARGS__))
可知就是sys_reboot函數,是以我們直接進入到SYSCALL_DEFINE4函數
27. sys.c-SYSCALL_DEFINE4 函數
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2,unsigned int, cmd,
void __user*, arg)
{
structpid_namespace *pid_ns = task_active_pid_ns(current);
charbuffer[256];
int ret = 0;
if(!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))
return-EPERM;
if (magic1 !=LINUX_REBOOT_MAGIC1 ||
(magic2 != LINUX_REBOOT_MAGIC2 &&
magic2 != LINUX_REBOOT_MAGIC2A&&
magic2 !=LINUX_REBOOT_MAGIC2B &&
magic2 != LINUX_REBOOT_MAGIC2C))
return-EINVAL;
ret =reboot_pid_ns(pid_ns, cmd);
if (ret)
return ret;
if ((cmd ==LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
cmd =LINUX_REBOOT_CMD_HALT;
mutex_lock(&reboot_mutex);
switch (cmd) {
caseLINUX_REBOOT_CMD_RESTART:
kernel_restart(NULL);
break;
caseLINUX_REBOOT_CMD_CAD_ON:
C_A_D = 1;
break;
caseLINUX_REBOOT_CMD_CAD_OFF:
C_A_D = 0;
break;
caseLINUX_REBOOT_CMD_HALT:
kernel_halt();
do_exit(0);
panic("cannothalt");
caseLINUX_REBOOT_CMD_POWER_OFF:
kernel_power_off();
// kernel 關閉電源
do_exit(0);
break;
caseLINUX_REBOOT_CMD_RESTART2:
if(strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1) < 0) {
ret =-EFAULT;
break;
}
buffer[sizeof(buffer)- 1] = '\0';
kernel_restart(buffer);
break;
#ifdef CONFIG_KEXEC
caseLINUX_REBOOT_CMD_KEXEC:
ret =kernel_kexec();
break;
#endif
#ifdef CONFIG_HIBERNATION
caseLINUX_REBOOT_CMD_SW_SUSPEND:
ret =hibernate();
break;
#endif
default:
ret =-EINVAL;
break;
}
mutex_unlock(&reboot_mutex);
return ret;
}
在該函數中,首先檢測權限問題,隻有超級使用者才可以執行重新開機操作,否則傳回權限錯誤,對應的權限清單在/kernel/include/uapi/linux/Capability.h檔案中。CAP_SYS_BOOT的值為22,随後對magicnumber進行了校驗。接下來做了一個判斷就是如果使用者要求關機,而pm_power_off為空,則就把使用者的關機指令轉化為挂起。pm_power_off的定義位置在/kernel/arch/arm/kernel/Process.c
void (*pm_power_off)(void);
EXPORT_SYMBOL(pm_power_off);
可知pm_power_off為函數指針,而且做了全局操作,整個kernel都可以調用它。最後會調用kernel_power_off函數完成關機操作。
28.kernel_power_off 函數
void kernel_power_off(void)
{
kernel_shutdown_prepare(SYSTEM_POWER_OFF);//準備shutdown
if(pm_power_off_prepare)
pm_power_off_prepare();
migrate_to_reboot_cpu();
syscore_shutdown();
printk(KERN_EMERG"Power down.\n");
kmsg_dump(KMSG_DUMP_POWEROFF);
machine_power_off();//machine關閉電源
}
Kernel層工作總結:
1. 調用syscalldefine 關機
2. 存儲相關資訊并關閉電源