Post

LookIn源码研究与学习

LookIn源码研究与学习

源码地址:

  • iOS 端 LookinServer:https://github.com/QMUI/LookinServer
  • macOS 端软件:https://github.com/hughkli/Lookin/
  • Lookin原理及5个开发难点
  • https://github.com/hughkli/KKConnector
  • https://medium.com/better-programming/a-side-by-side-comparison-of-two-great-ios-views-debugging-tools-85fefbf69881
  • https://github.com/FLEXTool/FLEX

技巧

如何在 Lookin 中展示自定义信息: https://bytedance.larkoffice.com/docx/TRridRXeUoErMTxs94bcnGchnlb 如何在 Lookin 中展示更多成员变量: https://bytedance.larkoffice.com/docx/CKRndHqdeoub11xSqUZcMlFhnWe 如何为 Lookin 开启 Swift 优化: https://bytedance.larkoffice.com/docx/GFRLdzpeKoakeyxvwgCcZ5XdnTb 文档汇总:https://bytedance.larkoffice.com/docx/Yvv1d57XQoe5l0xZ0ZRc0ILfnWb

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
Src
├── Base
├── Main
│   ├── Server
│   │   ├── Category
│   │   ├── Connection
│   │   │   └── RequestHandler
│   │   └── Others
│   └── Shared
│       ├── Category
│       └── Peertalk
└── Swift

一、监听、读写、业务主要类

管理类型:Server.Connection.LKS_ConnectionManager
读写io类:Peertalk.Lookin_PTChannel
处理业务类:Server.Connection.LKS_RequestHandler

1、启动服务

利用所有类都会在App启动时调用load方法进行启动:
LKS_ConnectionManager -> + (void)load -> sharedInstance -> init -> 注册通知UIApplicationDidBecomeActiveNotification
-> _handleApplicationDidBecomeActive -> _tryToListenOnPortFrom:to:current:
-> Lookin_PTChannel -> channel listenOnPort:IPv4Address:callback: -> 系统的bindcloselisten等方法

链接端口逻辑: 递归尝试from-to之间的端口,如果listen失败,则端口号+1进行监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
     [channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
        if (error) {
            if (error.code == 48) {
                // 该地址已被占用
            } else {
                // 未知失败
            }
            
            if (currentPort < toPort) {
                // 尝试下一个端口
                NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@). Will try anothor address ...", currentPort, error);
                [self _tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)];
            } else {
                // 所有端口都尝试完毕,全部失败
                NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@).", currentPort, error);
                NSLog(@"LookinServer - Connect failed in the end. Ask for help: lookin@lookin.work");
            }
            
        } else {
            // 成功
            NSLog(@"LookinServer - Connected successfully on 127.0.0.1:%d", currentPort);
            // 此时 peerChannel_ 状态为 listening
            self.peerChannel_ = channel;
        }
    }];

2、接收数据

listenOnPort: -> while -> acceptIncomingConnection -> type + tag + data -> LKS_RequestHandler -> canHandleRequestType + handleRequestType -> 处理逻辑并生成数据\

3、返回数据

-> LKS_ConnectionManager -> respond:requestType:tag: -> _sendData
-> Lookin_PTChannel -> sendFrameOfType -> 相关数据组装 -> dispatch_io_write

4、请求页面层次

LookinRequestTypeHierarchy -> [LookinHierarchyInfo staticInfoWithLookinVersion:clientVersion] -> displayItems: [LookinDisplayItem] -> UIApplication.sharedApplication.windows -> layer + sublayers

查看结构:LookinRequestTypeHierarchy

1
2
3
4
5
6
7
 LookinDisplayItem
├── viewObject<LookinObject>
├── layerObject<LookinObject>
│   ├── memoryAddress<String>
│   └── classChainList<[String]>
│   └── completedSelfClassName<String>
└── subitems<[LookinDisplayItem]>

5、查看UI元素UI内容截图

通过oid查找到layer,通过layer绘制图片

LookinRequestTypeHierarchyDetails -> LKS_HierarchyDetailsHandler.startWithPackages -> _dequeueAndHandlePackage
-> [NSObject lks_objectWithOid:task.oid] -> layer -> lks_soloScreenshotWithLowQuality -> groupScreenshot+soloScreenshot-> LookinDisplayItemDetail

6、修改属性

6.1 执行修改指令 LookinRequestTypeInbuiltAttrModification -> LKS_InbuiltAttrModificationHandler -> handleModification -> LookinRequestTypeAttrModificationPatch -> Runtime的方法转发 -> NSMethodSignature + NSInvocation

6.2 修改后的截图 LookinRequestTypeAttrModificationPatch -> LKS_InbuiltAttrModificationHandler -> handlePatchWithTasks
-> [NSObject lks_objectWithOid:task.oid] -> layer -> lks_soloScreenshotWithLowQuality -> groupScreenshot+soloScreenshot-> LookinDisplayItemDetail


使用Xcode的debug模式下动态库注入实现sever

使用断点调试的功能,在Xcode中配置后UIApplicationMain运行时,使用LLDB执行加载命令加载动态库, 这样跟直接集成代码和动态加载一样都能实现创建一个Server

1
expr (Class)NSClassFromString(@"Lookin") == nil ? (void *)dlopen("/Applications/Lookin.app/Contents/Resources/LookinServerFramework/LookinServer.framework/LookinServer", 0x2) : ((void*)0)

image.png


使用optoolinsert_dylib对ipa进行注入动态库安装到真机

This post is licensed under CC BY 4.0 by the author.