Android系統關機或重啓的幾種實現方式

------------------------------------------------------------------------------------------------------
       此文章僅作爲學習交流所用

       轉載或引用請務必註明原文地址:

     http://blog.csdn.net/luzhenrong45/article/details/42092007 

  或聯繫作者:[email protected]

       謝謝!                      

------------------------------------------------------------------------------------------------------

    


   前陣子工作上遇到一些關於Android系統關機或重啓的系統修改,於是,做了一些嘗試,也蒐集了一下資料,現在整理一下,做一些總結,方便學習或者日後工作的需要。

    默認的SDK並沒有提供應用開發者直接的Android系統關機或重啓的API接口,一般來講,實現Android系統的關機或重啓,需要較高的權限(系統權限甚至Root權限)。所以,在一般的APP中,如果想要實現關機或重啓功能,要麼是在App中聲明系統權限,要麼是通過某種“間接”的方式,比如廣播或反射,來間接實現系統關機或重啓。再者,就是放在源碼環境中進行編譯,這樣做有一個好處,就是可以直接調用Android中不公開的API,這是Eclipse+SDK沒法達到的效果。下面是我自己嘗試的幾種方式:

    一. 發送系統廣播方式

    Broadcast是Android的四大基本組件之一,也就是我們常說的廣播。Android系統本身就包含了許多廣播,時時刻刻在監聽着系統中註冊的每一個廣播並隨時準備響應操作。其中,就有關於關機或重啓的廣播:Intent.ACTION_REQUEST_SHUTDOWN和Intent.ACTION_REBOOT,通過發送這兩個廣播,Android就能自動接收廣播,並響應關機或重啓的操作。ACTION_REQUEST和ACTION_REBOOT是Intent.java是聲明的兩個字符串常量

   public static final String ACTION_REBOOT =
              "android.intent.action.REBOOT";
   public static final String ACTION_REQUEST_SHUTDOWN = "android.intent.action.ACTION_REQUEST_SHUTDOWN";

Intent.java位於源碼/frameworks/base/core/java/android/content/Intent.java下面。具體實現方法如下

//廣播方式關機重啓
case R.id.shutdown_btn1:
	Log.v(TAG, "broadcast->shutdown");
	Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
	intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
	//其中false換成true,會彈出是否關機的確認窗口
	startActivity(intent);
break;
case R.id.reboot_btn1:
	Log.v(TAG, "broadcast->reboot");
	Intent intent2 = new Intent(Intent.ACTION_REBOOT);
	intent2.putExtra("nowait", 1);
	intent2.putExtra("interval", 1);
	intent2.putExtra("window", 0);
	sendBroadcast(intent2);  
	break;


需要注意的幾點是:

第一,如前面所說,需要將APP提升至系統權限,具體做法是在AndroidMenifest.xml中添加如下代碼

android:sharedUserId="android.uid.system"

第二,同時需要添加關機權限

<uses-permission android:name="android.permission.SHUTDOWN" />

第三,在Eclipse中,代碼中的Intent.ACTION_REQUEST_SHUTDOWN 及 Intent.EXTRA_KEY_CONFIRM 在Eclipse IDE中報錯,還是和前面說的一樣,這兩個屬性不對上層開放,如果把項目放在源碼中進行編譯,是可以編譯通過的。

第四,由於需要在源碼中編譯項目,所以需要爲項目編寫mk文件,在項目根目錄下添加Android.mk文件,內容如下所示:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES := $(call all-java-files-under, src)

LOCAL_PACKAGE_NAME := PowerActionDemo
LOCAL_CERTIFICATE := platform

include $(BUILD_PACKAGE) 
最後,將編譯生成的apk文件,通過adb push到機器上就可以驗證功能了。


二. 通過init.rc啓動系統服務來運行sh文件

    Android啓動文件系統後調用的會調用第一個應用程序是/init,此文件一個很重要的內容就是解析了init.rc和init.xxx.rc,然後執行解析出來的任務。而init.rc,可以在系統的初始化過程中進行一些簡單的初始化操作。利用這一點,可以編寫簡單的關機或重啓的sh腳本文件,通過系統init解析,執行相應的關機或重啓操作。

1.首先,編寫關機和重啓的sh腳本。比如,新建

重啓腳本 system_reboot.sh,內容如下:

#!/system/bin/sh  
reboot
關機腳本 system_shutdown.sh

