由于一个特殊的需求,检测应用有没有在联网,于是我尝试写了这个工具。需要声明一点,我这个工具需要自己重新编译ROM。

原理

其实很简单,APP需要访问网络时,需要调用Java层的接口

// 请求的地址
String spec = "http://localhost:9090/formTest";
// 根据地址创建URL对象
URL url = new URL(spec);
// 根据URL对象打开链接
HttpURLConnection urlConnection = (HttpURLConnection) url
                    .openConnection();

所以我们在URL#openConnection()中,添加自己的监控逻辑即可。
可以看到java.net.URL是在jrt中的,也就是java的运行环境中的,所以我们需要修改jrt的东西。
经过一些查询我知道了Android的libcore和ojluni。

ojluni

ojluni就是OpenJDK, java.lang / java.util / java.net / java.io 的缩写,就是OpenJDK核心库的意思。ojluni在libcore目录下。
有兴趣的同学可以在http://androidxref.com/9.0.0_r3/xref/libcore/查看所有的源码

具体实现

接下来我们需要修改ojluni下的URL的源码,需要先下载libcore项目,找到URL.java的位置:

libcore/ojluni/src/main/java/java/net/URL.java

在openConnection()方法中添加日志打印,并且将日志保存下来

public URLConnection openConnection() throws java.io.IOException {
       java.util.SeempLog.record_str(91, "URL:" + query);
       logHttpUrlByLocalSocket();  // 添加此行,记录上层应用访问网络的情况
       return handler.openConnection(this);
   }
/** 记录访问网络的应用PID/UID/TID/进程名称和调用栈 */
private void logHttpUrlByLocalSocket() {

    String url = String.format("%s://%s%s?%s", protocol, authority, path, query);

    String lineData = String.format("%s#pid:%s#uid:%s#tid:%s#cmdline:%s#%s\n",
             System.currentTimeMillis(), getPid(), getUid(), getTid(), getCmdline(getPid()), url.trim());
    String callStack = getCallStack();
    File file = new File("sdcard/Download/http.txt");
    try {
        if (!file.exists()) {
            file.createNewFile();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    try (RandomAccessFile raf = new RandomAccessFile(file, "rwd")) {
        raf.seek(file.length());
        raf.write(lineData.getBytes());
        raf.write(callStack.getBytes());
        raf.write("\n".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/** 获取pid对应的进程名称 */
public static String getCmdline(int pid) {
    FileReader in = null;
    BufferedReader reader = null;
    String result = null;
    try {
        File file = new File("/proc/" + pid + "/cmdline");
        in = new FileReader(file);
        reader = new BufferedReader(in);
        String line = reader.readLine();
        if (line != null) {
            result = line.replace("\0", "");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (in != null) {
                in.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    if (result == null) {
        return "null";
    }
    return result;
}

/** 反射获取PID */
private static int getPid() {
    try {
        Object pid = callStaticObjectMethod(Class.forName("android.os.Process"), "myPid", null);
        if (pid != null) {
            return (int) pid;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}

/** 反射获取UID */
private static int getUid() {
    try {
        Object pid = callStaticObjectMethod(Class.forName("android.os.Process"), "myUid", null);
        if (pid != null) {
            return (int) pid;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}

/** 反射获取TID */
private static int getTid() {
    try {
        Object pid = callStaticObjectMethod(Class.forName("android.os.Process"), "myTid", null);
        if (pid != null) {
            return (int) pid;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}

/** 获取调用栈 */
private static String getCallStack() {
    StackTraceElement[] stackTrace = new Throwable().fillInStackTrace().getStackTrace();
    StringBuffer callStack = new StringBuffer();
    callStack.append("callStack:");
    for (int i = 0; i < stackTrace.length; i++) {
        callStack.append(stackTrace[i].toString());
        callStack.append("\n");
    }
    return callStack.toString();
}

private static Object callObjectMethod(Object target, String method, Class<?>[] parameterTypes, Object... values)
        throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    Class<? extends Object> clazz = target.getClass();
    Method declaredMethod = clazz.getDeclaredMethod(method, parameterTypes);
    declaredMethod.setAccessible(true);
    return declaredMethod.invoke(target, values);
}

public static Object callStaticObjectMethod(Class<?> clazz, String method, Class<?>[] parameterTypes, Object... values)
        throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    Method declaredMethod = clazz.getDeclaredMethod(method, parameterTypes);
    declaredMethod.setAccessible(true);
    return declaredMethod.invoke(null, values);
}

以上就是整个功能。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

做技术预研的一些想法 上一篇
HashMap实现原理 下一篇