blog

Androidシャットダウンの詳細分析

最終的なシャットダウンは、Linuxカーネルで完了する必要があり、実際には、最下層は実際にはLinuxです。すべての呼び出しは、Androidの仮想マシンDalvikとDalvikとサーバーとのカーネ...

Feb 7, 2020 · 22 min. read
シェア

アンドロイドのシャットダウンシークエンス

  • 電源ボタンが長押しされたとき、電話機からは具体的に何が送信されるのですか?
  • シャットダウンの順序は?
  • Androidのシャットダウン・シーケンスはLinuxのデスクトップ・システムとどう違うのですか?
  • シャットダウンメニューの変更方法を教えてください。

次の図は、Androidのシャットダウンシーケンスについて詳しく説明したものです。

ステップ1:電源ボタンを半秒間長押しします。

以下は、電源ボタンの長押しイベントを処理するコードスニペットです。

/** {@inheritDoc} */ 
@Override 
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { 
.... 
.... 
.... 
case KeyEvent.KEYCODE_POWER: { 
     result &= ~ACTION_PASS_TO_USER; 
       if (down) { 
         if (isScreenOn && !mPowerKeyTriggered 
               && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { 
                   mPowerKeyTriggered = true; 
                   mPowerKeyTime = event.getDownTime(); 
                   interceptScreenshotChord(); 
            } 
               ITelephony telephonyService = getTelephonyService(); 
                boolean hungUp = false; 
               if (telephonyService != null) { 
                   try { 
                       if (telephonyService.isRinging()) { 
                           // 着信が鳴ったときに電源ボタンが押されると、システムは着信アラートをオフにする 
                            telephonyService.silenceRinger(); 
                       } else if ((mIncallPowerBehavior 
                                & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 
                               && telephonyService.isOffhook()) { 
                            // 通話中で電源ボタンのハングアップオプションが有効になっている場合、電源ボタンを押すと通話が終了する。 
                            hungUp = telephonyService.endCall(); 
                       } 
                   } catch (RemoteException ex) { 
                        Log.w(TAG, "ITelephony threw RemoteException", ex); 
                   } 
               } 
               interceptPowerKeyDown(!isScreenOn || hungUp 
                       || mVolumeDownKeyTriggered || mVolumeUpKeyTriggered); 
           } else { 
               mPowerKeyTriggered = false; 
               cancelPendingScreenshotChordAction(); 
               if (interceptPowerKeyUp(canceled || mPendingPowerKeyUpCanceled)) { 
                   result = (result & ~ACTION_WAKE_UP) | ACTION_GO_TO_SLEEP; 
               } 
               mPendingPowerKeyUpCanceled = false; 
           } 
          break; 
      } 
.... 
.... 
.... 
} 

上記のコードには、着信の消音、スクリーンショットの撮影、電源のオフなど、さまざまなシナリオにおける電源ボタンの長押しの処理が含まれています。 システムは、電源ボタンが押し続けられている時間と、関連するキーがどのように使用されているかに基づいて、現在のユーザーアクションを適切に処理する方法を決定します。 電源キーが押され、スクリーンショットがトリガされない場合、interceptPowerKeyDown が呼び出され、他のキー応答はトリガされません。

次のコードは、500ミリ秒のタイムアウトイベントがトリガーされたときにmPowerLongPressスレッドを開始するコールバック関数を登録するinterceptPowerKeyDown関数の内容を示しています。

private void interceptPowerKeyDown(boolean handled) { 
  mPowerKeyHandled = handled; 
  if (!handled) { 
       mHandler.postDelayed(mPowerLongPress, ViewConfiguration.getGlobalActionKeyTimeout()); 
  } 
} 

mPowerLongPressスレッドは以下のように実装されています。

private final Runnable mPowerLongPress = new Runnable() { 
        @Override 
        public void run() { 
            // The context isn't read 
            if (mLongPressOnPowerBehavior < 0) { 
                mLongPressOnPowerBehavior = mContext.getResources().getInteger( 
                        com.android.internal.R.integer.config_longPressOnPowerBehavior); 
            } 
            int resolvedBehavior = mLongPressOnPowerBehavior; 
            if (FactoryTest.isLongPressOnPowerOffEnabled()) { 
                resolvedBehavior = LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; 
            } 
  
            switch (resolvedBehavior) { 
            case LONG_PRESS_POWER_NOTHING: 
                break; 
            case LONG_PRESS_POWER_GLOBAL_ACTIONS: 
                mPowerKeyHandled = true; 
                if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) { 
                    performAuditoryFeedbackForAccessibilityIfNeed(); 
                } 
                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); 
                showGlobalActionsDialog(); 
                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(resolvedBehavior == LONG_PRESS_POWER_SHUT_OFF); 
                break; 
            } 
        } 
    }; 

このクラスは、Androidディストリビューションによって異なりますが、通常、電源オフ、フライトモード、スクリーンショットなどのシャットダウンオプションを含むダイアログボックスを表示する役割を果たします。これらのオプションはAndroidディストリビューションによって異なり、通常は電源オフ、フライトモード、スクリーンショットが含まれます。GlobalActionsクラスは、システムでサポートされている現在のメニューに基づいてダイアログを作成するshowdialogメソッドを実装しています。

