初尝Perl -- 使用aapt给apk软件包批量重命名

    不知道什么是Perl猛戳这个链接 http://zh.wikipedia.org/wiki/Perl

    任务:    
            随着手机/平板的各方面性能的不断发展(CPU,内存,存储),Android这个移动领域绝对的霸主也升级到了4.1版本,软硬件的提高意味着人们对使用体验有了越来越高的要求,在移动设备里面安装的App也越来越多,但是Android总是以刷机为乐趣的,每次刷机就意味着要备份软件(这里不讨论软件数据的备份),也就意味着备份apk,apk一多,管理起来就成了一件头痛的事情,最起码的,我们要知道我们都备份了哪些apk,最最起码要知道它们叫什么名字,因为不管在windows还是linux平台下面,都是无法直接解析apk的名字的。所以不管你是以什么样的方式备份出来的apk,它总不可能是按你的要求来命名的,今天的目标/任务就是把apk按你的要求来命名。

    方法:
            由于在windows/linux上无法直接通过系统的文件管理器解析apk的名字等等属性,所以必须要借助别的工具,这其中最好的必须是Google自家的aapt工具,不知道aapt是什么?猛戳这个链接http://www.ltesting.net/ceshi/ceshijishu/sjcs/android/2012/0511/204843.html。aapt可以直接读取出来一个apk的各种属性,比如AndroidManifest.xml里面定义的包名,软件名,版本号,使用的权限等等信息。使用aapt读取出来的信息是一大堆,所以必须要使用正则表达式的模式匹配获取我们所需要的信息,然后再根据要求来给相应的apk重命名。注意,这个地方是要给当前目录下所有的apk自动重命名,所以,必然要用到for语句(在perl里面是foreach)。

    先介绍一下aapt的使用。这里只介绍用到的功能,dump,具体命令是aapt d(ump) badging mms.apk。它会打印出如下的信息:

package: name='com.elsdoerfer.android.autostarts' versionCode='24' versionName='1.7.7'
sdkVersion:'4'
targetSdkVersion:'14'
application-label:'Autostarts'
application-label-zh:'自启管家'
application-icon-160:'res/drawable/icon.png'
application-icon-240:'res/drawable-hdpi/icon.png'
application: label='Autostarts' icon='res/drawable/icon.png'
launchable-activity: name='com.elsdoerfer.android.autostarts.ListActivity'  label='Autostarts' icon=''
uses-permission:'android.permission.WRITE_SETTINGS'
uses-permission:'android.permission.WRITE_SECURE_SETTINGS'
uses-permission:'android.permission.CHANGE_COMPONENT_ENABLED_STATE'
uses-configuration:
uses-gl-es:'0xffffffff'
uses-feature:'android.hardware.touchscreen'
uses-implied-feature:'android.hardware.touchscreen','assumed you require a touch screen unless explicitly made optional'
main
other-activities
supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'zh'
densities: '160' '240'

    注意,在windows的cmd里面,编码是cp936,和aapt默认输出的utf8是冲突的,所以如果你直接执行上述命令,会在cmd的窗口中看到所有的中文都是乱码的,正确的方法是把输出的结果重定向到文本文件里面,再用记事本或其它的工具打开输出的文本文件,就不会有乱码了。关于aapt的其它用法请自行查看帮助,直接输入aapt即可。

    我们的目标就是要从上面的那一堆的数据中,用perl里面的正则表达式提取出我们需要的信息,我所需要的信息数据于这样:Google Play 音乐_v5.0.1053J.731804_(com.google.android.music).apk,软件名称_v版本号_(apk的包名).apk。

    通过分析上面的输出结果可以看到:
        name='com.elsdoerfer.android.autostarts'
        versionName='1.7.7'
        application-label:'Autostarts'
        application-label-zh:'自启管家'
    这四个字符串是我们要需要的,下面的任务就是如何把它们的值提取出来。下面上整个程序的perl源代码。


# use strict;
use Encode;
use open ':encoding(GBK)', ':std';
use open ':encoding(UTF-8)';

my @files = (glob(".*.apk"), glob("*.apk"));

