本文正在参与「金石方案 . 瓜分6万现金大奖」

Android 应用开发中,咱们一般会运用到 Android Studio 的各种开发工具。比如过滤打印log的 logcat ;获取 App 的View树的 Layout Inspector;以及对 App 进行debug 调试的 Debug等等。上述提到的Android Studio供给的功用都离不开DDMLIB

DDMLIB 是对Android的adb 指令进行的一层java封装。ddmlib内部帮你封装了一个个的adb指令,你能够经过调用ddmlib供给的接口发送相应的adb指令,ddmlib会接纳adb的呼应,并解析数据回调给咱们进行处理。

预备知识

adb介绍

adb是Android调试桥,能够执行各种设备操作。如图,adb包含三部分:

  • 客户端:用来发送指令,运行在PC
  • 守护程序(adbd):用于在手机或许模拟器上执行指令
  • 服务器:用于管理客户端和adbd之间的通讯

DDMLIB的源码解析

NIO介绍

在ddmlib中运用nio与服务器进行通讯,这儿介绍一下nio。nio是非堵塞式IO,这儿的非堵塞式是指建议IO恳求时,如果没有没有数据准备好,会直接回来,而不会堵塞线程。而传统IO即BIO会堵塞线程,直到有数据准备好才执行。详细能够看Java NIO浅析

概述

在ddmlib中有几个中心类,如下所示:

  • AndroidDebugBridge:代表adb的客户端
  • Device:代表adb衔接的手机或许模拟器
  • Client:代表设备中的app
  • ClientData:存储app的数据,如堆、线程、hprof等
  • MonitorThread:监督衔接的线程,偏重点在监听发送adb指令后数据的呼应上
  • ChunkHandler:处理adb服务器回来的数据
  • DeviceMonitor:监听设备衔接状况的改变,偏重点在设备状况改变上

AndroidDebugBridge

AndroidDebugBridge的运用示例如下所示:

//这儿的boolean表明是否debug
AndroidDebugBridge.initIfNeeded(false);
AndroidDebugBridge bridge = AndroidDebugBridge.createBridge("adb的路径", false);
while(true){//循环等候adb衔接好设备       
    if (bridge.hasInitialDeviceList()){               
        IDevice[] devices = bridge.getDevices();
        break;      
   }
}

initIfNeeded用来设置AndroidDebugBridge的形式,它有两种形式,一种是一般形式,一种是debug形式,false表明挑选一般形式。initIfNeeded办法内部调用init办法。代码如下所示,在init办法中创立并启动了MonitorThread,并给该线程注册各种ChunkHandler。这个注册的效果鄙人面详细介绍。

    public static synchronized void init(boolean clientSupport) {
        ...
        MonitorThread monitorThread = MonitorThread.createInstance();
        monitorThread.start();
        HandleHello.register(monitorThread);
        HandleAppName.register(monitorThread);
        HandleTest.register(monitorThread);
        HandleThread.register(monitorThread);
        HandleHeap.register(monitorThread);
        HandleWait.register(monitorThread);
        HandleProfiling.register(monitorThread);
        HandleNativeHeap.register(monitorThread);
        HandleViewDebug.register(monitorThread);
    }

createBridge办法用来获取AndroidDebugBridge目标,如下所示该办法内部创立了一个AndroidDebugBridge目标,并最终调用了DeviceMonitor的start办法。

    public static AndroidDebugBridge createBridge() {
        synchronized (sLock) {
            if (sThis != null) {
                return sThis;
            }
            try {
                sThis = new AndroidDebugBridge();
                sThis.start();
            } catch (InvalidParameterException e) {
                sThis = null;
            }
            ...
            return sThis;
        }
    }
    boolean start() {
        if (mAdbOsLocation != null && sAdbServerPort != 0 && (!mVersionCheck || !startAdb())) {
            return false;
        }
        mStarted = true;
        // now that the bridge is connected, we start the underlying services.
        mDeviceMonitor = new DeviceMonitor(this);
        mDeviceMonitor.start();
        return true;
    }

能够看出AndroidDebugBridge将adb衔接的使命交给DeviceMonitor来完成了。实际上,经过AndroidDebugBridge目标调用的hasInitialDeviceList和getDevices办法最终也是经过DeviceMonitor来完成的。

DeviceMonitor

    /**
     * Starts the monitoring.
     */
    void start() {
        mDeviceListMonitorTask = new DeviceListMonitorTask(mServer, new DeviceListUpdateListener());
        new Thread(mDeviceListMonitorTask, "Device List Monitor").start(); //$NON-NLS-1$
    }

