android 批量替换,用perl脚本批量替换Android项目中的代码

有幸在之前的工作中用到了perl脚本,个人非常喜欢它的简约和文件操作性,有兴趣的同学欢迎一起交流

wechat: whatistheman 备注:简书 perl

最近做Android项目遇到了一个需求:项目使用了大量的ButtterKnife,由于ButtterKnife的依赖注入特性,使得项目整体编译速度很慢,达10几分钟

为提高效率,考虑替换ButtterKnife改为原生的findViewById方式。

在项目中搜了下用到的地方有小几万处,故放弃人工修改,使用perl脚本批量替换,代码如下,随笔。

main.perl

#!/usr/bin/perl

use warnings;

use strict;

use constant JAVA_FILE => qr/^.*\S+.java\s*$/;

# 扫描目录下的所有.java文件,进行类文件级别的自动化替换ButterKnife操作

# 模块根目录

my $dir = $ARGV[0];

my $pkgName = "";

scan_file($dir);

sub scan_file{

my @files = glob($_[0]);

foreach (@files){

if(-d $_){

my $path = "$_/*";

scan_file($path);

}elsif(-f $_ && $_ =~ JAVA_FILE){

system "./checkUseBF.perl $_ $pkgName";

}

}

}

checkUseBF.perl

#!/usr/bin/perl

use warnings;

use strict;

# 脚本调用前处理掉 mUnbinder = ButterKnife.bind(this, view); 这种情况

use constant BUTTER_KNIFE => qr/^\s*import\s+butterknife.(\S+)\s*$/;

use constant BUTTER_KNIFE_ROOT_BIND => qr/^\s*ButterKnife.bind\((.*)\)\;.*$/;

use constant BUTTER_KNIFE_BINDVIEW => qr/^\s*\@BindView\((\S+)\).*$/;

use constant BUTTER_KNIFE_BINDVIEW_DEF => qr/^\s*\@BindView\(\S+\)\s+(\S+\s*\S+)\;.*$/;

use constant BUTTER_KNIFE_ONCLICK => qr/^\s*\@OnClick\((.*)\).*$/;

use constant BUTTER_KNIFE_ONCLICK_FUNC => qr/^\s*\@OnClick\(.*\).*\s+(\S+\s*\(.*\)).*$/;

use constant BUTTER_KNIFE_ONCLICK_FUNC_PARAM => qr/^\s*(\S+)\s*\((.*)\).*$/;

# 有继承无接口实现

use constant CLASS_DEFINITION_TYPE1 => qr/^.*class\s+\S+\s+(extends\s+\S+).*$/;

# 有继承有接口实现,但接口实现被排到了第二行

use constant CLASS_DEFINITION_TYPE1_1 => qr/^.*(implements).*$/;

# 有继承有接口实现

use constant CLASS_DEFINITION_TYPE2 => qr/^.*class\s+\S+\s+extends\s+\S+\s+(implements).*$/;

# 无继承无接口实现

