本文正在参与「金石方案 . 瓜分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之间的通讯
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信息的进程和源码。文章最终求求免费的赞吧。