新增阶段五文档:面板接真实数据(增删改查/属性读写/Inspector 真名翻译/滚动焦点/统计)+ 14 条踩坑点 + 未完成清单

This commit is contained in:
tianmo committed 2026-10-16 18:46:16 +08:00
1 parent bb817b85e5
commit 7f06c6744a
43 files changed
+2942 -79

No files matched your search

+177
View File
@@ -0,0 +1,177 @@
import java.lang.instrument.Instrumentation;
import java.lang.reflect.*;
import java.net.URLClassLoader;
/**
* 只读探针 6:把面板的**整棵控件树**连坐标打出来(含 stage 坐标),
* 用来定位"列表里的内容被画到面板外"。
*
* 取值方法来自字节码(javap -p -c):
* dl.c_()F=getX dl.d_()F=getY dl.L()F=getWidth dl.I()F=getHeight
* dl.D()Ldo; = getParent do.D()Lhq; = getChildren hq extends fI(Array),size 字段在 fI 里
* ⚠️ et(Table) 自己也声明了 `int L()` / `int I()`(列数/行数),所以必须**按返回类型 float 过滤**。
*/
public final class GeomProbe6 {
static Instrumentation INST;
static Object STATE;
static Class<?> HQL, ARR, AO;
public static void agentmain(String args, Instrumentation inst) {
INST = inst;
try { run(); } catch (Throwable e) { log("失败: " + e); e.printStackTrace(System.err); }
}
static void log(String m) { System.err.println("[geom6] " + m); }
static void run() throws Exception {
for (Class<?> c : INST.getAllLoadedClasses()) {
if (!c.getName().equals("com.spineview.ObjectPanel")) continue;
try {
Field f = c.getDeclaredField("LAST_STATE");
f.setAccessible(true);
Object v = f.get(null);
if (v != null) STATE = v;
} catch (Throwable ignored) { }
}
if (STATE == null) { log("LAST_STATE 拿不到"); return; }
ClassLoader payload = null;
for (Class<?> c : INST.getAllLoadedClasses()) {
if (c.getClassLoader() instanceof URLClassLoader) { payload = c.getClassLoader(); break; }
}
HQL = Class.forName("hq", true, payload);
ARR = Class.forName("fI", true, payload);
AO = Class.forName("dl", true, payload);
Class<?> og = null;
for (Class<?> c : INST.getAllLoadedClasses()) if (c.getName().equals("OG")) og = c;
Method onRender = null;
for (Method m : og.getDeclaredMethods()) {
Class<?>[] t = m.getParameterTypes();
if (m.getName().equals("B") && t.length == 1 && t[0] == Runnable.class) { onRender = m; break; }
}
onRender.setAccessible(true);
onRender.invoke(null, new Runnable() {
@Override public void run() {
try { work(STATE); } catch (Throwable e) { log("work 失败: " + e); e.printStackTrace(System.err); }
}
});
}
static void work(Object state) throws Exception {
Object root = state("root");
log("=========== 面板控件树(缩进=层级;坐标为 stage 绝对坐标)===========");
walk(root, 0, 0f, 0f, 6);
log("=========== 面板根再往上(谁给它多大地方)===========");
Object p = parent(root);
float ax = 0f, ay = 0f;
for (int i = 0; i < 6 && p != null; i++) {
log(" 上级[" + i + "] " + p.getClass().getName() + " " + geom(p));
Object np = parent(p);
if (np == p) break;
p = np;
}
log("=========== 关键对象定位 ===========");
Object scroll = state("listScroll"), table = state("listTable");
log(" listScroll " + geom(scroll) + " 子控件=" + size(childrenOf(scroll)));
log(" listTable " + geom(table) + " 行数=" + size(childrenOf(table)));
log(" listTable 的 stage 坐标 = " + stageX(table) + "," + stageY(table));
log("=======================================================");
}
static Object state(String name) throws Exception {
Field f = STATE.getClass().getDeclaredField(name);
f.setAccessible(true);
return f.get(STATE);
}
static void walk(Object actor, int depth, float px, float py, int maxDepth) {
if (actor == null || depth > maxDepth) return;
StringBuilder ind = new StringBuilder();
for (int i = 0; i < depth; i++) ind.append(" ");
float x = fl(actor, "c_"), y = fl(actor, "d_");
Object kids = childrenOf(actor);
int n = size(kids);
log(ind + actor.getClass().getName() + " " + geom(actor)
+ " stage=(" + r(px + x) + "," + r(py + y) + ") 子控件=" + n);
for (int i = 0; i < n; i++) {
Object child = get(kids, i);
walk(child, depth + 1, px + x, py + y, maxDepth);
}
}
static float stageX(Object actor) {
float x = 0f;
Object a = actor;
for (int i = 0; i < 8 && a != null; i++) { x += fl(a, "c_"); a = parent(a); }
return r(x);
}
static float stageY(Object actor) {
float y = 0f;
Object a = actor;
for (int i = 0; i < 8 && a != null; i++) { y += fl(a, "d_"); a = parent(a); }
return r(y);
}
static String geom(Object actor) {
if (actor == null) return "null";
return "[local x=" + r(fl(actor, "c_")) + " y=" + r(fl(actor, "d_"))
+ " w=" + r(fl(actor, "L")) + " h=" + r(fl(actor, "I"))
+ " top=" + r(fl(actor, "S")) + " right=" + r(fl(actor, "C")) + "]";
}
static Object parent(Object actor) {
if (actor == null) return null;
for (Method m : actor.getClass().getMethods()) {
if (m.getParameterTypes().length != 0) continue;
if (!m.getName().equals("D")) continue;
if (!m.getReturnType().getName().equals("do")) continue;
try { m.setAccessible(true); return m.invoke(actor); } catch (Throwable ignored) { }
}
return null;
}
static Object childrenOf(Object actor) {
if (actor == null) return null;
for (Method m : actor.getClass().getMethods()) {
if (m.getParameterTypes().length != 0) continue;
if (m.getReturnType() != HQL) continue;
try { m.setAccessible(true); return m.invoke(actor); } catch (Throwable ignored) { }
}
return null;
}
static int size(Object array) {
if (array == null) return 0;
for (Class<?> t = ARR; t != null && t != Object.class; t = t.getSuperclass()) {
for (Field f : t.getDeclaredFields()) {
if (f.getType() != int.class) continue;
f.setAccessible(true);
try { return f.getInt(array); } catch (Throwable ignored) { }
}
}
return 0;
}
static Object get(Object array, int index) {
if (array == null) return null;
for (Method m : array.getClass().getMethods()) {
if (!m.getName().equals("D")) continue;
Class<?>[] t = m.getParameterTypes();
if (t.length != 1 || t[0] != int.class || m.getReturnType() != Object.class) continue;
try { m.setAccessible(true); return m.invoke(array, Integer.valueOf(index)); } catch (Throwable ignored) { }
}
return null;
}
static float fl(Object actor, String name) {
if (actor == null) return -1f;
for (Method m : actor.getClass().getMethods()) {
if (!m.getName().equals(name)) continue;
if (m.getParameterTypes().length != 0 || m.getReturnType() != float.class) continue;
try { m.setAccessible(true); return ((Float) m.invoke(actor)).floatValue(); } catch (Throwable ignored) { }
}
return -1f;
}
static float r(float v) { return Math.round(v * 100f) / 100f; }
}