`

自动化findViewById缩减工具

阅读更多

一、背景

在Android O版本之后,findViewById 函数现在返回的是 <T extends View>,所以以后 findViewById 就不需要强转了。如果项目中compileSdkVersion >= 26,使用findViewById就会提示警告,表示可以不用再写强转了。如下所示:

所以看到这部分的时候就觉得不舒服,而且AS代码区右侧会提示标黄的小警告,光标移上去会提示:Casting 'item.findViewById(R.id.xxx)' to 'TextView' is redundant.

故而觉得此事的解决对强迫症患者挺有必要的,而且也是代码规范迟早要做的事情。所以使用python编写了这个脚本工具。命名为afc.py (auto findViewById cut)。

二、分析

关于findViewById的用法,不外乎如下几类情况:

 

1.仅findViewById
a. TextView tv1 = (TextView)findViewById(R.id.tv);//无空格
b. TextView tv2 = (TextView) findViewById(R.id.tv);//有空格
c. TextView tv3 = (TextView)root.findViewById(R.id.tv);
d. TextView tv4 = (TextView) root.findViewById(R.id.tv);
正则1:\(\w+\)\s*(?=((\w+\.)?findViewById\(R\.id\.\w+\);))
 
2.findViewById后set操作
e. ((TextView)findViewById(R.id.tv)).setText("text");//设置文字
f. ((TextView) findViewById(R.id.tv)).setOnClickListener(xxx);//设置点击
g. ((TextView)root.findViewById(R.id.tv)).setText(“text”);
h. ((TextView) root.findViewById(R.id.tv)).setOnClickListener(xxx);
正则2:\(\(\w+\)\s*(\w+\.)?findViewById\(R\.id\.\w+\)\)(?=\.(setOnClickListener|set...))
正则3:(\w+\.)?findViewById\(R\.id\.\w+\)

 这里我将他们对应的正则表达式也给了出来。其中e和g是不能忽略强转的。后面的方法是特定的类型才具有的。

 

三、正则

正则1: \(\w+\)\s*(?=((\w+\.)?findViewById\(R\.id\.\w+\);))

(?=...)代表...是在这个匹配的前面

\s*代表空格个数0-n个

\(\w+\)代表()和英文字符

(\w+\.)?代表若干英文字符和点的组合有0-1个

其他就是字面意思。所以正则1匹配的就是a,b,c,d这些类型的行。

正则2:\(\(\w+\)\s*(\w+\.)?findViewById\(R\.id\.\w+\)\)(?=\.(setOnClickListener|set...))

这个就好理解多了。意思就是匹配f,h这些类型的行。只要在最后的setOnClickListener|set...里面不去包含setText就可以顺利排除这些不能忽略强转的用法了。

正则3:(\w+\.)?findViewById\(R\.id\.\w+\)

含义略。这个正则存在的意义就是替换正则2匹配的内容,比如:正则2匹配出来了((TextView)root.findViewById(R.id.tv)),正则3从这中间拿出root.findViewById(R.id.tv)。从而进行最终的文本替换。

四、脚本

#!/usr/bin/python
# coding=utf-8
 
import os,re,fileinput,sys
 
#根据文件扩展名判断文件类型
def endWith(s, *endstring):
    array = map(s.endswith,endstring)
    if True in array:
        return True
    else:
        return False
 
def searchFiles(dirname):
    # 匹配该样式类型的行:(TextView)findViewById(R.id.tv);
    pattern1 = re.compile(ur'\(\w+\)\s*(?=((\w+\.)?findViewById\(R\.id\.\w+\);))')
 
    # 匹配该样式类型的行:findViewById(R.id.xxx) 或 root.findViewById(R.id.xxx)
    pattern3Str = ur'(\w+\.)?findViewById\(R\.id\.\w+\)\)'
    pattern3 = re.compile(pattern3Str)
 
    # 区分下面两类的行:setText的类型转换不能忽略,而setOnClickListener可以
    # ((TextView)findViewById(R.id.tv)).setText("text");
    # ((TextView) findViewById(R.id.tv)).setOnClickListener(xxx);
    # 可以忽略的加入下面数组
    pattern2List = ['setOnClickListener']
    # 匹配该样式类型的行 ((TextView) findViewById(R.id.tv)) 或 ((TextView)root.findViewById(R.id.tv))
    pattern2Str = ur'\(\(\w+\)\s*' + pattern3Str + '(?=\.(' + '|'.join(pattern2List) + '))'
    pattern2 = re.compile(pattern2Str)
 
    count1 = 0
    count2 = 0
    for root,dirs,files in os.walk(dirname):
        for file in files:
            if endWith(file, '.java'):
                # 打开文件
                filename = root + os.sep + file #绝对路径
                filename = filename.replace("\\","\\\\") #将路径中的单反斜杠替换为双反斜杠,因为单反斜杠可能会导致将路径中的内容进行转义了,replace函数中"\\"表示单反斜杠,"\\\\"表示双反斜杠
                 
                # fileinput模块支持文件的边读边写
                for line in fileinput.input(filename, inplace=True):
                    # 返回一个含两个元素的元组,索引0为替换后的行,索引1为该行替换次数
                    result1 = re.subn(pattern1, "", line)
                    line = result1[0]
                    count1 = count1 + result1[1]
 
                    # 找到符合模式2的要替换的部分
                    toreplace = re.search(pattern2, line)
                    if toreplace != None:
                        # 从要替换中找到要替换为的部分
                        replaced = re.search(pattern3, toreplace.group(0))
                        if replaced != None:
                            # 执行最终的替换
                            result2 = re.subn(pattern2, replaced.group(0), line)
                            line = result2[0]
                            count2 = count2 + result2[1]
 
                    # 将模式1和模式2的结果写回文件
                    print line.rstrip()
    print '成功!findViewById总转换数:%d个。' % (count1 + count2)
    print '模式1的替换数:%d个;' % count1
    print '模式2的替换数:%d个。' % count2
 
if __name__ == '__main__':
    searchFiles(sys.argv[1])

 

五、使用及结果

使用方法非常简单,如下:

python afc.py (本地仓库路径)

 例如:

代码修改完后,就去提pr集赞merge代码吧。遇到的问题:checkstyle的时候可能会有没有使用的import。

适用范围:凡compileSdkVersion >= 26的Android仓库同学均可使用。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics