之前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. Android源码内部编译过程总结(Make)(转)
  2. android的消息处理机制(图+源码分析)——Looper,Handler,Message (
  3. android 命令行安装apk
  4. Android常用命令之创建avd
  5. 命令行下创建 Android 工程,用 Ant 进行编译部署
  6. 解析 ViewTreeObserver 源码,体会观察者模式、Android消息传递(下)
  7. Android平台架构介绍和源码下载
  8. android 联系人源码分析 新字段的添加流程
  9. Android adb命令的使用

随机推荐

  1. libjpeg哈夫曼算法压缩图片
  2. android XMLPullParser读取xml文件
  3. Android 2.2用户超过一半
  4. [置顶] Android硬件抽象层模块编写规范
  5. Android输入输出流
  6. Android 多平台解决方案
  7. ask:Android原教旨主义失败了吗?
  8. Android中实现滑动(上)----基础知识
  9. android反编译和防止反编译的方法
  10. Android Camera源码分析