This commit is contained in:
REAndroid 2022-12-16 08:40:48 -05:00
parent 9d8421b0c7
commit 8bac03ca9b
30 changed files with 739 additions and 41 deletions

View File

@ -2,7 +2,7 @@
apply plugin: 'java-library'
group 'com.reandroid.lib.arsc'
version '1.0.6'
version '1.0.7'
java {
sourceCompatibility JavaVersion.VERSION_1_8

View File

@ -0,0 +1,129 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.APKArchive;
import com.reandroid.lib.arsc.chunk.TableBlock;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
public class ApkBundle {
private final Map<String, ApkModule> mModulesMap;
private APKLogger apkLogger;
public ApkBundle(){
this.mModulesMap=new HashMap<>();
}
public ApkModule mergeModules() throws IOException {
List<ApkModule> moduleList=getApkModuleList();
if(moduleList.size()==0){
throw new FileNotFoundException("Nothing to merge, empty modules");
}
ApkModule result=new ApkModule("merged", new APKArchive());
result.setAPKLogger(apkLogger);
ApkModule base=getBaseModule();
if(base==null){
base=getLargestTableModule();
}
result.merge(base);
for(ApkModule module:moduleList){
if(module==base){
continue;
}
result.merge(module);
}
if(result.hasTableBlock()){
TableBlock tableBlock=result.getTableBlock();
tableBlock.sortPackages();
tableBlock.refresh();
}
result.sortApkFiles();
return result;
}
private ApkModule getLargestTableModule() throws IOException {
ApkModule apkModule=null;
int chunkSize=0;
for(ApkModule module:getApkModuleList()){
if(!module.hasTableBlock()){
continue;
}
TableBlock tableBlock=module.getTableBlock();
int size=tableBlock.getHeaderBlock().getChunkSize();
if(apkModule==null || size>chunkSize){
chunkSize=size;
apkModule=module;
}
}
return apkModule;
}
public ApkModule getBaseModule(){
for(ApkModule module:getApkModuleList()){
if(module.isBaseModule()){
return module;
}
}
return null;
}
public List<ApkModule> getApkModuleList(){
return new ArrayList<>(mModulesMap.values());
}
public void loadApkDirectory(File dir) throws IOException {
if(!dir.isDirectory()){
throw new FileNotFoundException("No such directory: "+dir);
}
List<File> apkList=ApkUtil.listFiles(dir, ".apk");
if(apkList.size()==0){
throw new FileNotFoundException("No '*.apk' files in directory: "+dir);
}
logMessage("Found apk files: "+apkList.size());
for(File file:apkList){
logVerbose("Loading: "+file.getName());
String name=ApkUtil.toModuleName(file);
ApkModule module=ApkModule.loadApkFile(file, name);
module.setAPKLogger(apkLogger);
addModule(module);
}
}
public void addModule(ApkModule apkModule){
String name=apkModule.getModuleName();
mModulesMap.remove(name);
mModulesMap.put(name, apkModule);
}
public boolean containsApkModule(String moduleName){
return mModulesMap.containsKey(moduleName);
}
public ApkModule removeApkModule(String moduleName){
return mModulesMap.remove(moduleName);
}
public ApkModule getApkModule(String moduleName){
return mModulesMap.get(moduleName);
}
public List<String> listModuleNames(){
return new ArrayList<>(mModulesMap.keySet());
}
public int countModules(){
return mModulesMap.size();
}
public Collection<ApkModule> getModules(){
return mModulesMap.values();
}
public void setAPKLogger(APKLogger logger) {
this.apkLogger = logger;
}
private void logMessage(String msg) {
if(apkLogger!=null){
apkLogger.logMessage(msg);
}
}
private void logError(String msg, Throwable tr) {
if(apkLogger!=null){
apkLogger.logError(msg, tr);
}
}
private void logVerbose(String msg) {
if(apkLogger!=null){
apkLogger.logVerbose(msg);
}
}
}

View File

@ -2,6 +2,7 @@ package com.reandroid.lib.apk;
import com.reandroid.archive.*;
import com.reandroid.lib.arsc.array.PackageArray;
import com.reandroid.lib.arsc.chunk.BaseChunk;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
@ -30,6 +31,16 @@ public class ApkModule {
this.mUncompressedFiles=new UncompressedFiles();
this.mUncompressedFiles.addPath(apkArchive);
}
public List<DexFileInputSource> listDexFiles(){
List<DexFileInputSource> results=new ArrayList<>();
for(InputSource source:getApkArchive().listInputSources()){
if(DexFileInputSource.isDexName(source.getAlias())){
results.add(new DexFileInputSource(source.getAlias(), source));
}
}
DexFileInputSource.sort(results);
return results;
}
public boolean isBaseModule(){
if(!hasAndroidManifestBlock()){
return false;
@ -223,6 +234,9 @@ public class ApkModule {
tableBlock=((SplitJsonTableInputSource)inputSource).getTableBlock();
}else if(inputSource instanceof SingleJsonTableInputSource){
tableBlock=((SingleJsonTableInputSource)inputSource).getTableBlock();
}else if(inputSource instanceof BlockInputSource){
BaseChunk block = ((BlockInputSource<?>) inputSource).getBlock();
tableBlock=(TableBlock) block;
}else {
InputStream inputStream = inputSource.openStream();
if(loadDefaultFramework){
@ -246,6 +260,78 @@ public class ApkModule {
this.loadDefaultFramework = loadDefaultFramework;
}
public void merge(ApkModule module) throws IOException {
if(module==null||module==this){
return;
}
logMessage("Merging: "+module.getModuleName());
mergeDexFiles(module);
mergeTable(module);
mergeFiles(module);
getUncompressedFiles().merge(module.getUncompressedFiles());
}
private void mergeTable(ApkModule module) throws IOException {
if(!module.hasTableBlock()){
return;
}
logMessage("Merging resource table: "+module.getModuleName());
TableBlock exist;
if(!hasTableBlock()){
exist=new TableBlock();
BlockInputSource<TableBlock> inputSource=new BlockInputSource<>(TableBlock.FILE_NAME, exist);
getApkArchive().add(inputSource);
}else{
exist=getTableBlock();
}
TableBlock coming=module.getTableBlock();
exist.merge(coming);
}
private void mergeFiles(ApkModule module) throws IOException {
APKArchive archiveExist = getApkArchive();
APKArchive archiveComing = module.getApkArchive();
Map<String, InputSource> comingAlias=ApkUtil.toAliasMap(archiveComing.listInputSources());
Map<String, InputSource> existAlias=ApkUtil.toAliasMap(archiveExist.listInputSources());
UncompressedFiles uf=getUncompressedFiles();
for(InputSource inputSource:comingAlias.values()){
if(existAlias.containsKey(inputSource.getAlias())||existAlias.containsKey(inputSource.getName())){
continue;
}
if(DexFileInputSource.isDexName(inputSource.getName())){
continue;
}
logVerbose("Added: "+inputSource.getAlias());
archiveExist.add(inputSource);
uf.addPath(inputSource);
}
}
private void mergeDexFiles(ApkModule module){
List<DexFileInputSource> existList=listDexFiles();
List<DexFileInputSource> comingList=module.listDexFiles();
APKArchive archive=getApkArchive();
int index=0;
if(existList.size()>0){
index=existList.get(existList.size()-1).getDexNumber();
if(index==0){
index=2;
}else {
index++;
}
}
for(DexFileInputSource source:comingList){
String name=DexFileInputSource.getDexName(index);
DexFileInputSource add=new DexFileInputSource(name, source.getInputSource());
archive.add(add);
logMessage("Added ["+module.getModuleName()+"] "
+source.getAlias()+" -> "+name);
index++;
if(index==1){
index=2;
}
}
}
void sortApkFiles(){
sortApkFiles(new ArrayList<>(getApkArchive().listInputSources()));
}
public void setAPKLogger(APKLogger logger) {
this.apkLogger = logger;
}
@ -264,6 +350,10 @@ public class ApkModule {
apkLogger.logVerbose(msg);
}
}
@Override
public String toString(){
return getModuleName();
}
public static ApkModule loadApkFile(File apkFile) throws IOException {
return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
}
@ -271,4 +361,39 @@ public class ApkModule {
APKArchive archive=APKArchive.loadZippedApk(apkFile);
return new ApkModule(moduleName, archive);
}
private static void sortApkFiles(List<InputSource> sourceList){
Comparator<InputSource> cmp=new Comparator<InputSource>() {
@Override
public int compare(InputSource in1, InputSource in2) {
return getSortName(in1).compareTo(getSortName(in2));
}
};
sourceList.sort(cmp);
int i=0;
for(InputSource inputSource:sourceList){
inputSource.setSort(i);
i++;
}
}
private static String getSortName(InputSource inputSource){
String name=inputSource.getAlias();
StringBuilder builder=new StringBuilder();
if(name.equals(AndroidManifestBlock.FILE_NAME)){
builder.append("0 ");
}else if(name.equals(TableBlock.FILE_NAME)){
builder.append("1 ");
}else if(name.startsWith("classes")){
builder.append("2 ");
}else if(name.startsWith("res/")){
builder.append("3 ");
}else if(name.startsWith("lib/")){
builder.append("4 ");
}else if(name.startsWith("assets/")){
builder.append("5 ");
}else {
builder.append("6 ");
}
builder.append(name.toLowerCase());
return builder.toString();
}
}

View File

@ -1,8 +1,9 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
public class ApkUtil {
public static String replaceRootDir(String path, String dirName){
@ -106,6 +107,21 @@ public class ApkUtil {
}
return results;
}
public static String toModuleName(File file){
String name=file.getName();
int i=name.lastIndexOf('.');
if(i>0){
name=name.substring(0,i);
}
return name;
}
public static Map<String, InputSource> toAliasMap(Collection<InputSource> sourceList){
Map<String, InputSource> results=new HashMap<>();
for(InputSource inputSource:sourceList){
results.put(inputSource.getAlias(), inputSource);
}
return results;
}
public static final String JSON_FILE_EXTENSION=".json";
public static final String RES_JSON_NAME="res-json";
public static final String ROOT_NAME="root";

View File

@ -0,0 +1,51 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DexFileInputSource extends RenamedInputSource<InputSource> implements Comparable<DexFileInputSource>{
public DexFileInputSource(String name, InputSource inputSource){
super(name, inputSource);
}
public int getDexNumber(){
return getDexNumber(getAlias());
}
@Override
public int compareTo(DexFileInputSource source) {
return Integer.compare(getDexNumber(), source.getDexNumber());
}
public static void sort(List<DexFileInputSource> sourceList){
sourceList.sort(new Comparator<DexFileInputSource>() {
@Override
public int compare(DexFileInputSource s1, DexFileInputSource s2) {
return s1.compareTo(s2);
}
});
}
public static boolean isDexName(String name){
return getDexNumber(name)>=0;
}
static String getDexName(int i){
if(i==0){
return "classes.dex";
}
return "classes"+i+".dex";
}
static int getDexNumber(String name){
Matcher matcher=PATTERN.matcher(name);
if(!matcher.find()){
return -1;
}
String num=matcher.group(1);
if(num.length()==0){
return 0;
}
return Integer.parseInt(num);
}
private static final Pattern PATTERN=Pattern.compile("^classes([0-9]*)\\.dex$");
}

View File

@ -0,0 +1,40 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class RenamedInputSource<T extends InputSource> extends InputSource {
private final T inputSource;
public RenamedInputSource(String name, T input){
super(name);
this.inputSource=input;
super.setMethod(input.getMethod());
super.setSort(input.getSort());
}
public T getInputSource() {
return inputSource;
}
@Override
public void close(InputStream inputStream) throws IOException {
getInputSource().close(inputStream);
}
@Override
public long getLength() throws IOException {
return getInputSource().getLength();
}
@Override
public long getCrc() throws IOException {
return getInputSource().getCrc();
}
@Override
public long write(OutputStream outputStream) throws IOException {
return getInputSource().write(outputStream);
}
@Override
public InputStream openStream() throws IOException {
return getInputSource().openStream();
}
}

View File

@ -30,6 +30,7 @@ public class TableBlockJsonBuilder {
for(File pkgDir:pkgDirList){
scanPackageDirectory(tableBlock, pkgDir);
}
tableBlock.sortPackages();
tableBlock.refresh();
return tableBlock;
}

View File

@ -159,6 +159,17 @@ public class UncompressedFiles implements JSONConvert<JSONObject> {
}
}
}
public void merge(UncompressedFiles uf){
if(uf==null||uf==this){
return;
}
for(String path: uf.mPathList){
addPath(path);
}
for(String ext:uf.mExtensionList){
addExtension(ext);
}
}
public void fromJson(File jsonFile) throws IOException {
if(!jsonFile.isFile()){
return;

View File

@ -1,12 +1,18 @@
package com.reandroid.lib.arsc.array;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.item.IntegerArray;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.value.BaseResValue;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.lib.arsc.value.ResValueInt;
import com.reandroid.lib.arsc.value.ValueType;
import com.reandroid.lib.json.JSONConvert;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
import java.util.Iterator;
public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSONConvert<JSONArray> {
public EntryBlockArray(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart){
@ -56,7 +62,6 @@ public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSO
}
return jsonArray;
}
@Override
public void fromJson(JSONArray json) {
clearChildes();
@ -74,6 +79,34 @@ public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSO
}
refreshCountAndStart();
}
public void merge(EntryBlockArray entryBlockArray){
if(entryBlockArray==null||entryBlockArray==this||entryBlockArray.isEmpty()){
return;
}
ensureSize(entryBlockArray.childesCount());
Iterator<EntryBlock> itr=entryBlockArray.iterator(true);
while (itr.hasNext()){
EntryBlock comingBlock=itr.next();
EntryBlock existingBlock=get(comingBlock.getIndex());
if(shouldMerge(existingBlock, comingBlock)){
existingBlock.merge(comingBlock);
}
}
}
private boolean shouldMerge(EntryBlock exist, EntryBlock coming){
if(exist.isNull()){
return true;
}
if(coming.isNull()){
return false;
}
BaseResValue resVal = coming.getResValue();
if(resVal instanceof ResValueInt){
ValueType valueType=((ResValueInt)resVal).getValueType();
return valueType!=ValueType.INT_BOOLEAN;
}
return true;
}
@Override
public String toString(){
return getClass().getSimpleName()+": size="+childesCount();

View File

@ -15,6 +15,25 @@ public class LibraryInfoArray extends BlockArray<LibraryInfo> implements JSONCon
public LibraryInfoArray(IntegerItem infoCount){
this.mInfoCount=infoCount;
}
public LibraryInfo getOrCreate(int pkgId){
LibraryInfo info=getById(pkgId);
if(info!=null){
return info;
}
int index=childesCount();
ensureSize(index+1);
info=get(index);
info.setPackageId(pkgId);
return info;
}
public LibraryInfo getById(int pkgId){
for(LibraryInfo info:listItems()){
if(pkgId==info.getPackageId()){
return info;
}
}
return null;
}
@Override
public LibraryInfo newInstance() {
return new LibraryInfo();
@ -61,4 +80,13 @@ public class LibraryInfoArray extends BlockArray<LibraryInfo> implements JSONCon
libraryInfo.fromJson(jsonObject);
}
}
public void merge(LibraryInfoArray infoArray){
if(infoArray==null||infoArray==this||infoArray.childesCount()==0){
return;
}
for(LibraryInfo info:infoArray.listItems()){
LibraryInfo exist=getOrCreate(info.getPackageId());
exist.merge(info);
}
}
}

View File

@ -11,14 +11,22 @@ import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
import java.io.IOException;
import java.util.Comparator;
import java.util.Iterator;
public class PackageArray extends BlockArray<PackageBlock> implements BlockLoad, JSONConvert<JSONArray> {
public class PackageArray extends BlockArray<PackageBlock>
implements BlockLoad, JSONConvert<JSONArray>, Comparator<PackageBlock> {
private final IntegerItem mPackageCount;
public PackageArray(IntegerItem packageCount){
this.mPackageCount=packageCount;
mPackageCount.setBlockLoad(this);
}
public void sort(){
for(PackageBlock packageBlock:listItems()){
packageBlock.sortTypes();
}
sort(this);
}
public PackageBlock getOrCreate(byte pkgId){
PackageBlock packageBlock=getPackageBlockById(pkgId);
if(packageBlock!=null){
@ -88,4 +96,17 @@ public class PackageArray extends BlockArray<PackageBlock> implements BlockLoad,
packageBlock.fromJson(jsonObject);
}
}
public void merge(PackageArray packageArray){
if(packageArray==null||packageArray==this){
return;
}
for(PackageBlock packageBlock:packageArray.listItems()){
PackageBlock exist=getOrCreate((byte) packageBlock.getId());
exist.merge(packageBlock);
}
}
@Override
public int compare(PackageBlock p1, PackageBlock p2) {
return p1.compareTo(p2);
}
}

View File

@ -1,6 +1,7 @@
package com.reandroid.lib.arsc.array;
import com.reandroid.lib.arsc.base.BlockArray;
import com.reandroid.lib.arsc.value.ResValueBag;
import com.reandroid.lib.arsc.value.ResValueBagItem;
import com.reandroid.lib.json.JSONConvert;
import com.reandroid.lib.json.JSONArray;
@ -22,6 +23,13 @@ public class ResValueBagItemArray extends BlockArray<ResValueBagItem> implements
@Override
protected void onRefreshed() {
}
@Override
public void clearChildes(){
for(ResValueBagItem bagItem:listItems()){
bagItem.onRemoved();
}
super.clearChildes();
}
@Override
public JSONArray toJson() {
@ -47,4 +55,17 @@ public class ResValueBagItemArray extends BlockArray<ResValueBagItem> implements
get(i).fromJson(json.getJSONObject(i));
}
}
public void merge(ResValueBagItemArray bagItemArray){
if(bagItemArray==null||bagItemArray==this){
return;
}
clearChildes();
int count=bagItemArray.childesCount();
ensureSize(count);
for(int i=0;i<count;i++){
ResValueBagItem coming=bagItemArray.get(i);
ResValueBagItem exist=get(i);
exist.merge(coming);
}
}
}

View File

@ -1,6 +1,7 @@
package com.reandroid.lib.arsc.array;
import com.reandroid.lib.arsc.base.BlockArray;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.value.EntryBlock;
@ -226,6 +227,18 @@ public class SpecTypePairArray extends BlockArray<SpecTypePair>
specTypePair.fromJson(jsonObject);
}
}
public void merge(SpecTypePairArray pairArray){
if(pairArray==null || pairArray==this){
return;
}
for(SpecTypePair typePair:pairArray.listItems()){
if(typePair.isEmpty()){
continue;
}
SpecTypePair exist=getOrCreate(typePair.getTypeId());
exist.merge(typePair);
}
}
@Override
public int compare(SpecTypePair typePair1, SpecTypePair typePair2) {
return typePair1.compareTo(typePair2);

View File

@ -58,16 +58,27 @@ public class StyleArray extends OffsetBlockArray<StyleItem> implements JSONConve
public JSONArray toJson() {
if(childesCount()==0){
return null;
}
int i=0;
for(StyleItem styleItem:listItems()){
}
return null;
}
@Override
public void fromJson(JSONArray json) {
}
public void merge(StyleArray styleArray){
if(styleArray==null||styleArray==this){
return;
}
if(childesCount()!=0){
return;
}
int count=styleArray.childesCount();
ensureSize(count);
for(int i=0;i<count;i++){
StyleItem exist=get(i);
StyleItem coming=styleArray.get(i);
exist.merge(coming);
}
}
private static final byte END_BYTE= (byte) 0xFF;
}