#!/system/bin/sh  
reboot -p

注意:此處關機命令並不是shutdown,而是reboot -p (如果你的android系統system/bin 目錄下存在shutdown文件,使用shutdown命令也是可以的,因爲我的系統只有reboot,所以就用 reboot -p代替shutdown了)

2. 編寫Android.mk編譯腳本,目的是在源碼編譯的時候,將這兩個sh文件一起編譯到/system/bin目錄下

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_PREBUILT_EXECUTABLES := system_shutdown.sh system_reboot.sh
LOCAL_MODULE_TAGS := optional
include $(BUILD_MULTI_PREBUILT)

3. init.rc添加關機和重啓的服務,打開init.rc文件,在最後面添加如下內容:

service system_shutdown /system/bin/system_shutdown.sh
        oneshot
        disabled 

service system_reboot /system/bin/system_reboot.sh
        oneshot
        disabled

oneshot選項表示該服務只啓動一次,而如果沒有oneshot選項,這個可執行程序會一直存在--如果可執行程序被殺死,則會重新啓動。

disabled 表示禁用服務,此服務開機時不會自動啓動,但是可以在應用程序中手動啓動它。


4.新建一個目錄,比如poweraction, 將以上的Android.mk , system_shutdown.sh, system_reboot.sh放在這個目錄下,然後將poweraction這個目錄拷貝到Android系統中,比如device路徑下面。然後,編譯Android源碼,源碼編譯完成後, 查看生成的out/.../system/bin下面是不是包含system_shutdown.sh, system_reboot.sh兩個sh文件,如果有,則說明編譯成功。

5.最後,啓動系統服務,進行關機或重啓。

//啓動系統服務進行關機或重啓
case R.id.shutdown_btn2:
	Log.v(TAG, "system service->shutdown");
	SystemProperties.set("ctl.start", "system_shutdwon");
	break;
case R.id.reboot_btn2:
	Log.v(TAG, "system service->reboot");
	SystemProperties.set("ctl.start", "system_reboot");
	break;


</pre><pre>

三. Runtime調用Linux-shell

我們知道,Runtime這個Java類是可以用來調用並執行shell命令的,而Android虛擬機是支持Linux-shell語言的,基於這一點,可以利用Runtime來執行 關機或重啓的shell命令,這一點和上面介紹的方式二原理上大致相同。功能代碼如下:

//Runtime執行linux-shell
case R.id.shutdown_btn3:
	try{
		Log.v(TAG, "root Runtime->shutdown");
		//Process proc =Runtime.getRuntime().exec(new String[]{"su","-c","shutdown"});  //關機
		Process proc =Runtime.getRuntime().exec(new String[]{"su","-c","reboot -p"});  //關機
		proc.waitFor();
	}catch(Exception e){
		e.printStackTrace();
	}
	break;
case R.id.reboot_btn3:
	try { 
		Log.v(TAG, "root Runtime->reboot");
		Process proc =Runtime.getRuntime().exec(new String[]{"su","-c","reboot "});  //關機
		proc.waitFor();
	}catch (Exception ex){
		ex.printStackTrace();
	}
	break; 



    使用該方法需要注意的是,普通用戶是沒有權限執行reboot和shutdown的,自然而然也無法實現關機或重啓。使用的Android設備必須已經root過,上面的代碼加上su命令其實也就是爲了獲取管理員權限。另外一點,需要注意的是,該方法能夠奏效的前提是,你的android系統system/bin 目錄下存在reboot和shutdown文件(其實跟上面的原理一樣,也是調用bin目錄下的文件),聽說大部分設備存在reboot和shutdown這兩個文件,可我使用的RK Android系統偏偏沒有shutdown文件,所以,無法直接使用

Runtime.getRuntime().exec(new String[]{"su","-c","shutdown"})

只能執行下面命令來進行關機(好神奇的p參數)

Runtime.getRuntime().exec(new String[]{"su","-c","reboot -p"});

四 . PowerManager reboot以及反射調用PowerManagerService shutdown

1. PowerManager提供了reboot等接口,因此,利用PowerManager實現重啓,就比較簡單。

PowerManager pManager=(PowerManager) getSystemService(Context.POWER_SERVICE);  
pManager.reboot(null);//重啓</span>

