Apk瘦身脚本 - res资源可用性检查

本文介绍了如何使用Python脚本在AndroidStudio项目中快速检查和删除无用的资源,如layout、drawable和mipmap,以提高代码健壮性和项目性能。脚本通过遍历资源目录,检查文件引用并执行Gradleclean来实现资源剔除。

AndroidStudio为我们提供了针对无用res检查的工具: Lint 

Lint可以帮我们检索项目中可能存在错误的地方,平时功能代码写完后,也建议大家用其检查一下代码,帮助我们提高代码的健壮性。但这里主要介绍脚本,就不介绍Lint的使用了。

在一些大一点的项目,Lint在执行之后会Run很长时间,且只是给你找出问题点。在瘦身时可能存在一定量的res需要剔除,用Lint检查起来就会相对慢一些,且有些Code删除之后我们也需要重新检查其是否有引用哪些layout、drawable等,这些也是可以一并检查删除的。

下面是脚本代码,实现起来相对简单暴力,直接放在Android项目下运行即可。

# coding=utf-8
import fnmatch
import os
import subprocess
from typing import Sequence


def run_command(command, project_dir):
    subprocess.check_call(command, cwd=project_dir)


def run_gradle_clean(project_dir):
    command = ['./gradlew', 'clean']
    run_command(command, project_dir)
    print(f"Run gradle clean successfully! ==========")


def file_split(split_url):
    import os
    (parent_path, file_name) = os.path.split(split_url)
    (shot_name, extension) = os.path.splitext(file_name)
    if split_url.endswith('.9.png'):
        return parent_path, file_name, file_name.split(".")[0], '.9.png'
    else:
        return parent_path, file_name, shot_name, extension


# 驼峰 + 首字母大写
def hump_with_upper_primary(text):
    arr = filter(None, text.lower().split('_'))
    res = ''
    j = 0
    for i in arr:
        # if j == 0:
        #     res = i
        # else:
        res = res + i[0].upper() + i[1:]
        j += 1
    return res


# 驼峰 + 首字母小写
def hump_with_lower_primary(text):
    arr = filter(None, text.lower().split('_'))
    res = ''
    j = 0
    for i in arr:
        if j == 0:
            res = i
        else:
            res = res + i[0].upper() + i[1:]
        j += 1
    return res


def _res_files(_root: str) -> Sequence[str]:
    for _base, _, _files in os.walk(_root):
        yield from (os.path.join(_base, file) for file in _files if
                    '/res/' in _base or file == 'AndroidManifest.xml' and 'build' not in _base and 'node_modules' not in _base)


def fuck_unused_normal(_check_dir, _type, _file_shot_name):
    if '-' in _type:
        _type = _type.split('-')[0]

    r_string1 = 'R.' + _type + '.' + _file_shot_name
    r_string2 = '@' + _type + '/' + _file_shot_name

    for _root, _dirs, _files in os.walk(_check_dir):
        for name in _files:
            if not match_patterns(file_split(name)[1], check_file_end_fix):
                continue
            check_file_path = os.path.join(_root, name)
            with open(check_file_path, encoding='utf-8') as f:
                _content = f.read()
                if r_string1 in _content or r_string2 in _content:
                    # 该文件被其他文件引用了
                    return True
    return False


def fuck_unused_layout(_check_dir, _type, _file_shot_name) -> bool:
    r_string1 = 'R.' + _type + '.' + _file_shot_name
    r_string2 = '@' + _type + '/' + _file_shot_name
    r_string3 = hump_with_upper_primary(_file_shot_name) + 'Binding'
    r_string4 = 'kotlinx.android.synthetic.main.' + _file_shot_name

    for _root, _dirs, _files in os.walk(_check_dir):
        for name in _files:
            if not match_patterns(file_split(name)[1], check_file_end_fix):
                continue
            check_file_path = os.path.join(_root, name)
            with open(check_file_path, encoding='utf-8') as f:
                _content = f.read()
                if (r_string1 in _content
                        or r_string2 in _content
                        or r_string3 in _content
                        or r_string4 in _content):
                    # 该文件被其他文件引用了
                    return True
    return False


def match_patterns(_file, _patterns):
    for _pattern in _patterns:
        if fnmatch.fnmatch(_file, _pattern):
            return True
    return False


def handle_res_list_check_and_remove(_res_path):
    res_list = []
    for _dir in os.listdir(_res_path):
        if match_patterns(_dir, res_type_list):
            res_list.append(_dir)
    for res_type in res_list:
        type_dir = os.path.join(_res_path, res_type)
        for file_name in os.listdir(type_dir):
            if file_name in ignore_prefix:
                continue
            file_path = os.path.join(type_dir, file_name)
            shot_name = file_split(file_name)[2]
            if 'layout' in res_type:
                is_ref = fuck_unused_layout(CWD, 'layout', shot_name)
                if not is_ref:
                    print(f'remove {file_path}')
                    os.remove(file_path)
            else:
                is_ref = fuck_unused_normal(CWD, res_type, shot_name)
                if not is_ref:
                    print(f'remove {file_path}')
                    os.remove(file_path)


ignore_root = ['*mjbNormal*', '*mjbForTest*']  # 忽略检查这些文件下是否存在目标资源
ignore_prefix = []  # 忽略的文件
check_file_end_fix = ['*.xml', '*.java', '*.kt']  # 在哪些文件中检查是否有引用目标文件
res_type_list = ['anim*', 'drawable*', 'mipmap*']  # res下哪些资源是可以被当成目标文件的,目标文件会被检查删除
CWD = os.getcwd()  # 项目路径

if __name__ == '__main__':
    run_gradle_clean(CWD)
    for root, dirs, files in os.walk(CWD):
        if match_patterns(root, ignore_root):
            continue
        if root.endswith('/res'):
            handle_res_list_check_and_remove(root)  # root是项目中各级的res文件路径

食用方式

python3 remove_unused_res.py

可能会有水土不服的情况,使用者可以具体问题具体处理

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值