Android TV下LeanbackLauncher的反编译,AS重新打包修改

本文介绍Android 7.1平台下Google LeanbakLauncher的反编译流程,并使用AS工具重新打包修改。

最近需要开发一款TV桌面应用,调研了各类桌面,如当贝桌面,小米桌面,开源的猫桌面。其中当贝桌面,小米桌面不符合Google推荐的Tv开发方式,猫桌面简洁,但是功能较少,如缺少APP的排序,卸载管理等功能。猫桌面的Github地址:https://github.com/JackyAndroid/AndroidTVLauncher。

相比之下,Google LeanbackLauncher页面较为绚丽,动画完美,符合Tv开发规范,是一款支持应用移动,卸载,视频内容推送的Leanback风格的桌面软件。只是这款软件不开源,于是自然想到反编译。

Google LeanbackLauncher的桌面UI如下:

Android TV下LeanbackLauncher的反编译,AS重新打包修改_第1张图片



Android TV下LeanbackLauncher的反编译,AS重新打包修改_第2张图片


反编译,用AS重新打包的UI如下(更换了壁纸,添加了以太网连接标记,视频推荐内容暂时无法还原):

Android TV下LeanbackLauncher的反编译,AS重新打包修改_第3张图片


下面正式介绍反编译与回编译的流程:

(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);
					}
				}
			}
		}        
    }
}

(9) 上述Python脚本可以替换掉大部分findViewById,getColor,getDrawabel的参数,但仍有少部分需要自己手动调整。重新Build之后,在Nexus机器上应用可以起来,但是在我们平台上会奔溃,原因是我们的平台把壁纸服务去掉了,无法获取WallPapaerManger。

(10) 修改相关代码之后,成功在我们平台上运行。


(11)重编译的Github源码:https://github.com/MoMoWait/LeanbackLauncher


觉得对您有益,支持一下?

Android TV下LeanbackLauncher的反编译,AS重新打包修改_第4张图片    Android TV下LeanbackLauncher的反编译,AS重新打包修改_第5张图片

你可能感兴趣的:(Android TV下LeanbackLauncher的反编译,AS重新打包修改)