【Python】Android 系统API列表提取

本文介绍如何使用Python遍历并正则匹配Android系统API文件,提取API类和函数信息,进行去重和格式化处理。通过路径格式化、类与包名映射建立、API列表格式化等步骤,最终生成完整的Android系统API列表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


为方便查看Android APP的系统API调用情况,一种措施是将APP的apk安装包反编译,对于反编译之后得到的代码进行扫描遍历,查看系统API调用情况。但实现这一功能的前提是已经提取到系统API,并且按照反编译之后的格式输出。本文不包含具体查看APP系统API调用情况,仅包括提取系统API,并格式化。


Android API 版本: Android23
编程语言: Python
系统: Window 10
开发环境: JetBrains PyCharm

一遍历


遍历Android系统API文件夹,返回所有Android系统.java文件目录,方便之后的访问。


'''
    扫描文件夹dir中所有.java文件并按照包名输出
    dir 为 Android 系统 API 的相对文件目录
    调用方法:
    list = GetFileList('../android-23',[])
    list 即为所有 系统 .java 文件的目录列表
'''
def GetFileList(dir, fileList):
    newDir = dir
    if os.path.isfile(dir):  # 如果是文件
        fileList.append(dir)
    elif os.path.isdir(dir):
        for s in os.listdir(dir):  # 如果是文件夹
            newDir = os.path.join(dir, s)
            GetFileList(newDir, fileList)
    return fileList

二正则匹配


在得到Android系统.java文件目录之后即可进行正则匹配,提取所有API类的函数声明等信息,并进行参数格式化,添加类的引用关系,完成Android系统API列表的提取。但经过一下处理得到的列表,不能区分该方法是类的方法还是该类内部类的方法,并且会产生许多重复的列表信息(因为多个内部类),故这里默认所有方法均为类的方法(暴力笼统了)。
输出样例【包名.类名.方法名(参数列表):返回值】:
android.accessibilityservice.AccessibilityService.onAccessibilityEvent(android.view.accessibility.AccessibilityEvent):void
对于内部类的处理
A.B.C的内部类D的方法E()返回值为F,那么输出格式为:
A.B.C.E():F
由于有较多重复项目,故还应该进行去重操作。
去重之后,为了统一格式,匹配反编译之后的代码,需要重新格式化
去重及重新格式化稍后讨论


'''
    遍历 package 指向的.java 类中所有的方法
    1).java 类自己的方法
    2).java 类的内部类(接口)的方法
'''
def ergodicClassFunc(package):
    '''
    :param package:存储每个系统API的Java的相对路径
    :return:0:.java类的方法正常遍历;1:.java类的方法没有正常提取
    '''
    '''
        编译正则表达式
    '''
    pattern = re.compile(
        r'^[ \t]*(public |protected |private )((static |final |abstract )*)([\w|_|\$| |,|\r|\n|\t|.|\[|\]|\<|\>|\:|\?|@]*)\(([\w|_|\$| |,|\r|\n|\t|.|\[|\]|\<|\>|\:|\?|@|\(|\)|=]*)(.*)$',
        re.M);

    # 正则表达式匹配 import 语句
    patternImport = re.compile(
        r'^(import )([a-zA-Z0-9|_|\$| |,|.|\:|\?|@]*)(;)$',
        re.M);

    '''
        数据处理
    '''
    flag = 0        # 标志变量:0:.java类的方法正常遍历;1:.java类的方法没有正常提取

    # package 存储每个系统API的Java的相对路径
    # tmpClassPackage 存储格式化之后的文件路径
    tmpClassPackage = apiRouteReform(package)

    # 遍历该.java文件提取所有方法
    # print(package)
    IDXfile = open(package, 'r', encoding='utf-8')  # gbk 格式的文件 IDXfile.read() 会报错,以 utf-8 格式打开就行
    fileread = IDXfile.read()
    IDXfile.close()
    methods = re.findall(pattern, fileread)  # 匹配 类声明的方法
    classImport = re.findall(patternImport, fileread)  # 匹配 import 语句
    # 构造 类:包名 的对应关系 : 包含 1. import 语句引用的;2.与该类同一个 package 的
    # 3.内部类(接口)- public / private / protected 三种类型的都记录下来
    classMatch = classMatchPackage(classImport, package)  # 构造 map
    # print(methods)

    # 输出所有未能遍历的.java类 ,更换正则表达式遍历
    if len(methods) == 0:
        # print(tmp)    # tmp 中存储的是 . 连接的类名
        flag = 1
    else:
        for eFunc in methods:
            # eFunc 为每个函数的完整的说明
            apiFunc = eFunc[3];  # 第四个分组为函数名格式 : void release  或者为类名如:AccessibilityServiceInfo 又或者由三部分组成,这个时候需要细致区分
            varFunc = eFunc[4];  # 第五个分组为参数声明分组:其中包含与参数声明无关的 \r,\n,\t 字符,需要先将这几种字符去掉,并且包含 声明括号中右括号)之后到 { 之前内容如:String name)\n        throws SAXNotRecognizedException, SAXNotSupportedException
            # 函数参数格式化
            varFunc = varReform(varFunc, classMatch)
            # 如果不是类名则为方法名,用split 分隔
            if ' ' in apiFunc:
                # 得到该方法的返回值和函数名
                apiFuncReturn, apiFuncName = findReturnValueAndFuncName(apiFunc);
                # 对返回值的类型进行 类:包名 的对应
                apiFuncReturn = varClassIncludePackage(apiFuncReturn, classMatch)
                # 去除函数名中多于的空格
                apiFuncName = apiFuncNameReform(apiFuncName)
                apiFunc = tmpClassPackage + '.' + apiFuncName + '(' + varFunc + '):' + apiFuncReturn;
            # 对于构造函数
            else:
                # 去除函数名中多于的空格
                apiFunc = apiFuncNameReform(apiFunc)
                apiFunc = tmpClassPackage + '.' + apiFunc + '(' + varFunc + ')';

            # 去除 API 中的 \n 等空白字符
            apiFunc = removeVarSpaceChar(apiFunc)

            # 输出API

            print(apiFunc)
    return flag

