本文介绍Android 7.1平台下Google LeanbakLauncher的反编译流程,并使用AS工具重新打包修改。
最近需要开发一款TV桌面应用,调研了各类桌面,如当贝桌面,小米桌面,开源的猫桌面。其中当贝桌面,小米桌面不符合Google推荐的Tv开发方式,猫桌面简洁,但是功能较少,如缺少APP的排序,卸载管理等功能。猫桌面的Github地址:https://github.com/JackyAndroid/AndroidTVLauncher。
相比之下,Google LeanbackLauncher页面较为绚丽,动画完美,符合Tv开发规范,是一款支持应用移动,卸载,视频内容推送的Leanback风格的桌面软件。只是这款软件不开源,于是自然想到反编译。
Google LeanbackLauncher的桌面UI如下:
反编译,用AS重新打包的UI如下(更换了壁纸,添加了以太网连接标记,视频推荐内容暂时无法还原):
下面正式介绍反编译与回编译的流程:
(1) 下载LeanbackLauncher http://www.coolapk.com/apk/com.google.android.leanbacklauncher
(2) 一般的反编译工具推荐是APKTool,Dex2Jar,JadUI,其中APKTool用于解压资源文件,Dex2Jar用于将dex转换成Jar包,JadUI用于查看jar文件对应的java文件。不过我反编译LeanbackLauncher只用到了APKTool,此外,借助了一个在线网站,直接将APK转换成Java文件。
(3) 将下载下来的APK文件传到网站 http://www.javadecompilers.com/apk,等待一段时间之后,网站会自生成APK文件反编译后对应的Java文件和资源文件,下载下来解压。
(4) 解压之后的文件包括src和res文件,但是res文件的id是一连串的数字,无法直接翻译成@+id的形式,于是使用APKTool工具来解决该问题。从APKTool网站下
载该工具并配置好环境之后,使用命令:apktool d LeanbackLauncher.apk -o C:/LeanbackLauncher将资源文件解压到C盘的LeanbackLauncher目录下。
(5) 启动Android Studio,新建LeanbackLauncher工程,包名为com.google.android.leanbacklauncher,将在线网站反编译的java文件和APKTool工具反编译出来的res文件复制到对应的目录下,重新build,解决相关错误之后,编译可生成一个APK。
(6) 生成的APK如果直接install至Box上是起不来的,原因如下:
一些权限必须是系统应用才可以申请使用的,所以必须使用芯片厂商提供的签名工具,将其变成系统应用
相关findViewById,getColor,getDrawable等方法的参数是一连串的数字,必须将其变成R.type.name的引用
(7) 在工程目录下包含res/values/public.xml,这个xml文件包含所有资源引用的类型,名字,和16进制的引用值。Java文件下findViewById,getColor,getDrawabel方法的参数,转换成16进制之后,能在这个xml找到对应的某一行,通过组合成R.type.name的形式替换这些方法的参数。
(8) 通过手动一个一个去替换findViewById,getColor等方法的参数,显然太费时间,也很无聊,于是我用Python写了一个脚本文件去搜索,替换文件中的这些参数,Python脚本代码如下:
import sys
import os
def readAllLine(filePath):
allLines = []
with open(filePath, 'r') as f:
for line in f:
allLines.append(line)
return allLines
def filterLines(orginLines, str):
lines = []
for line in orginLines:
if line.find(str) >= 0:
lines.append(line)
return lines
def extractValueForLine(line, key, start, end):
searchIndex = line.find(key)
if searchIndex < 0:
return None
searchIndex += 1
while searchIndex < len(line) and line[searchIndex] != start:
searchIndex += 1
if searchIndex >= len(line):
return None
start_tag = searchIndex
searchIndex += 1
while searchIndex < len(line) and line[searchIndex] != end:
searchIndex += 1
if searchIndex >= len(line):
return None
end_tag = searchIndex
end_tag += 1
str_value = ''
list_value = []
for i in range(start_tag + 1, end_tag - 1):
list_value.append(line[i])
str_value = str_value.join(list_value)
str_value = str_value.strip()
print("start_tag->" + start, start_tag)
print("end_tag->" + end, end_tag)
return str_value
def mergeValue(str_value, filePath):
if str_value.find('R') >= 0:
return str_value
else:
with open(filePath) as f:
for line in f:
try:
int(str_value)
except:
continue
if line.find(hex(int(str_value))) >= 0:
type = extractValueForLine(line, "type=", '\"', '\"')
name = extractValueForLine(line, "name=", '\"', '\"')
return "R." + type + "." + name
def replaceFileStr(filePath, keyWord, searchPath):
allLines = readAllLine(filePath);
newLines = []
for line in allLines:
newLine = line
if line.find(keyWord) >=0:
str_num = extractValueForLine(line, keyWord, '(', ')')
if str_num is not None and str_num.find('R') < 0:
compValue = mergeValue(str_num, searchPath)
if compValue is not None:
newLine = newLine.replace(str_num, compValue)
newLines.append(newLine)
with open(filePath, 'w') as f:
f.writelines(newLines)
if __name__ == '__main__':
os.system('java ReadAllFiles /home2/gf/PythonStudy/TextProc/java > all_files.txt')
allLines = readAllLine('all_files.txt')
for itemPath in allLines:
itemPath = itemPath.replace('\n', '')
replaceFileStr(itemPath, 'findViewById', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getDimension', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getText', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getString', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getColor', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getDrawable', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getBoolean', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getFloat', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getLayout', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getAnimation', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getXml', '/home2/gf/PythonStudy/TextProc/public.xml')
replaceFileStr(itemPath, 'getInt', '/home2/gf/PythonStudy/TextProc/public.xml')
由于刚学Python不久,对Python文件遍历这块内容不熟悉,于是写了一段Java代码,用于获取某文件夹(包括子文件夹)下所有的文件,配合Python脚本一起使用,代码如下:
import java.util.List;
import java.util.LinkedList;
import java.io.File;
import java.util.ArrayList;
public class ReadAllFiles{
public static void main(String[] args){
String rootPath = args[0];
//System.out.println(rootPath);
List paths = new ArrayList();
File targetFile = new File(rootPath);
if(targetFile.isFile())
paths.add(targetFile.getPath());
else{
LinkedList dirFiles = new LinkedList();
dirFiles.add(targetFile);
while(!dirFiles.isEmpty()){
File dirFile = dirFiles.removeFirst();
File[] childFiles = dirFile.listFiles();
if(childFiles != null && childFiles.length > 0){
for(File childFile : childFiles){
if(childFile.isFile())
System.out.println(childFile.getPath());
else if(!childFile.getName().equals(".git"))
dirFiles.add(childFile);
}
}
}
}
}
}
(10) 修改相关代码之后,成功在我们平台上运行。
(11)重编译的Github源码:https://github.com/MoMoWait/LeanbackLauncher
觉得对您有益,支持一下?