foreach (@files)
{
    # print "Start Processing -> $_ \n";

    # 执行aapt并赋值给$info
    $info = `aapt d badging $_`;

    $info =~ m/name=\'(.*?)\'/;
    $pkg_name = $1;  # apk的包名
    $info =~ m/versionName=\'(.*?)\'/;
    $version = $1;  # apk的版本号

    # 判断是否存在 label-zh_CN名字,
    # 若不存在则以label名字代替
    if ($info =~ m/application-label-zh_CN:\'(.*?)\'/) {
        $name = $1;
    }
    else {
        $info =~ m/application-label:\'(.*?)\'/;
        $name = $1;  
    }

    # 拼接格式化后想要的文件名
    $out_filename = "$name\_v$version\_($pkg_name).apk";

    open(LOG, ">>log.txt") || warn "Can't Open the file";
    print LOG "$out_filename \n";

    # 必须要转换成gb2312编码,否则会造成中文乱码
    $out_filename = encode("gb2312", $out_filename);
    print decode("gb2312", $out_filename);

    # 若修改后导致文件重名,则输出警告,不作处理
    if(-e $out_filename){
        warn "Can't rename $_ to $out_filename.
        The $out_filename exists!\n";
    }
    else{  
        # 重命名文件,若重命名失败,则输出警告
        # 注意此处不要调用system("ren $a $b")或`ren a b`
        # 因为直接调用会造成文件名有空格会重命名失败的问题
        rename $_, $out_filename
            or warn "Rename $_ to $out_filename failed: $!\n";

    }

    print "\n";
}

close LOG || warn "Can't close the file";



    对这个perl文件作一个小小的解释:
        my @files = (glob(".*.apk"), glob("*.apk"));    使用glob()函数获取到目录下面的所有文件名,这里使用的是*.apk和.*.apk,这两个是有区别的,因为perl源于unix/linux,在linux下面,所有以点开头的文件都是隐藏文件,程序默认是不解析的,所以我们必须要加上.*.apk。
        foreach (@files)     perl里面的for循环是用foreach来实现的,对于@files这个数组里面的所有元素,它会依次执行大括号里面的语句。
        $info =~ m/name=\'(.*?)\'/;  正则表达式,=~m表示模式匹配,/.../之间的内容是正则表达式的主体,name=\'(.*?)\'表示取name='...'单引号里面的内容,就是我们所需要的。具体正则表达式的语法,请自行google。
        $out_filename = encode("gb2312", $out_filename);   由于 perl起码起源于linux/unix,故其内部处理数据都是以utf8编码来实现的,而windows的cmd是cp936,如果不做编码转换的话,直接调用perl的rename对文件重命名的时候就会出现中文乱码的情况,所以这里用encode()函数把所需要的目标文件名转换成gb2312编码,即cp936。

    执行perl rename_apk.pl,它会自动查找当前目录下的所有apk,并按给定的 软件名称_v版本号_(apk的包名).apk 这个规则重命名,同时会在当前目录下输出一个log.txt文件,它会记录所有已经格式化好的目标文件名,一来是可以查看目标文件名有没有正确,二来是可以给那些因为有特殊字符而不能自动重命名的apk一个供修改的文件名,方便手动修改文件名然后再手动重命名。

    好了,以上这个perl文件就是我花了一天时间从零开始学习perl和正则表达式所写出来的东西,达到了我所要的目标,现总结一下对perl印象深刻的几个地方:
        1. 它的变量不分类型,什么整数啊,浮点数啊,字符串啊,都是直接用$var来定义的。
        2. `...`,可以使用这条语句执行原本可以在cmd里面执行的命令,即直接调用外部shell的命令,当然,也可以用system()来调用,作用貌似是一样的。
        3. 数组使用@var来定义,可以直接用foreach(@var)来遍历。
        4. 在foreach中,直接使用$_这个变量来调用数组中存在的当前正在使用的元素的值。比如数据中存的是5个字符串,foreach就是依次遍历5次,然后每次使用$_就是直接调用当前个元素的值。
        5. 在print里面,可以直接把变量替换成对应的值输出。比如$a = "abc",  print "123$a"输出的结果就是123abc,需要注意的是此处的print后面是用的双引号,如果是用单引号,则不解析变量的值,这里有点类似于PHP的语法。

    OK,学习笔记记录完毕,凌晨12点40分,准备睡觉。

posted @ 2013-07-29 00:50  Sunny_zhufeng  阅读(372)  评论(0编辑  收藏  举报