DeviceMonitor的start办法启动了线程用来监听设备列表,并设置了设备更新监听。该线程的完成如下所示:

        @Override
        public void run() {
            do {
                if (mAdbConnection == null) {
                    Log.d("DeviceMonitor", "Opening adb connection");
                    mAdbConnection = openAdbConnection();//与adb服务器建立衔接
                    ...
                }
                try {
                    if (mAdbConnection != null && !mMonitoring) {
                        mMonitoring = sendDeviceListMonitoringRequest();//发送获取设备列表的恳求
                    }
                    if (mMonitoring) {
                        int length = readLength(mAdbConnection, mLengthBuffer);
                        if (length >= 0) {
                            // 解析获取的数据,并回调告诉AndroidDebugBridge
                            processIncomingDeviceData(length);
                            // flag the fact that we have build the list at least once.
                            mInitialDeviceListDone = true;
                        }
                      }
                      ...
            } while (!mQuit);//循环,保证获取
        }

Device

Device类是IDevice的完成类,经过这个类能够对设备进行截屏、安装卸载app、上传下载文件等功用,这儿就不多介绍了。拿到Device的实例后,就能够调用getClient办法获取Client目标,下面介绍Client。

Client

Client代表一个设备上的app进程,经过它咱们就能够获取app的UI、内存、hprof等信息。Client自身只存储基本的信息,app的详细信息保存在ClientData中,每一个Client都有一个对应的ClientData。需求注意的是ClientData保存的hprof信息是二进制数组,需求自己进行解析。

这儿以获取UI信息为例,介绍Client的运用流程及其内部原理。如下代码所示,在获取到Client目标后,调用HandleViewDebug的dumpViewHierarchy传入Client目标以及目标的一些参数,就能够获取对应的UI数据了。

        IDevice device = devices[0];
        Client[] clients = device.getClients();
        HandleViewDebug.dumpViewHierarchy(clients[0], "viewRoot", false, true, new ViewDumpHandler() {
            @Override
            protected void handleViewDebugResult(ByteBuffer data) {
                //处理呼应数据
            }
        });

HandleViewDebug是怎样做到这个功用的?这个就要从最开端的init办法内部调用MonitorThread的start办法开端。下面是简化的MonitorThread的run办法的代码

    @Override
    public void run() {
        // create a selector 1.创立一个selector,在nio中,经过selector来获取有数据抵达的Channel
        try {
            mSelector = Selector.open();
        } 
        ...
        while (!mQuit) {
            try {
                /*
                 * sync with new registrations: we wait until addClient is done before going through
                 * and doing mSelector.select() again.
                 * @see {@link #addClient(Client)}
                 */
                 //2.DeviceMonitor创立Client后,会调用MonitorThread的addClient办法将Client的Channel注册到Selector中
                synchronized (mClientList) {
                }
               ...
                Set<SelectionKey> keys = mSelector.selectedKeys();
                Iterator<SelectionKey> iter = keys.iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    iter.remove();
                    try {
                    //3.adb服务器有数据回来时调用
                        if (key.attachment() instanceof Client) {
                            processClientActivity(key);
                        }
                        ...
                }
            } catch (Exception e) {
              ...
            }
        }
    }

在该办法中,第一步是创立Selector,用来处理注册的Channel;第二步,DeviceMonitor创立Client后,会调用MonitorThread的addClient办法将Client的Channel注册到Selector中。还记得init办法中注册的各种ChunkHandler吗,在addClient办法中,会将这些ChunkHandler添加到Client中。第三步,等adb服务器有数据回来时调用processClientActivity办法。那什么时候会有数据回来?答案是咱们调用HandleViewDebug#dumpViewHierarchy办法时,该办法的源码如下:

    public static void dumpViewHierarchy(@NonNull Client client, @NonNull String viewRoot,
            boolean skipChildren, boolean includeProperties, @NonNull ViewDumpHandler handler)
                    throws IOException {
        ByteBuffer buf = allocBuffer(4      // opcode
                + 4                         // view root length
                + viewRoot.length() * 2     // view root
                + 4                         // skip children
                + 4);                       // include view properties
        JdwpPacket packet = new JdwpPacket(buf);
        ByteBuffer chunkBuf = getChunkDataBuf(buf);
        chunkBuf.putInt(VURT_DUMP_HIERARCHY);
        chunkBuf.putInt(viewRoot.length());
        ByteBufferUtil.putString(chunkBuf, viewRoot);
        chunkBuf.putInt(skipChildren ? 1 : 0);
        chunkBuf.putInt(includeProperties ? 1 : 0);
        finishChunkPacket(packet, CHUNK_VURT, chunkBuf.position());
        //上面的是拼接恳求包的代码,建议恳求是client调用了send办法
        client.send(packet, handler);
    }

咱们知道当DeviceMonitor创立Client后会注册Channel到MonitorThread的Selector中,所以Client的恳求会走到MonitorThread的run办法的第三步的processClientActivity办法,源码如下:

    private void processClientActivity(SelectionKey key) {
        Client client = (Client)key.attachment();
        try {
            if (!key.isReadable() || !key.isValid()) {
                Log.d("ddms", "Invalid key from " + client + ". Dropping client.");
                dropClient(client, true /* notify */);
                return;
            }
            client.read();//读取数据
            /*
             * See if we have a full packet in the buffer. It's possible we have
             * more than one packet, so we have to loop.
             */
            JdwpPacket packet = client.getJdwpPacket();
            while (packet != null) {
                client.incoming(packet, client.getDebugger());
               ...
    }

在rocessClientActivity办法,先读取数据,在调用Client的incoming办法对数据进行处理,该办法的源码如下:

    public void incoming(@NonNull JdwpPacket packet, @Nullable JdwpAgent target) throws IOException {
        mProtocol.incoming(packet, target);
        int id = packet.getId();
        if (packet.isReply()) {
            JdwpInterceptor interceptor = mReplyInterceptors.remove(id);
            if (interceptor != null) {
                packet = interceptor.intercept(this, packet);
            }
        }
        for (JdwpInterceptor interceptor : mInterceptors) {
            if (packet == null) break;
            packet = interceptor.intercept(this, packet);
        }
        if (target != null && packet != null) {
            target.send(packet);
        }
    }

能够看到最终调用了JdwpInterceptor的intercept办法,从上面的分析咱们知道在ddClient办法中,会将这些ChunkHandler添加到Client中(ChunkHandler完成了JdwpInterceptor),即最终会调到HandleViewDebug的intercept办法。经过这个流程HandleViewDebug就能够获取到UI的数据信息并对其进行解析,最终经过回调交给咱们处理。

总结

本篇文章主要介绍了ddmlib的几个中心类,及其源码的完成;重点讲解了经过ddmlib获取UI信息的进程和源码。文章最终求求免费的赞吧。