通过以上正则表达式

 pattern = re.compile(
        r'^[ \t]*(public |protected |private )((static |final |abstract )*)([\w|_|\$| |,|\r|\n|\t|.|\[|\]|\<|\>|\:|\?|@]*)\(([\w|_|\$| |,|\r|\n|\t|.|\[|\]|\<|\>|\:|\?|@|\(|\)|=]*)(.*)$',
        re.M);

并不能匹配所有Android系统API,大约有700个(/12000)匹配不成功,这其中大部分为标示性接口,故以上正则表达式能够较好满足提取条件。
在根据路径匹配Android系统API的过程中,需要先把路径格式的文件转换为包名.类名的形式,方便之后添加输出前缀。

2.1路径格式化
'''
    Android 系统 API 的相对路径格式化:
    将android-23\android\accessibilityservice\AccessibilityService.java
       格式化为:
           android.accessibilityservice.AccessibilityService
    将格式化之后的数据存储在 tmp 中
'''
def apiRouteReform(package):
    tmp = ''  # 存储此次遍历的类名
    flag = 0  # 标志是否开始存储
    for str in package:
        # print(str,end='')
        if (flag == 0) and (str == '\\'):
            flag = 1
            # print(flag,end='')
            continue
        # flag == 1 : 开始
        if flag == 1:
            if str == '\\':
                tmp = tmp + '.'
            elif str == '.':
                break
            else:
                tmp = tmp + str
    return tmp
2.2类:包名.类 引用关系建立

Java类在编辑过程中如果需要引用其他包的类则需要在.java文件开头添加 import 语句。如果需要引用同一个包中的类与内部类则不需要添加 import语句,但是在反编译结果中也需要知道这些类的 包名.类。可是在API函数声明时大部分时候是不包含类名的,所以为了添加参数与返回值列表的 包名.类 的声明,需要自行构造 类:包名.类 的 Dict 映射。
1.正则匹配.java文件中 import 语句部分,构造Dict;
2.与该类统一包中的所有类的 Dict;
3.该类的内部类与接口

但其上构造没有将 String 和 Class 提取出来,这个在之后的重新格式化中实现,并且基础数据类型不替换


