之前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 := settingsLOCAL_SRC_FILES := settingsLOCAL_MODULE_CLASS := EXECUTABLESLOCAL_MODULE_TAGS := optionalinclude $(BUILD_PREBUILT)

就是将settings文件作为源文件放入到系统中。并且定义为可执行文件。
2.settings

#!/system/bin/shcmd 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函数。
内容如下

@Overridepublic int onCommand(String cmd) {···//获取ContentProviderfinal 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根据后面的参数执行相应的动作。之后将结果返回。
通过上面的分析可以看出,这个过程比较简单。其他命令流程类似这里就不再分析。

更多相关文章

  1. Kotlin 概览——如何看待 Google 将 Kotlin 选为 Android(安卓)
  2. Android系统启动流程(nougat7.1.1_r6)
  3. android 命令行安装apk
  4. android AsyncTask详解
  5. Android(安卓)recovery UI实现分析
  6. Android应用程序线程消息循环模型分析
  7. Android常用命令之创建avd
  8. 穿越之旅之--android中如何执行java命令
  9. 箭头函数的基础使用

随机推荐

  1. Android实用代码片段(二)
  2. 2013.12.03(8) ——— android ActionbarSh
  3. Android(安卓)SDK腾讯镜像
  4. SearchView 自动展开和弹出输入法
  5. android 应用程序使用统计
  6. Android(安卓)Apkshare
  7. Android(安卓)Fresco监听回调,成功调回Bi
  8. Android(安卓)AlertDialog背景透明
  9. android仿照ipone的弹性效果
  10. android 自定义对话框宽不能占满父layout