Android 9.0 cmds

之前Android中的cmd都是bin文件,例如svc/settings等。這些命令的源碼都是存放在android/framework/base/cmds目錄下。在android 9.0中也是在這個位置,但是實現方式進行了改動。下面就來說說Android 9.0中是如何實現的。

cmds的實現方式

通過查看Android源碼發現,在framework/base/cmds目錄下面依舊存在這些目錄結構,但是進入到目錄之後看到這裏面並沒有實現命令的源碼。以settings這個命令爲例,在framework/base/cmds/settings的目錄下面只存在兩個文件:1.Android.mk;2.settings
這兩個文件非常簡單,文件內容如下:

  1. Android.mk
# Copyright 2011 The Android Open Source Project
#
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

include $(CLEAR_VARS)
LOCAL_MODULE := settings
LOCAL_SRC_FILES := settings
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)

就是將settings文件作爲源文件放入到系統中。並且定義爲可執行文件。
2.settings

#!/system/bin/sh
cmd settings "$@"

我們看到settings實際上就是一個shell腳本。在這個腳本中調用了cmd這個命令。這個命令之後有兩個參數:1. settings 也就是說這個命令執行的內容是settings;2.$@ 這個用來存儲命令行中輸入的參數。

所以說現在的cmds的主要實現邏輯還是在cmd這個bin文件裏面。
下面看一下這個cmd bin的源碼是如何實現的。

1.源碼位置:
/framework/native/cmds/cmd/cmd.cpp
下面看一下cmd的入口函數main的主要部分

int main(int argc, char* const argv[])
{
	···
	//獲取c層的ServiceManager
    sp<IServiceManager> sm = defaultServiceManager();

    if (argc == 1) {
        aerr << "cmd: No service specified; use -l to list all services" << endl;
        return 20;
    }

	//獲取參數
    Vector<String16> args;
    for (int i=2; i<argc; i++) {
        args.add(String16(argv[i]));
    }
	
    //這個就是查找調用的那個service
    String16 cmd = String16(argv[1]);
    sp<IBinder> service = sm->checkService(cmd);
    if (service == NULL) {
        ALOGW("Can't find service %s", argv[1]);
        aerr << "cmd: Can't find service: " << argv[1] << endl;
        return 20;
    }
	//註冊callback函數,用來接受命令執行結果
    sp<MyShellCallback> cb = new MyShellCallback();
    sp<MyResultReceiver> result = new MyResultReceiver();


    // TODO: block until a result is returned to MyResultReceiver.
	//這裏就是調用對應的Service來執行相應的命令
    status_t err = IBinder::shellCommand(service, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, args,
            cb, result);
	//判斷命令執行結果
    if (err < 0) {
        const char* errstr;
        switch (err) {
            case BAD_TYPE: errstr = "Bad type"; break;
            case FAILED_TRANSACTION: errstr = "Failed transaction"; break;
            case FDS_NOT_ALLOWED: errstr = "File descriptors not allowed"; break;
            case UNEXPECTED_NULL: errstr = "Unexpected null"; break;
            default: errstr = strerror(-err); break;
        }
        return err;
    }
	//結果輸出
    cb->mActive = false;
    status_t res = result->waitForResult();
    return res;
}

看到現在的命令行都是通過調用相應的service來執行相應的動作,通過註冊的callback來返回命令的執行結果。
既然知道是通過調用相應的Service來執行動作,那麼這些Service是在什麼地方實現的?
此處還是以settings爲例。

settings源碼分析

對應的Service爲:frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
1.SettingsService實例化

 public boolean onCreate() {
 	ServiceManager.addService( "settings" , new SettingsService(this));
 }

看到,此處註冊了SettingsService,名稱爲settings。這與之前settings的shell腳本里是一致的。
因爲SettingsProvider是系統app,所以在開機的時候就會啓動。也就是SettingsService就被實例化並且註冊到系統了。

2.SettingsService的調用過程

final public class SettingsService extends Binder {
		@Override
	    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
        (new MyShellCommand(mProvider, false)).exec(
                this, in, out, err, args, callback, resultReceiver);
    }
	
	final static class MyShellCommand extends ShellCommand {
	
	 	public int onCommand(String cmd) {
	 
	 	}
	}
}

看到SettingsService是繼承了Binder。那麼在cmd裏面調用 IBinder::shellCommand()也就是調用Binder.java中的shellCommand函數。看一下Binder.java中是否有這個函數,如果有的話,這個函數裏面幹了什麼

    public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
            @Nullable FileDescriptor err,
            @NonNull String[] args, @Nullable ShellCallback callback,
            @NonNull ResultReceiver resultReceiver) throws RemoteException {
        onShellCommand(in, out, err, args, callback, resultReceiver);
    }

看到確實存在shellCommand函數,並且調用了onShellCommand。
並且在上面SettingsService的類中override了Binder的onShellCommand。所以就會執行SettingsService.onShellCommand函數。
在onShellCommand函數裏面實例化並且執行了這個對象的exec()函數。
我們看到MyShellCommand繼承了ShellCommand。在ShellCommand中的實現方法主要如下:

public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
            String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
		//初始化相應的變量,用於之後視同
        init(target, in, out, err, args, callback, start);
        mCmd = cmd;
        mResultReceiver = resultReceiver;


        int res = -1;
		//調用onCommand函數執行命令
        try {
            res = onCommand(mCmd);
            if (DEBUG) Slog.d(TAG, "Executed command " + mCmd + " on " + mTarget);
        } catch (SecurityException e) {
            PrintWriter eout = getErrPrintWriter();
            eout.println("Security exception: " + e.getMessage());
            eout.println();
            e.printStackTrace(eout);
        } catch (Throwable e) {

        } finally {
			//返回cmd執行結果
            if (mResultReceiver != null) {
                mResultReceiver.send(res, null);
            }
        }
        return res;
    }

同時再MyShellCommand類中看override了onCommand函數。
內容如下

@Override
public int onCommand(String cmd) {
	···
	//獲取ContentProvider
	final IContentProvider iprovider = mProvider.getIContentProvider();
            final PrintWriter pout = getOutPrintWriter();
			//對ContentProvider進行操做
            switch (mVerb) {
                case GET:
                    pout.println(getForUser(iprovider, mUser, mTable, mKey));
                    break;
                case PUT:
                    putForUser(iprovider, mUser, mTable, mKey, mValue, mTag, mMakeDefault);
                    break;
                case DELETE:
                    pout.println("Deleted "
                            + deleteForUser(iprovider, mUser, mTable, mKey) + " rows");
                    break;
                case LIST:
                    for (String line : listForUser(iprovider, mUser, mTable)) {
                        pout.println(line);
                    }
                    break;
                case RESET:
                    resetForUser(iprovider, mUser, mTable, mTag);
                    break;
                default:
                    perr.println("Unspecified command");
                    return -1;
            }

            return 0;
}

命令行輸入的命令對應上面的switch。
例如:settings put global XXXX
就是對應上面的case PUT。
將上面的命令真正展開就是:cmd settings put global XXXX
以上就是settings命令的邏輯分析過程。

總結

Android 9.0中的cmds就是cmd命令通過後面的參數(settings)查找對應的Service。之後Service根據後面的參數執行相應的動作。之後將結果返回。
通過上面的分析可以看出,這個過程比較簡單。其他命令流程類似這裏就不再分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章