'''
    输入:import 集合 形如:[('import ', 'android.os.Parcelable', ';'), ('import ', 'android.os.Parcel', ';')]
    输出:map 对象 map 对应关系
    <1>
    List --> java.util.List
    SomeArgs --> com.android.internal.os.SomeArgs
    类:包名 对应关系
    <2>
    package 为当前类所在包中所有类的 map 对应关系 tmpClassThisPackageMathch
    最后返回的 map 为 import 与 tmpClassThisPackageMathch 的合并
    先求 tmpClassThisPackageMathch 后 求 import 是为了更新同名类(不同包,但同名,以import 为准)的引用
    <3>
    package 所指向类的内部类、接口
    如:Callbacks 为 android.accessibilityservice.AccessibilityService 的内部接口
    则返回:android.accessibilityservice.AccessibilityServic.Callbacks
    即:
    Callbacks --> android.accessibilityservice.AccessibilityServic.Callbacks
'''
def classMatchPackage(classImport, package):

    # 构造当前 类 所在 package 中的左右类的 类:包名 对应关系
    tmpClassThisPackageMathch = classInThisPackageMatch(package)

    # package 所指向类的内部类、接口的 类(接口):包名 对应关系
    tmpClassOrInterfaceInThisPackage = classOrInterfaceInThisPackage(package)

    # 遍历 classImport 集合
    for tmpclassImport in classImport:
        tmpPackage = tmpclassImport[1]
        tmpClassSet = tmpPackage.split('.')             # 将包名根据 '.' 分隔
        tmpClassName = tmpClassSet[len(tmpClassSet)-1]  # 最后一个字符串为类名
        tmpClassThisPackageMathch[tmpClassName] = tmpPackage

    # 遍历 tmpClassOrInterfaceInThisPackage 集合
    for tmpPackage in tmpClassOrInterfaceInThisPackage:
        tmpClassSet = tmpPackage.split('.')             # 将包名根据 '.' 分隔
        tmpClassName = tmpClassSet[len(tmpClassSet)-1]  # 最后一个字符串为类名
        tmpClassThisPackageMathch[tmpClassName] = tmpPackage

    # for item in tmpClassThisPackageMathch:
    #     print(item, ':',tmpClassThisPackageMathch[item])
    #
    # print('\n\n')

    return tmpClassThisPackageMathch

构造当前 类 所在 package 中的左右类的 类:包名 对应关系

'''
    构造当前 package 中 所有 类:包名 对应关系
'''
def classInThisPackageMatch(package):

    # 得到当前包中所有类的格式化的形式的列表
    tmpClassInThisPackage = classInThisPackage(classRoute(package))

    tmpClassInThisPackageMatch = {
  
  'key':'package'}
    flag = 0;       # 标记是否开始存储数据
    for classPackage in tmpClassInThisPackage:
        tmpClassSet = classPackage.split('.')  # 将包名根据 '.' 分隔
        tmpClassName = tmpClassSet[len(tmpClassSet) - 1]  # 最后一个字符串为类名
        # 如果没有赋值过则先清空 map
        if (flag == 0):
            flag = 1
            tmpClassInThisPackageMatch.clear()
        tmpClassInThisPackageMatch[tmpClassName] = classPackage

    # 这个包里面没有类则返回空
    if (flag == 0):
        tmpClassInThisPackageMatch.clear()

    # for tmpItem in tmpClassInThisPackageMatch:
    #     print(tmpClassInThisPackageMatch[tmpItem])

    return tmpClassInThisPackageMatch
'''
    classRoute 与 classInThisPackage 是针对每一个类本身的路径的提取与同一个包内部
    类所在包的情况,这样时间与空间复杂度较高,运行时间长,为此了解决 0-记录.txt 中的
    Question 2 和 3 在遍历完所有.java 文件之后即构建 android-23 所有类与对应包名之间的
    对应关系
'''

'''
    <得到类所在包名的路径>
    输入:android-23\android\accessibilityservice\AccessibilityService.java (Java 类的相对路径)
    转化成:../android-23\android\accessibilityservice
    方便之后遍历文件夹,得到与当前类在同一个包中的所有类的名称

    tmpClassRoute 存储类所在包名的路径 如:../android-23\android\accessibilityservice
    classRouteReform 存储类所在包名的格式化 如:android.accessibilityservice
'''
def classRoute(package):
    tmp = ''    # 存储类所在的包名的路径
    flag = 0    # 用于标记是否在split得到的串之间加 斜杠
    tmpClassRoute = ''
    tmp = package.split('\\')   # 根据 斜杠 符号分隔最后一个元组为 .java 的名称 去除之后及为要得到的路径
    i = 0
    while(i < (len(tmp)-1)):
        if(flag == 0):
            flag = 1
        else:
            tmpClassRoute += '\\'
        tmpClassRoute += tmp[i]
        i += 1
    return tmpClassRoute
'''
    package 的格式:../android-23\android\accessibilityservice 及 classRoute的返回值
    得到与本.java 同一个包中所有类的格式化
    返回一个列表
'''
def classInThisPackage(package):
    classList = GetFileList(package,[])     # 得到这个文件夹下所有的类()
    classReformList = []
    for tmpPackage in classList:
        tmp = apiRouteReform(tmpPackage)
        classReformList.append(tmp)
    # print(classReformList)
    return classReformList

package 所指向类的内部类、接口

'''
    package 所指向类的内部类、接口
    如:Callbacks 为 android.accessibilityservice.AccessibilityService 的内部接口
    则返回:android.accessibilityservice.AccessibilityServic.Callbacks
    即:
    Callbacks --> android.accessibilityservice.Acc
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值