View File

@ -5,6 +5,7 @@ import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.base.BlockArray;
import com.reandroid.lib.arsc.chunk.SpecBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.header.HeaderBlock;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.TypeString;
@ -277,6 +278,15 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
typeBlock.fromJson(jsonObject);
}
}
public void merge(TypeBlockArray typeBlockArray){
if(typeBlockArray==null||typeBlockArray==this){
return;
}
for(TypeBlock typeBlock:typeBlockArray.listItems()){
TypeBlock block=getOrCreate(typeBlock.getResConfig());
block.merge(typeBlock);
}
}
@Override
public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) {
return typeBlock1.compareTo(typeBlock2);

View File

@ -50,10 +50,15 @@ public class LibraryBlock extends BaseChunk {
mLibCount.set(count);
mLibraryInfoArray.setChildesCount(count);
}
@Override
protected void onChunkRefreshed() {
mLibCount.set(mLibraryInfoArray.childesCount());
}
public void merge(LibraryBlock libraryBlock){
if(libraryBlock==null||libraryBlock==this){
return;
}
getLibraryInfoArray().merge(libraryBlock.getLibraryInfoArray());
}
}

View File

@ -1,6 +1,7 @@
package com.reandroid.lib.arsc.chunk;
import com.reandroid.lib.arsc.array.LibraryInfoArray;
import com.reandroid.lib.arsc.array.PackageArray;
import com.reandroid.lib.arsc.array.SpecTypePairArray;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.container.PackageLastBlocks;
@ -24,7 +25,8 @@ import java.io.IOException;
import java.util.*;
public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JSONObject> {
public class PackageBlock extends BaseChunk
implements BlockLoad, JSONConvert<JSONObject>, Comparable<PackageBlock> {
private final IntegerItem mPackageId;
private final PackageName mPackageName;
@ -136,7 +138,7 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JS
return mSpecTypePairArray;
}
public Collection<LibraryInfo> listLibraryInfo(){
return mLibraryBlock.listLibraryInfo();
return getLibraryBlock().listLibraryInfo();
}
public void addLibrary(LibraryBlock libraryBlock){
@ -148,7 +150,10 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JS
}
}
public void addLibraryInfo(LibraryInfo info){
mLibraryBlock.addLibraryInfo(info);
getLibraryBlock().addLibraryInfo(info);
}
private LibraryBlock getLibraryBlock(){
return mLibraryBlock;
}
public Set<Integer> listResourceIds(){
return mEntriesGroup.keySet();
@ -291,6 +296,22 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JS
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries));
}
public void merge(PackageBlock packageBlock){
if(packageBlock==null||packageBlock==this){
return;
}
if(getId()!=packageBlock.getId()){
throw new IllegalArgumentException("Can not merge different id packages: "
+getId()+"!="+packageBlock.getId());
}
setName(packageBlock.getName());
getLibraryBlock().merge(packageBlock.getLibraryBlock());
getSpecTypePairArray().merge(packageBlock.getSpecTypePairArray());
}
@Override
public int compareTo(PackageBlock pkg) {
return Integer.compare(getId(), pkg.getId());
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();

View File

@ -30,6 +30,9 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
addChild(mTableStringPool);
addChild(mPackageArray);
}
public void sortPackages(){
getPackageArray().sort();
}
public Collection<PackageBlock> listPackages(){
return getPackageArray().listItems();
}
@ -138,6 +141,16 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
getPackageArray().fromJson(json.getJSONArray(NAME_packages));
refresh();
}
public void merge(TableBlock tableBlock){
if(tableBlock==null||tableBlock==this){
return;
}
if(getPackageArray().childesCount()==0){
getTableStringPool().merge(tableBlock.getTableStringPool());
}
getPackageArray().merge(tableBlock.getPackageArray());
refresh();
}
public static TableBlock loadWithAndroidFramework(InputStream inputStream) throws IOException{
TableBlock tableBlock=load(inputStream);
tableBlock.addFramework(Frameworks.getAndroid());

View File

@ -1,6 +1,7 @@
package com.reandroid.lib.arsc.chunk;
import com.reandroid.lib.arsc.array.EntryBlockArray;
import com.reandroid.lib.arsc.array.TypeBlockArray;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.item.IntegerArray;
@ -138,6 +139,17 @@ public class TypeBlock extends BaseTypeBlock
getEntryBlockArray().fromJson(json.getJSONArray("entries"));
getResConfig().fromJson(json.getJSONObject("config"));
}
public void merge(TypeBlock typeBlock){
if(typeBlock==null||typeBlock==this){
return;
}
if(getTypeId() != typeBlock.getTypeId()){
throw new IllegalArgumentException("Can not merge different id types: "
+getTypeId()+"!="+typeBlock.getTypeId());
}
setTypeName(typeBlock.getTypeName());
getEntryBlockArray().merge(typeBlock.getEntryBlockArray());
}
@Override
public int compareTo(TypeBlock typeBlock) {
int id1=getTypeId();

View File

@ -18,12 +18,17 @@ public class AndroidManifestBlock extends ResXmlBlock{
public ResXmlElement getMainActivity(){
for(ResXmlElement activity:listActivities()){
for(ResXmlElement intentFilter:activity.listElements(TAG_intent_filter)){
ResXmlAttribute attribute = intentFilter.searchAttributeByResourceId(ID_name);
for(ResXmlElement action:intentFilter.listElements(TAG_action)){
ResXmlAttribute attribute = action.searchAttributeByResourceId(ID_name);
if(attribute==null){
continue;
}
if(VALUE_android_intent_action_MAIN.equals(attribute.getValueAsString())){
return activity;
}
}
}
}
return null;
}
public List<ResXmlElement> listActivities(){
@ -294,6 +299,8 @@ public class AndroidManifestBlock extends ResXmlBlock{
public static final String NAME_versionCode = "versionCode";
public static final String NAME_versionName = "versionName";
public static final String NAME_name = "name";
public static final String NAME_extractNativeLibs = "extractNativeLibs";
public static final String NAME_isSplitRequired = "isSplitRequired";
public static final int ID_name = 0x01010003;
public static final int ID_compileSdkVersion = 0x01010572;
@ -302,6 +309,8 @@ public class AndroidManifestBlock extends ResXmlBlock{
public static final int ID_host = 0x01010028;
public static final int ID_configChanges = 0x0101001f;
public static final int ID_screenOrientation = 0x0101001e;
public static final int ID_extractNativeLibs = 0x010104ea;
public static final int ID_isSplitRequired = 0x01010591;
public static final String VALUE_android_intent_action_MAIN = "android.intent.action.MAIN";

View File

@ -159,6 +159,16 @@ public class SpecTypePair extends BlockContainer<Block>
getSpecBlock().setTypeId((byte) json.getInt("id"));
getTypeBlockArray().fromJson(json.getJSONArray("types"));
}
public void merge(SpecTypePair typePair){
if(typePair==null||typePair==this){
return;
}
if(getTypeId() != typePair.getTypeId()){
throw new IllegalArgumentException("Can not merge different id types: "
+getTypeId()+"!="+typePair.getTypeId());
}
getTypeBlockArray().merge(typePair.getTypeBlockArray());
}
@Override
public int compareTo(SpecTypePair specTypePair) {
return Integer.compare(getTypeId(), specTypePair.getTypeId());

View File

@ -5,6 +5,23 @@ public class ByteItem extends BlockItem {
public ByteItem() {
super(1);
}
public boolean getBit(int index){
return ((get()>>index) & 0x1) == 1;
}
public void putBit(int index, boolean bit){
int val=get();
int left=val>>index;
if(bit){
left=left|0x1;
}else {
left=left & 0xFE;
}
left=left<<index;
index=8-index;
int right=(0xFF>>index) & val;
val=left|right;
set((byte) val);
}
public void set(byte b){
getBytesInternal()[0]=b;
}

View File

@ -307,6 +307,14 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
addSpanInfo(spanInfo.getTag(), spanInfo.getFirst(), spanInfo.getLast());
}
}
public void merge(StyleItem styleItem){
if(styleItem==null||styleItem==this){
return;
}
for(int[] info:styleItem.getIntSpanInfoList()){
addStylePiece(info[0], info[1], info[2]);
}
}
@Override
public String toString(){
return "Spans count = "+getSpanInfoList().size();

View File

@ -15,4 +15,22 @@ public class TableStringPool extends BaseStringPool<TableString> {
StringArray<TableString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
return new TableStringArray(offsets, itemCount, itemStart, is_utf8);
}
public void merge(TableStringPool stringPool){
if(stringPool==null||stringPool==this){
return;
}
StringArray<TableString> existArray = getStringsArray();
if(existArray.childesCount()!=0){
return;
}
StringArray<TableString> comingArray = stringPool.getStringsArray();
int count=comingArray.childesCount();
existArray.ensureSize(count);
for(int i=0;i<count;i++){
TableString exist = existArray.get(i);
TableString coming = comingArray.get(i);
exist.set(coming.get());
}
getStyleArray().merge(stringPool.getStyleArray());
}
}

View File

@ -11,6 +11,9 @@ public abstract class BaseResValue extends BlockItem implements JSONConvert<JSON
super(bytesLength);
}
protected void onRemoved(){
}
public EntryBlock getEntryBlock(){
Block parent=getParent();
while(parent!=null){

View File

@ -1,8 +1,10 @@
package com.reandroid.lib.arsc.value;
import com.reandroid.lib.arsc.array.EntryBlockArray;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.base.BlockCounter;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.SpecBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.group.EntryGroup;
@ -17,6 +19,7 @@ import com.reandroid.lib.json.JSONObject;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class EntryBlock extends Block implements JSONConvert<JSONObject> {
@ -228,41 +231,23 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
removeSpecReferences();
}
public void setEntryTypeBag(boolean b){
int val=mFlagEntryType.get();
if(b){
val=val|0x1;
}else {
val=val&0xFE;
}
mFlagEntryType.set((byte) val);
mFlagEntryType.putBit(0, b);
refreshHeaderSize();
}
public boolean isEntryTypeBag(){
return ((mFlagEntryType.get() & 0x1) != 0);
return mFlagEntryType.getBit(0);
}
public void setEntryTypeShared(boolean b){
int val=mFlagEntryType.get();
if(b){
val=val|0x2;
}else {
val=val&0xFD;
}
mFlagEntryType.set((byte) val);
mFlagEntryType.putBit(1, b);
}
public boolean isEntryTypeShared(){
return (mFlagEntryType.get() & 0x2) !=0;
return mFlagEntryType.getBit(1);
}
public void setEntryTypePublic(boolean b){
int val=mFlagEntryType.get();
if(b){
val=val|0x4;
}else {
val=val&0xFB;
}
mFlagEntryType.set((byte) val);
mFlagEntryType.putBit(2, b);
}
public boolean isEntryTypePublic(){
return (mFlagEntryType.get() & 0x4) !=0;
return mFlagEntryType.getBit(2);
}
private void setByteFlagsB(byte b){
mByteFlagsB.set(b);
@ -310,6 +295,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
if(mResValue!=null){
mResValue.setIndex(-1);
mResValue.setParent(null);
mResValue.onRemoved();
}
if(resValue!=null){
setNull(false);
@ -685,6 +671,41 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
baseResValue.fromJson(json.getJSONObject(NAME_value));
mResValue.onDataLoaded();
}
public void merge(EntryBlock entryBlock){
if(entryBlock==null||entryBlock==this||entryBlock.isNull()){
return;
}
String name=entryBlock.getName();
if(name==null){
name="";
}
if(!entryBlock.isEntryTypeBag()){
ResValueInt comingValue = (ResValueInt) entryBlock.getResValue();
ResValueInt exist = getOrCreateResValueInt();
setResValue(exist);
exist.merge(comingValue);
}else {
ResValueBag comingValue = (ResValueBag) entryBlock.getResValue();
ResValueBag exist=getOrCreateResValueBag();
setResValue(exist);
exist.merge(comingValue);
}
SpecString spec = getPackageBlock()
.getSpecStringPool().getOrCreate(name);
setSpecReference(spec.getIndex());
}
private ResValueBag getOrCreateResValueBag(){
if(mResValue instanceof ResValueBag){
return (ResValueBag) mResValue;
}
return new ResValueBag();
}
private ResValueInt getOrCreateResValueInt(){
if(mResValue instanceof ResValueInt){
return (ResValueInt) mResValue;
}
return new ResValueInt();
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();

View File

@ -1,5 +1,6 @@
package com.reandroid.lib.arsc.value;
import com.reandroid.lib.arsc.array.LibraryInfoArray;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.base.BlockCounter;
import com.reandroid.lib.arsc.io.BlockReader;
@ -88,6 +89,16 @@ public class LibraryInfo extends Block implements JSONConvert<JSONObject> {
setPackageId(json.getInt("id"));
setPackageName(json.getString("name"));
}
public void merge(LibraryInfo info){
if(info==null||info==this){
return;
}
if(getPackageId()!=info.getPackageId()){
throw new IllegalArgumentException("Can not add different id libraries: "
+getPackageId()+"!="+info.getPackageId());
}
setPackageName(info.getPackageName());
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();

View File

@ -113,7 +113,6 @@ public class ResValueBag extends BaseResValue {
if(isNull()){
return 0;
}
refreshCount();
int result;
result=mParentId.writeBytes(writer);
result+=mCount.writeBytes(writer);
@ -150,6 +149,14 @@ public class ResValueBag extends BaseResValue {
getResValueBagItemArray().fromJson(json.getJSONArray(NAME_items));
refreshCount();
}
public void merge(ResValueBag resValueBag){
if(resValueBag==null||resValueBag==this){
return;
}
setParentId(resValueBag.getParentId());
getResValueBagItemArray().merge(resValueBag.getResValueBagItemArray());
refreshCount();
}
@Override
public String toString(){

View File

@ -11,6 +11,10 @@ public class ResValueBagItem extends BaseResValueItem{
super(BYTES_COUNT);
setHeaderSize(BYTES_SIZE);
}
@Override
public void onRemoved(){
removeTableReference();
}
public String getValueAsString(){
return getTableString(getData()).getHtml();
}
@ -186,6 +190,19 @@ public class ResValueBagItem extends BaseResValueItem{
setData(json.getInt(NAME_data));
}
}
public void merge(ResValueBagItem bagItem){
if(bagItem==null||bagItem==this){
return;
}
setId(bagItem.getId());
ValueType valueType=bagItem.getValueType();
if(valueType==ValueType.STRING){
setValueAsString(bagItem.getValueAsString());
}else {
setType(valueType);
setData(bagItem.getData());
}
}
@Override
public String toString(){

View File

@ -1,6 +1,7 @@
package com.reandroid.lib.arsc.value;
import com.reandroid.lib.arsc.decoder.ValueDecoder;
import com.reandroid.lib.arsc.item.SpecString;
import com.reandroid.lib.arsc.item.TableString;
import com.reandroid.lib.json.JSONObject;
@ -9,6 +10,9 @@ public class ResValueInt extends BaseResValueItem {
super(BYTES_COUNT);
setHeaderSize(BYTES_SIZE);
}
protected void onRemoved(){
removeTableReference();
}
public String getValueAsString(){
return getString(getData());
}
@ -116,6 +120,18 @@ public class ResValueInt extends BaseResValueItem {
setData(json.getInt(NAME_data));
}
}
public void merge(ResValueInt resValueInt){
if(resValueInt==null||resValueInt==this){
return;
}
ValueType valueType=resValueInt.getValueType();
if(valueType==ValueType.STRING){
setValueAsString(resValueInt.getValueAsString());
}else {
setType(valueType);
setData(resValueInt.getData());
}
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();