void showGlobalActionsDialog() { 
    if (mGlobalActions == null) { 
        mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs); 
    } 
    final boolean keyguardShowing = keyguardIsShowingTq(); 
    mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned()); 
    if (keyguardShowing) { 
         // シャットダウン・ダイアログ・ボックスをアクティブにするには、電源ボタンを2秒以上長押しする必要があるため、ダイアログ・ボックスが表示された後、画面のウェイクアップ状態がロックされ、ユーザーがダイアログ・ボックスの内容を簡単に閲覧できるようになる。 
        mKeyguardMediator.userActivity(); 
    } 
} 

ステップ 4:ユーザーが「電源オフ」を選択すると、システムの制御が PhoneWindowManager に戻され、PhoneWindowManager がシャットダウンプロセスを開始します。

ステップ 5:シャットダウンのプロセスは、ShutdownThreadモジュールのshutdowninnerメソッドで開始します。このメソッドは、まず、ユーザがシャットダウンを確認するかキャンセルするかを選択できる確認ダイアログを作成します。 ユーザがシャットダウンの確認を選択した場合、システムは実際にシャットダウン・プロセスに入ります。

ステップ6:前述のように、ユーザーが確認ボタンをクリックすると、beginShutdownSequenceメソッドが呼び出され、シャットダウンシーケンスが開始されます。

private static void beginShutdownSequence(Context context) { 
        synchronized (sIsStartedGuard) { 
            if (sIsStarted) { 
                Log.d(TAG, "Shutdown sequence already running, returning."); 
                return; 
            } 
            sIsStarted = true; 
        } 
  
        // パワーダウンダイアログボックスを表示する 
        ProgressDialog pd = new ProgressDialog(context); 
        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); 
        pd.show(); 
        sInstance.mContext = context; 
        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 
        // CPUがハイバネーション状態になるのを防ぐ 
        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; 
        } 
        // ユーザー・エクスペリエンスを向上させるために、電源が切れるまで画面を起こしたままにする。 
        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; 
            } 
        } 
        // シャットダウンシーケンスを担当するスレッドを開始する 
        sInstance.mHandler = new Handler() { 
        }; 
        sInstance.start(); 
    } 

実際のシャットダウン処理を開始する関数を実行します。

public void run() { 
        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(); 
            } 
        }; 
  
        /* 
         *  写入一个系统参数,以防Android系统中的System Server 
         * (Dalvik VMと実システム・カーネルの間で動作するサーバーが、VMとカーネル間の通信を担当する)実ハードウェアが再起動する前に再起動を完了させる。 
         * 当上述情况发生时, 则在System Server完成启动后重试的重启操作。 
         */ 
        { 
            String reason = (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..."); 
  
        // モバイル通信をオフにする 
        mActionDone = false; 
        Intent intent = 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 timed out"); 
                    break; 
                } 
                try { 
                    mActionDoneSync.wait(delay); 
                } catch (InterruptedException e) { 
                } 
            } 
        } 
  
        Log.i(TAG, "Shutting down activity manager..."); 
  
        final IActivityManager am = 
            ActivityManagerNative.asInterface(ServiceManager.checkService("activity")); 
        if (am != null) { 
            try { 
                am.shutdown(MAX_BROADCAST_TIME); 
            } catch (RemoteException e) { 
            } 
        } 
  
        // モバイル通信をオフにする 
        shutdownRadios(MAX_RADIO_WAIT_TIME); 
  
        // 外部メモリーカードを安全に取り外す 
        IMountShutdownObserver observer = new IMountShutdownObserver.Stub() { 
            public void onShutDownComplete(int statusCode) throws RemoteException { 
                Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown"); 
                actionDone(); 
            } 
        }; 
  
        Log.i(TAG, "Shutting down MountService"); 
  
        // 変数を初期化し、シャットダウンのタイムアウト制限を設定する。 
        mActionDone = false; 
        final long endShutTime = 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(mReboot, mRebootReason); 
    } 

ステップ7:rebootOrShutdownメソッドが呼び出されると、システム制御はまず基礎となる関数nativeShutdownに転送され、最後にandroid_reboot関数が呼び出されてシャットダウンシーケンスが完了します。

static void nativeShutdown(JNIEnv *env, jclass clazz) { 
    android_reboot(ANDROID_RB_POWEROFF, 0, 0); 
} 
Read next

Vue.js 3は公式にRC版である!

RC フェーズに入ったということは、Vue 3 のコア API と実装が安定しているということです。原則として、新しい主要な機能を導入したり、最終バージョンのリリースに向けて大幅な変更を加えたりする必要はありません。現在、ほとんどの公式フレームワークコンポーネントはv3をサポートしています。最新の状況はこちらをご覧ください。 Vueドキュメンテーションチームは、v3ドキュメントを更新し、現在、v3ドキュメント上で利用可能です!大変な作業でした...

Feb 7, 2020 · 2 min read