use constant CLASS_DEFINITION_TYPE3 => qr/^.*class\s+(\S+)\s*\{.*$/;

# 无继承有接口实现

use constant CLASS_DEFINITION_TYPE4 => qr/^.*class\s+\S+\s+(implements).*$/;

use constant IMPORT_R => qr/^\s*import\s+\S+\.R2\;.*$/;

use constant IMPORT => qr/^\s*import\s+\S+\;.*$/;

use constant PARENT_CLASS_TYPE => qr/^.*\s*extends\s+(\S+).*$/;

use constant EMPTY_LINE => qr/^\s*\n$/;

# TODO 考虑两个bind在同一行 或者 在一行半+半行的情况

# 传入参数 文件目录

my $foundFile = $ARGV[0];

open CONFIG,"

my @array = readline CONFIG;

close CONFIG;

# 记录当前文件调用ButterKnife.bind的次数

my $bindCount = 0;

# 记录当前文件类定义的次数

my $classDefCount = 0;

# 记录当前文件类的种类 1 activity 2 自定义view

my $classDefType = 0;

# 记录当前文件调用ButterKnife.bind的参数 前两个为bind的参数,第三个参数为所在行数,""即不存在

my @bindParams = ("","","");

# 记录import所在行

my @importLines = ();

# 记录@OnClick所在行

my @onClickLines = ();

# 记录@BindView所在行

my @bindViewLines = ();

my $split = '~';

# key layoutId value 函数体 xxx(View x) or xxx() 例如 leftBtnClick(View v)

my %onClickMap = ();

# key layoutId value 控件类名+实例 例如 TextView mAuthNameView

my %bindViewMap = ();

# 待插入的接口名称

# user change var

my $insertImplments = "doBindView";

# user change var

my $insertImplmentsType = "IViewBinder";

# user change var

my $param_name = "view";

my $param_type = "View";

# user change var

# 待插入的接口定义

my $implmentMethod_view = " \@Override\n public void $insertImplments \($param_type $param_name\) ";

# user change var

my $importDef = "import com\.smile\.gifmaker\.mvps\.IViewBinder\;\n";

print "start all $foundFile\n";

# 第一大步骤的循环中将会实现如下四个目的

# 1 判断当前文件首部是否有import butterknife库,如果没有引用则直接返回,如果有则记录在变量中

# 2 判断当前文件是否调用了大于一次ButterKnife.bind 若存在调用则记录其位置,若仅调用了一次bind则执行后续操作,否则在文件首部插入注释"perl check..."后退出

# 3 判断当前文件是否有定义大于一次的类定义 若不等于一次,则在文件首部插入注释"perl check..."后退出

# 4 判断当前文件是否使用@OnClick、@BindView,若有则记录所在行位置

for(my $i = 0;$i<=$#array;$i++){

#找到ButterKnife.bind所在行

if($array[$i] =~ BUTTER_KNIFE_ROOT_BIND){

my $params = $1;

if($params =~ ","){

$bindParams[0] = (split(/,/,$params))[0];

$bindParams[1] = (split(/,/,$params))[1];

}else{

$bindParams[0] = $params;

}

$bindParams[2] = $i;

$bindCount++;

if($bindCount > 1){

print "调用了大于一次ButterKnife.bind,插入注释回退\n";

unshift(@array, "//perl check,when the Java file uses method 'Butterknife.bind' twice or more,do nothing,signed by wangpeng09\@kuaishou.com \n");

overrideFile(@array);

exit;

}

}elsif($array[$i] =~ BUTTER_KNIFE){

# 记录import行,留在后续删除

push(@importLines,$i);

}elsif($array[$i] =~ BUTTER_KNIFE_ONCLICK){

push(@onClickLines,"$i$split$1");

}elsif($array[$i] =~ BUTTER_KNIFE_BINDVIEW){

push(@bindViewLines,"$i$split$1");

}elsif($array[$i] =~ CLASS_DEFINITION_TYPE1

|| $array[$i] =~ CLASS_DEFINITION_TYPE2

|| $array[$i] =~ CLASS_DEFINITION_TYPE3

|| $array[$i] =~ CLASS_DEFINITION_TYPE4){

$classDefCount++;

if($classDefCount > 1){

print "存在大于一次的类定义,插入注释回退\n";

unshift(@array, "//perl check,when the Java file define class type twice or more,do nothing,signed by wangpeng09\@kuaishou.com \n");

overrideFile(@array);

exit;

}

# 判断当前类文件是否为activity文件或自定义view文件 若是这两种类型,则一定有继承关系

if($array[$i] =~ PARENT_CLASS_TYPE){

my $parentClass = $1;

if($parentClass =~ /Activity/){

$classDefType = 1;

}elsif($parentClass =~ /View/

|| $parentClass =~ /Layout/){

$classDefType = 2;

}

}

}

}

print "finish step 1 调用ButterKnife.bind的次数:$bindCount param1 : $bindParams[0] param2 : $bindParams[1] param3 : $bindParams[2]+1\n";

if($#importLines < 0){

print "no import\n";

exit;

}

print "finish step 2 引用import行数:($#importLines+1) \n";

# 3 找到import所在行删除

for my $line(@importLines){

$array[$line] = "";

}

print "finish step 3 找到import所在行删除 \n";

# 4 通过@bindParams 找到ButterKnife.bind所在行将其用接口表达方式代替

# 分两类处理 ButterKnife.bind(this) 或 ButterKnife.bind(this, view)

if($bindParams[2] ne ""){

if(hasMethodParams()){

# 对于ButterKnife.bind(this, view)直接拿到view传递参数

$array[$bindParams[2]] =~ s/ButterKnife\.bind\(.*\)\;/$insertImplments\($bindParams[1]\)\;/g;

}else{

# 对于ButterKnife.bind(this),分情况讨论 1 activity 2 自定义view

if($classDefType == 1){

# activity

$array[$bindParams[2]] =~ s/ButterKnife\.bind\(.*\)\;/$insertImplments\(getWindow\(\)\.getDecorView\(\)\)\;/g;

}elsif($classDefType == 2){

# 自定义view

$array[$bindParams[2]] =~ s/ButterKnife\.bind\(.*\)\;/$insertImplments\(this\)\;/g;

}

}

}

print "finish step 4 找到ButterKnife.bind所在行将其用接口表达方式代替 \n";

# 5 找到@OnClick的位置,删除所在行并记录控件id与方法名称的对应关系

for my $line(@onClickLines){

my $lineNum = (split(/$split/,$line))[0];

my $layoutIds = (split(/$split/,$line))[1];

# 找到@OnClick注解对应的函数体 xxx(xx) 型参可能为空或View

my $func = "$array[$lineNum]$array[$lineNum+1]";

$func =~ s/\n//g;

# 精确匹配函数体 xxx(View x) or xxx() 并记录layoutId与函数体的对应关系

if($func =~ BUTTER_KNIFE_ONCLICK_FUNC){

$onClickMap{$layoutIds} = $1;

}

# 精准匹配 @OnClick(xxx) 并删除

$array[$lineNum] =~ s/\@OnClick\($layoutIds\)//g;

if($array[$lineNum] =~ EMPTY_LINE){

$array[$lineNum] = "";

}

}

print "finish step 5 找到\@OnClick的位置,删除所在行并记录控件id与方法名称的对应关系 \n";

# 6 找到@BindView

for my $line(@bindViewLines){

my $lineNum = (split(/$split/,$line))[0];

my $layoutId = (split(/$split/,$line))[1];

# 找到@BindView注解对应的类型声明 例如 ImageView mImageView;

my $func = "$array[$lineNum]$array[$lineNum+1]";

$func =~ s/\n//g;

# 精确匹配类型声明 例如 ImageView mImageView;

if($func =~ BUTTER_KNIFE_BINDVIEW_DEF){

$bindViewMap{$layoutId} = $1;

}

$layoutId =~ s/ //g;

# 精准匹配 @BindView(xxx) 并删除

$array[$lineNum] =~ s/\@BindView\($layoutId\)//g;

if($array[$lineNum] =~ EMPTY_LINE){

$array[$lineNum] = "";

}

}

print "finish step 6 找到BindView 精准匹配 \@BindView(xxx) 并删除 \n";

# 7 找到文件内的class定义位置,实现接口

for(my $i = 0;$i<=$#array;$i++){

my $tag;

# 在类的定义部分插入implements的实现 合并当前行与下一行两行判断!!!防止定义被分成两行的情况

if($array[$i] =~ CLASS_DEFINITION_TYPE1){

$tag = $1;

if($array[$i+1] =~ CLASS_DEFINITION_TYPE1_1){

# 有继承有接口,但接口定义在下一行

$tag = $1;

$array[$i+1] =~ s/$tag/$tag $insertImplmentsType,/g;

addImplementsDef($i+1);

}else{

# 有继承无接口

$array[$i] =~ s/$tag/$tag implements $insertImplmentsType/g;

addImplementsDef($i);

}

}elsif($array[$i] =~ CLASS_DEFINITION_TYPE2){

$tag = $1;

# 有继承有接口

$array[$i] =~ s/$tag/$tag $insertImplmentsType,/g;

addImplementsDef($i);

}elsif($array[$i] =~ CLASS_DEFINITION_TYPE3){

$tag = $1;

# 无继承无接口

$array[$i] =~ s/$tag/$tag implements $insertImplmentsType/g;

addImplementsDef($i);

}elsif($array[$i] =~ CLASS_DEFINITION_TYPE4){

$tag = $1;

# 无继承有接口

$array[$i] =~ s/$tag/$tag $insertImplmentsType,/g;

addImplementsDef($i);

}

}

print "finish step 7 找到文件内的class定义位置,实现接口 \n";

for(my $i = 0;$i<=$#array;$i++){

if($array[$i] =~ IMPORT_R){

$array[$i] =~ s/R2/R/g;

last;

}

}

for(my $i = 0;$i<=$#array;$i++){

if($array[$i] =~ IMPORT){

$array[$i] = $importDef.$array[$i];

last;

}

}

print "finish step 8 第一个import之前插入新增接口的import行 \n";

overrideFile(@array);

print "finish all $foundFile\n";

################################################################################ function define ################################################################################

sub addImplementsDef{

#在类的定义尾部'{'字符之后,添加对接口方法的实现,并在实现中实现控件初始化和事件监听的定义

if($array[$_[0]] =~ /{/){

my $content = writeMethodContent();

$array[$_[0]] =~ s/\{/\{\n$implmentMethod_view\{\n$content\n \}\n/g;

}else{

addImplementsDef($_[0]+1);

}

}

# 合成实现接口方法的内容 控件初始化、设置事件监听等

sub writeMethodContent{

# step1 控件初始化

my $widgetInit = "";

foreach my $key(keys %bindViewMap){

my @widgets = (split(/ /,$bindViewMap{$key}));

my $paramClass = $widgets[0];

my $paramName = $widgets[$#widgets];

my $layoutId = $key;

$layoutId =~ s/R2/R/g;

$widgetInit .= " $paramName = \($paramClass\)findViewById\($layoutId\)\;\n";

}

# step2 设置监听事件

my $widgetSetListener = "";

foreach my $key(keys %onClickMap){

my $methodDef = $onClickMap{$key};

my @layoutIds = (split(/,/,$key));

for my $item(@layoutIds){

$item =~ s/R2/R/g;

my $methodName = "//\n";

my $methodparams = "";

my $lamadaParam = "view";

if($methodDef =~ BUTTER_KNIFE_ONCLICK_FUNC_PARAM){

$methodName = $1;

$methodparams = $2;

$methodparams =~ s/ //g;

}

if($methodparams ne ""){

$methodparams = $lamadaParam;

}

$widgetSetListener .= " if\($param_name\.findViewById\($item\) \!\= null\) \{\n $param_name\.findViewById\($item\)\.setOnClickListener\($lamadaParam \-\> \{$methodName\($methodparams\)\;\}\)\;\n \}\n";

}

}

return $widgetInit.$widgetSetListener;

}

# 空字符串返回false 否则返回true 判断接口函数是否需要带view参数

sub hasMethodParams{

$bindParams[1] =~ s/ //g;

return $bindParams[1];

}

# 对处理后的内容数组写入并替换原文件

sub overrideFile{

my @list = @_;

system "rm $foundFile";

system "touch $foundFile";

open NEW_FILE,">",$foundFile || die "Error, can`t open the file for writing";

foreach my $line(@list){

print NEW_FILE "$line";

}

close NEW_FILE;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值