2. PowerManager類並沒有提供關機的shutdown接口,而是通過IBinder這種Android中特有的通信模式,與PowerManagerService 類進行通信。PowerManagerService是PowerManager 類中定義的接口的具體實現,並進一步調用Power 類來與下一層進行通信. 在PowerManagerService實現了shutdown接口,power服務實現了關機功能
PowerManager的實現通過IPowerManager來調用Power服務的接口。 IPowerManager是AIDL文件自動生成的類,便於遠程通信。IPowerManage.aidl文件目錄

framework/base/core/java/android/os/IPowerManage.aidl 

IPowerManager實現了shutdown接口,所以,如果我們能夠獲得Power服務的IBinder,通過反射調用shutdown方法就能實現關機功能。
需要注意的是,ServiceManager管理着系統的服務程序,它保存着所有服務的IBinder,通過服務名就能獲取到這個服務的IBinder。
但ServiceManager這個類也是HIDE的,也需要反射進行調用。兩次,通過兩次反射調用,就能調用power服務實現的關機功能。

 try {
 
	 //獲得ServiceManager類
	 Class<?> ServiceManager = Class.forName("android.os.ServiceManager");
	 
	 //獲得ServiceManager的getService方法
	 Method getService = ServiceManager.getMethod("getService", java.lang.String.class);
	 
	 //調用getService獲取RemoteService
	 Object oRemoteService = getService.invoke(null,Context.POWER_SERVICE);
	 
	 //獲得IPowerManager.Stub類
	 Class<?> cStub = Class.forName("android.os.IPowerManager$Stub");
	 //獲得asInterface方法
	 Method asInterface = cStub.getMethod("asInterface", android.os.IBinder.class);
	 //調用asInterface方法獲取IPowerManager對象
	 Object oIPowerManager = asInterface.invoke(null, oRemoteService);
	 //獲得shutdown()方法
	 Method shutdown = oIPowerManager.getClass().getMethod("shutdown",boolean.class,boolean.class);
	 //調用shutdown()方法
	 shutdown.invoke(oIPowerManager,false,true);           

   } catch (Exception e) {         
	   Log.e(TAG, e.toString(), e);        
   }




五.使用ShutdownThread (嘗試不成功,但想法覺得可行)

其實,使用,PowerManager 來關機或重啓,PowerManager 接着還是會繼續還是會調用到ShutdwonThread的相關接口。比如

PowerManager提供的reboot接口。在PowerManager中,reboot()實現代碼如下 :

 public void reboot(String reason) {
        try {
            mService.reboot(false, reason, true);
        } catch (RemoteException e) {
        }
  }

mService 是是IPowerManager類型的,前面我們說過,PowerManagerServicePowerManager的具體實現,而PowerManager調用PowerManagerService實現的接口,也正是通過PowerManager的實現是通過IPowerManager來調用Power服務的接口。所以,繼續追蹤PowerManagerService中的代碼,可以知道其中就有reboot()的實現。

@Override // Binder call
    public void reboot(boolean confirm, String reason, boolean wait) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);

        final long ident = Binder.clearCallingIdentity();
        try {
            shutdownOrRebootInternal(false, confirm, reason, wait);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
  }

繼續跟蹤shutdownOrRebootInternal(false, confirm, reason, wait);

  private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,
              final String reason, boolean wait) {
          if (mHandler == null || !mSystemReady) {
              throw new IllegalStateException("Too early to call shutdown() or reboot()");
          }
  
          Runnable runnable = new Runnable() {
              @Override
              public void run() {
                  synchronized (this) {
                      if (shutdown) {
                          ShutdownThread.shutdown(mContext, confirm);
                      } else {
                          ShutdownThread.reboot(mContext, reason, confirm);
                      }
                  }
              }
          };

我們就會知道,它最終是調用到了ShutdownThread.java裏面的reboot()。從上面的代碼中,我們還知道,ShutdownThread同時還提供了關機接口

因此,要是可以利用ShutdownThread,那麼,一切就會方便很多.

不過,Shutdown並沒有提供公開API,網上有人說,可以,直接

Import com.android.server.pm.ShutdownThread

通過,執行Shutdown.shutdown() 或者Shutdown.reboot(), 就可以相應地關機或重啓。

但是,我放在源碼環境下,發現也沒能編譯通過,具體什麼原因,也沒再去深究,下次有時間,再繼續看看。不過,我覺得這種做法也是可以,可能是我遺漏了某些東西,導致編譯沒通過。








發佈了60 篇原創文章 · 獲贊 173 · 訪問量 30萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章