![JAR search and dependency download from the Maven repository](/logo.png)
com.gitee.l0km.casban.AndroidPackageScanner Maven / Gradle / Ivy
package com.gitee.l0km.casban;
import com.gitee.l0km.casban.utils.ZipUtil;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import dalvik.system.DexFile;
import net.gdface.utils.SimpleLog;
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class AndroidPackageScanner extends PackageScanner{
public static final AndroidPackageScanner ANDROID_PACKAGE_SCANNER = new AndroidPackageScanner();
/** 应用包名 */
private String appPackageName;
/** 应用安装包路径 */
private String apkSourceDir;
/** XML信息存储文档路径 */
private String sharedPrefsXmlPath;
/** DEX文件存储路径 */
private String dexDir;
/**
* 扫描指定包名下的所有类
* @param packagename
* @throws IOException
*/
@Override
protected LinkedHashSet> getClasses(String packagename, Predicate excludePackageFilter) throws IOException {
checkNotNull(packagename, "packagename must not be null");
Predicate packageFilter = buildPackageFilter(packagename,excludePackageFilter);
LinkedHashSet> classes = new LinkedHashSet<>();
long time = System.currentTimeMillis();
if(Strings.isNullOrEmpty(appPackageName)) {
try {
appPackageName = getAppPackageName();
} catch (Exception e) {
SimpleLog.log("get app packagename fail",e);
throw new IOException("get app packagename fail");
}
}
// String sourceDir = context.getApplicationInfo().sourceDir;
if(Strings.isNullOrEmpty(apkSourceDir)) {
try {
apkSourceDir = getApplicationSourceDir(classLoader);
} catch (Exception e){
SimpleLog.log("get application source dir fail",e);
throw new IOException("get application source dir fail");
}
}
SimpleLog.log(debugOutout,"查询应用信息用时:{}",System.currentTimeMillis() - time);
time = System.currentTimeMillis();
List classNames = new ArrayList<>();
DexFile dexfile = new DexFile(apkSourceDir);
Enumeration entries = dexfile.entries();
while (entries.hasMoreElements()){
classNames.add(entries.nextElement());
}
classNames.addAll(getSecondaryDexClassNames());
SimpleLog.log(debugOutout,"查找所有Class名称用时:{}",System.currentTimeMillis() - time);
time = System.currentTimeMillis();
FluentIterable.from(classNames)
.filter(packageFilter)
.transform(CLASS_FORNAME_GET_FUN)
.copyInto(classes);
SimpleLog.log(debugOutout,"加载Class用时:{}",System.currentTimeMillis() - time);
return classes;
}
/**
* 获取应用的包名
* */
private String getAppPackageName() throws Exception {
Class> clazz = Class.forName("android.app.ActivityThread");
Method currentPackage = clazz.getMethod("currentPackageName");
String packageName = (String)currentPackage.invoke(null);
if(Strings.isNullOrEmpty(packageName)) {
throw new IOException("packagename is empty");
}
SimpleLog.log(debugOutout,"appPackageName = {}",packageName);
return packageName;
}
/**
* 获取应用apk路径
* 通过反射机制,获取 BaseClassLoader中的 DexPathList,再得到 Element[] dexElements,最后得到应用apk路径
* @param classLoader
* @return 示例:/data/app/com.zykj.androidtest-2/base.apk
* */
private String getApplicationSourceDir(ClassLoader classLoader) throws Exception{
String sourcePath = null;
try {
// 反射获取 BaseDexClassLoader 类对象
Class> systemBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
// 反射获取 BaseDexClassLoader 中的 private final DexPathList pathList 字段
Field systemPathListField = systemBaseDexClassLoaderClass.getDeclaredField("pathList");
// 由于是私有成员字段 , 需要设置可访问性
systemPathListField.setAccessible(true);
// 获取系统的 PathClassLoader pathClassLoader 对象的
// private final DexPathList pathList 成员
Object systemPathListObject = systemPathListField.get(classLoader);
// 获取 DexPathList 类
Class> systemPathListClass = systemPathListObject.getClass();
// 获取 DexPathList 类中的 private final Element[] dexElements 成员字段
Field systemDexElementsField = systemPathListClass.getDeclaredField("dexElements");
// 由于是私有成员字段 , 需要设置可访问性
systemDexElementsField.setAccessible(true);
// 反射获取 DexPathList 对象中的 private Element[] dexElements 成员变量对象
Object[] oldElements = (Object[])systemDexElementsField.get(systemPathListObject);
if(oldElements.length>0){
String info = oldElements[0].toString();
//示例:zip file "/data/app/com.zykj.androidtest-2/base.apk"
int index = info.indexOf("\"");
if(index>0){
sourcePath = info.substring(index+1,info.length()-1);
SimpleLog.log(debugOutout,"application sourceDir = {}",sourcePath);
}
}
} catch (Exception e) {
SimpleLog.log(debugOutout,"Failed to get Application Source Dir via reflection",e);
}
if(Strings.isNullOrEmpty(sourcePath)) {
SimpleLog.log(debugOutout,"Use the command instead");
sourcePath = getApplicationSourceDirByAdb();
if(Strings.isNullOrEmpty(sourcePath)) {
throw new Exception("get application source dir fail");
}
}
return sourcePath;
}
/**
* 获取应用apk路径
* @return 示例:/data/app/com.zykj.androidtest-2/base.apk
* */
private String getApplicationSourceDirByAdb() throws IOException{
//可以列出所有包信息
List paths = execAdbCmd("pm list packages -f");
//结果示例:
//.....
//package:/data/app/com.zykj.androidtest.test-1/base.apk=com.zykj.androidtest.test
//package:/data/app/com.zykj.androidtest-2/base.apk=com.zykj.androidtest
String pageName2 = "="+appPackageName;
for(String path:paths){
if(path.endsWith(pageName2)){
String sourceDir = path.replace("package:","").replace(pageName2,"");
SimpleLog.log(debugOutout,"application sourceDir = {}",sourceDir);
return sourceDir;
}
}
return null;
}
/** 执行adb命令 */
private List execAdbCmd(String cmd) throws IOException{
List data = new ArrayList<>();
java.lang.Process pp = Runtime.getRuntime().exec(cmd);
InputStreamReader ir = new InputStreamReader(pp.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
String str = "";
while(null != str) {
str = input.readLine();
if (str != null) {
data.add(str.trim());
}
}
return data;
}
private List getSecondaryDexClassNames() throws IOException {
List secondryDexFiles = multiDexExtractorLoad(false);
List classNames = new ArrayList<>();
for (File file : secondryDexFiles) {
String path = file.getAbsolutePath();
try {
DexFile dexfile;
if (path.endsWith(".zip")) {
// NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}
Enumeration entries = dexfile.entries();
while (entries.hasMoreElements()){
classNames.add(entries.nextElement());
}
} catch (IOException e) {
SimpleLog.log("Error at loading dex file '" + path + "'",e);
throw new IOException("Error at loading dex file '" + path + "'");
}
}
return classNames;
}
/**
* 加载 dex 文件
* 把 APK(/data/app/{包名}/base.apk)中的 dex文件抽取到 dexDir(/data/user/0/{包名}/code_cache/secondary-dexes)目录中,
* 然后把 dex 文件路径返回
* @param forceReload 是否强制重新加载(如果不强制重新加载,则代表如果已经抽取过了,可以直接从缓存目录中拿来使用,这么做速度比较快)
* @return 返回的files集合有可能为空,表示没有secondaryDex
* */
private List multiDexExtractorLoad(boolean forceReload) throws IOException {
SimpleLog.log(debugOutout,"MultiDexExtractor.load({},{})",apkSourceDir,forceReload);
//示例输出:MultiDexExtractor.load(/data/app/com.zykj.androidtest-1/base.apk, false)
sharedPrefsXmlPath = "/data/user/0/" + appPackageName + "/shared_prefs/multidex.version.xml";
HashMap> params = readXml(sharedPrefsXmlPath);
int totalDexNumber = getIntFromParams(params,"dex.number", 1);
dexDir = "/data/user/0/" + appPackageName + "/code_cache/secondary-dexes";
File dexDirFile = new File(dexDir);
File sourceApk = new File(apkSourceDir);
long currentCrc = getZipCrc(sourceApk);
List files;
if (!forceReload && !isModified(params,sourceApk, currentCrc)) {
try {
files = loadExistingExtractions(sourceApk, dexDirFile,totalDexNumber);
} catch (IOException var9) {
SimpleLog.log(debugOutout,"Failed to reload existing extracted secondary dex files, falling back to fresh extraction: {}",var9.getMessage());
files = performExtractions(sourceApk, dexDirFile);
putStoredApkInfo(getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
} else {
SimpleLog.log(debugOutout,"Detected that extraction must be performed.");
files = performExtractions(sourceApk, dexDirFile);
putStoredApkInfo(getTimeStamp(sourceApk), currentCrc, files.size() + 1);
}
SimpleLog.log(debugOutout,"load found " + files.size() + " secondary dex files");
return files;
}
private List loadExistingExtractions(File sourceApk, File dexDir,int totalDexNumber) throws IOException {
SimpleLog.log(debugOutout,"loading existing secondary dex files");
String extractedFilePrefix = sourceApk.getName() + ".classes";
List files = new ArrayList<>(totalDexNumber);
for(int secondaryNumber = 2; secondaryNumber <= totalDexNumber; ++secondaryNumber) {
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
File extractedFile = new File(dexDir, fileName);
if (!extractedFile.isFile()) {
throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
}
files.add(extractedFile);
if (!verifyZipFile(extractedFile)) {
SimpleLog.log(debugOutout,"Invalid zip file: " + extractedFile);
throw new IOException("Invalid ZIP file.");
}
}
return files;
}
private boolean isModified(HashMap> params,File archive, long currentCrc) {
long timestamp = getLongFromParams(params,"timestamp", -1L);
long crc = getLongFromParams(params,"crc", -1L);
return timestamp != getTimeStamp(archive) || crc != currentCrc;
}
private long getTimeStamp(File archive) {
long timeStamp = archive.lastModified();
if (timeStamp == -1L) {
--timeStamp;
}
return timeStamp;
}
private long getZipCrc(File archive) throws IOException {
long computedValue = ZipUtil.getZipCrc(archive);
if (computedValue == -1L) {
--computedValue;
}
return computedValue;
}
private List performExtractions(File sourceApk, File dexDir) throws IOException {
String extractedFilePrefix = sourceApk.getName() + ".classes";
prepareDexDir(dexDir, extractedFilePrefix);
List files = new ArrayList<>();
ZipFile apk = new ZipFile(sourceApk);
try {
int secondaryNumber = 2;
for(ZipEntry dexFile = apk.getEntry("classes" + secondaryNumber + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + secondaryNumber + ".dex")) {
String fileName = extractedFilePrefix + secondaryNumber + ".zip";
File extractedFile = new File(dexDir, fileName);
files.add(extractedFile);
SimpleLog.log(debugOutout,"Extraction is needed for file {}",extractedFile);
// 示例输出:Extraction is needed for file /data/user/0/com.zykj.androidtest/code_cache/secondary-dexes/base.apk.classes2.zip
int numAttempts = 0;
boolean isExtractionSuccessful = false;
while(numAttempts < 3 && !isExtractionSuccessful) {
++numAttempts;
extract(apk, dexFile, extractedFile, extractedFilePrefix);
isExtractionSuccessful = verifyZipFile(extractedFile);
SimpleLog.log(debugOutout,"Extraction {} - length {}: ",isExtractionSuccessful ? "success" : "failed", extractedFile.getAbsolutePath(),extractedFile.length());
//示例输出:Extraction success - length /data/user/0/com.zykj.androidtest/code_cache/secondary-dexes/base.apk.classes2.zip: 2311419
if (!isExtractionSuccessful) {
extractedFile.delete();
if (extractedFile.exists()) {
SimpleLog.log(debugOutout,"Failed to delete corrupted secondary dex '{}'",extractedFile.getPath());
}
}
}
if (!isExtractionSuccessful) {
throw new IOException("Could not create zip file " + extractedFile.getAbsolutePath() + " for secondary dex (" + secondaryNumber + ")");
}
++secondaryNumber;
}
} finally {
try {
apk.close();
} catch (IOException var16) {
SimpleLog.log(debugOutout,"Failed to close resource. error:{}",var16.getMessage());
}
}
return files;
}
private void putStoredApkInfo(long timeStamp, long crc, int totalDexNumber) throws IOException{
// SharedPreferences prefs = getMultiDexPreferences(context);
// SharedPreferences.Editor edit = prefs.edit();
// edit.putLong("timestamp", timeStamp);
// edit.putLong("crc", crc);
// edit.putInt("dex.number", totalDexNumber);
HashMap> data = new HashMap<>();
HashMap item = new HashMap<>();
item.put("type","long");
item.put("value",""+timeStamp);
data.put("timestamp",item);
item = new HashMap<>();
item.put("type","long");
item.put("value",""+crc);
data.put("crc",item);
item = new HashMap<>();
item.put("type","int");
item.put("value",""+totalDexNumber);
data.put("dex.number",item);
editXml(sharedPrefsXmlPath,data);
}
private void prepareDexDir(File dexDir, final String extractedFilePrefix) throws IOException {
File cache = dexDir.getParentFile();
mkdirChecked(cache);
mkdirChecked(dexDir);
FileFilter filter = new FileFilter() {
public boolean accept(File pathname) {
return !pathname.getName().startsWith(extractedFilePrefix);
}
};
File[] files = dexDir.listFiles(filter);
if (files == null) {
SimpleLog.log(debugOutout,"Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
} else {
File[] arr$ = files;
int len$ = files.length;
for(int i$ = 0; i$ < len$; ++i$) {
File oldFile = arr$[i$];
SimpleLog.log(debugOutout,"Trying to delete old file " + oldFile.getPath() + " of size " + oldFile.length());
if (!oldFile.delete()) {
SimpleLog.log(debugOutout,"Failed to delete old file " + oldFile.getPath());
} else {
SimpleLog.log(debugOutout,"Deleted old file " + oldFile.getPath());
}
}
}
}
private void mkdirChecked(File dir) throws IOException {
dir.mkdir();
if (!dir.isDirectory()) {
File parent = dir.getParentFile();
if (parent == null) {
SimpleLog.log(debugOutout,"Failed to create dir " + dir.getPath() + ". Parent file is null.");
} else {
SimpleLog.log(debugOutout,"Failed to create dir " + dir.getPath() + ". parent file is a dir " + parent.isDirectory() + ", a file " + parent.isFile() + ", exists " + parent.exists() + ", readable " + parent.canRead() + ", writable " + parent.canWrite());
}
throw new IOException("Failed to create cache directory " + dir.getPath());
}
}
private void extract(ZipFile apk, ZipEntry dexFile, File extractTo, String extractedFilePrefix) throws IOException, FileNotFoundException {
InputStream in = apk.getInputStream(dexFile);
ZipOutputStream out = null;
File tmp = File.createTempFile(extractedFilePrefix, ".zip", extractTo.getParentFile());
SimpleLog.log(debugOutout,"Extracting {}",tmp.getPath());
//示例输出:Extracting /data/user/0/com.zykj.androidtest/code_cache/secondary-dexes/base.apk.classes1997043754.zip
try {
out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tmp)));
try {
ZipEntry classesDex = new ZipEntry("classes.dex");
classesDex.setTime(dexFile.getTime());
out.putNextEntry(classesDex);
byte[] buffer = new byte[16384];
for(int length = in.read(buffer); length != -1; length = in.read(buffer)) {
out.write(buffer, 0, length);
}
out.closeEntry();
} finally {
out.close();
}
SimpleLog.log(debugOutout,"Renaming to {}",extractTo.getPath());
//示例输出:Renaming to /data/user/0/com.zykj.androidtest/code_cache/secondary-dexes/base.apk.classes2.zip
if (!tmp.renameTo(extractTo)) {
throw new IOException("Failed to rename \"" + tmp.getAbsolutePath() + "\" to \"" + extractTo.getAbsolutePath() + "\"");
}
} finally {
closeQuietly(in);
tmp.delete();
}
}
private boolean verifyZipFile(File file) {
try {
ZipFile zipFile = new ZipFile(file);
try {
zipFile.close();
return true;
} catch (IOException var3) {
SimpleLog.log(debugOutout,"Failed to close zip file: {}",file.getAbsolutePath());
}
} catch (ZipException var4) {
SimpleLog.log(debugOutout,"File " + file.getAbsolutePath() + " is not a valid zip file. error:"+var4.getMessage());
} catch (IOException var5) {
SimpleLog.log(debugOutout,"Got an IOException trying to open zip file: " + file.getAbsolutePath()+" error:"+ var5.getMessage());
}
return false;
}
private void closeQuietly(Closeable closeable) {
try {
closeable.close();
} catch (IOException var2) {
SimpleLog.log(debugOutout,"Failed to close resource. error:{}",var2.getMessage());
}
}
/**
* 存储信息到xml文档,如果文件不存在,则创建
* @param filePath 文件地址
* @param data 需要添加的内容
* */
private void editXml(String filePath, HashMap> data) throws IOException{
long time = System.currentTimeMillis();
try {
Document document = null;
File docFile = new File(filePath);
if(docFile.exists()) {
//解析文档
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
DocumentBuilder db=dbf.newDocumentBuilder();
document=db.parse(docFile);
} else {
//创建文档
if(!docFile.getParentFile().exists()){
docFile.getParentFile().mkdirs();
}
docFile.createNewFile();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.newDocument();
document.setXmlStandalone(true);
}
Element mapNode = null;
boolean mapNodeIsNew = false;
NodeList mapNodeList=document.getElementsByTagName("map");
if(mapNodeList!=null && mapNodeList.getLength()>0){
mapNode = (Element)mapNodeList.item(0);
mapNodeIsNew = true;
}
if(mapNode==null){
mapNode = document.createElement("map");
}
for (Map.Entry> entry : data.entrySet()) {
String name = entry.getKey();
String type = entry.getValue().get("type");
String value = entry.getValue().get("value");
boolean isNodeExists= false;
NodeList typeNodeList=document.getElementsByTagName(type);
for(int i=0;i0){
Node nameN = nMap.getNamedItem("name");
Node valueN = nMap.getNamedItem("value");
if(Objects.equals(nameN.getNodeValue(),name)){
isNodeExists = true;
if(!Objects.equals(valueN.getNodeValue(),value)){
//修改值
valueN.setNodeValue(value);
}
break;
}
}
}
if(!isNodeExists) {
//新增值
Element item = document.createElement(type);
item.setAttribute("name",name);
item.setAttribute("value",value);
mapNode.appendChild(item);
}
}
if(!mapNodeIsNew){
document.appendChild(mapNode);
}
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
//这个是显示格式化用的
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(docFile);
transformer.transform(source,result);
// tf.transform(new DOMSource(document), new StreamResult(System.out)); //输出控制台
} catch (Exception e){
SimpleLog.log(String.format("Save File [%s] ERROR",filePath),e);
throw new IOException(String.format("Save File [%s] ERROR",filePath));
}
SimpleLog.log(debugOutout,"存储XML文件用时:{}",System.currentTimeMillis() - time);
}
/**
* 读取XML文档里的 map节点的所有内容
* @param filePath 文件地址
* @return
* */
private HashMap> readXml(String filePath) throws IOException{
long time = System.currentTimeMillis();
HashMap> data = new HashMap<>();
File file=new File(filePath);
if(!file.exists()){
return data;
}
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
DocumentBuilder db;
try {
db=dbf.newDocumentBuilder();
Document document=db.parse(file);
NodeList mapNodeList=document.getElementsByTagName("map");
if(mapNodeList!=null && mapNodeList.getLength()>0){
NodeList mapChildList=mapNodeList.item(0).getChildNodes();
for(int i=0;i0){
Node nameN = nMap.getNamedItem("name");
String name = nameN.getNodeValue();
Node valueN = nMap.getNamedItem("value");
String value = valueN.getNodeValue();
if(!Strings.isNullOrEmpty(name) && !Strings.isNullOrEmpty(value)) {
HashMap map = new HashMap<>();
map.put("type",type);
map.put("value",value);
data.put(name,map);
}
}
}
}
} catch (Exception e) {
SimpleLog.log(String.format("Read File [%s] ERROR",filePath),e);
throw new IOException(String.format("Read File [%s] ERROR",filePath));
}
SimpleLog.log(debugOutout,"读取XML文件用时:{}",System.currentTimeMillis() - time);
return data;
}
private int getIntFromParams(HashMap> params,String key,int defaultValue){
if(params!=null){
HashMap map = params.get(key);
if(map!=null){
String valueStr = map.get("value");
try {
Integer valueInt = Integer.parseInt(valueStr);
return valueInt.intValue();
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
return defaultValue;
}
private long getLongFromParams(HashMap> params,String key,long defaultValue){
if(params!=null){
HashMap map = params.get(key);
if(map!=null){
String valueStr = map.get("value");
try {
Long valueL = Long.parseLong(valueStr);
return valueL.longValue();
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
return defaultValue;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy