improve XML encoding speed

This commit is contained in:
REAndroid 2023-01-17 11:16:24 -05:00
parent 3eda5edaf6
commit 50753ca54c
6 changed files with 190 additions and 105 deletions

View File

@ -19,6 +19,7 @@ import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.arsc.pool.SpecStringPool;
import com.reandroid.lib.arsc.util.ResNameMap;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
import com.reandroid.xml.*;
@ -244,9 +245,23 @@ import java.util.*;
public final byte id;
public String name;
public final Map<Byte, Type> typeMap;
private final ResNameMap<Integer> mEntryNameMap;
public Package(byte id){
this.id = id;
this.typeMap = new HashMap<>();
this.mEntryNameMap = new ResNameMap<>();
}
public void loadEntryMap(){
mEntryNameMap.clear();
for(Type type:typeMap.values()){
String typeName=type.getName();
for(Type.Entry entry: type.entryMap.values()){
mEntryNameMap.add(typeName, entry.getName(), entry.getResourceId());
}
}
}
public Integer getResourceId(String typeName, String name){
return mEntryNameMap.get(typeName, name);
}
public Type.Entry getEntry(String typeName, String name){
Type type=getType(typeName);

View File

@ -25,6 +25,7 @@
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.arsc.item.SpecString;
import com.reandroid.lib.arsc.util.FrameworkTable;
import com.reandroid.lib.arsc.util.ResNameMap;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.lib.common.Frameworks;
import com.reandroid.lib.common.ResourceResolver;
@ -41,6 +42,7 @@
private APKLogger apkLogger;
private boolean mForceCreateNamespaces = true;
private Set<String> mFrameworkPackageNames;
private final ResNameMap<EntryBlock> mLocalResNameMap = new ResNameMap<>();
public EncodeMaterials(){
}
public SpecString getSpecString(String name){
@ -48,29 +50,6 @@
.get(name)
.get(0);
}
public void addTableStringPool(Collection<String> stringList){
getCurrentPackage()
.getTableBlock()
.getTableStringPool()
.addStrings(stringList);
}
public int resolveAttributeNameReference(String refString){
String packageName = null;
String type = "attr";
String name = refString;
int i=refString.lastIndexOf(':');
if(i>=0){
packageName=refString.substring(0, i);
name=refString.substring(i+1);
}
if(EncodeUtil.isEmpty(packageName)
|| packageName.equals(getCurrentPackageName())
|| !isFrameworkPackageName(packageName)){
return resolveLocalResourceId(type, name);
}
return resolveFrameworkResourceId(packageName, type, name);
}
public EntryBlock getAttributeBlock(String refString){
String packageName = null;
String type = "attr";
@ -111,34 +90,11 @@
}
return resolveFrameworkResourceId(packageName, type, name);
}
public EntryBlock resolveEntryReference(String refString){
if("@null".equals(refString)){
return null;
}
Matcher matcher = ValueDecoder.PATTERN_REFERENCE.matcher(refString);
if(!matcher.find()){
return null;
}
String prefix=matcher.group(1);
String packageName = matcher.group(2);
if(packageName!=null && packageName.endsWith(":")){
packageName=packageName.substring(0, packageName.length()-1);
}
String type = matcher.group(4);
String name = matcher.group(5);
if(EncodeUtil.isEmpty(packageName)
|| packageName.equals(getCurrentPackageName())
|| !isFrameworkPackageName(packageName)){
return getLocalEntryBlock(type, name);
}
return getFrameworkEntry(packageName, type, name);
}
public int resolveLocalResourceId(String type, String name){
for(ResourceIds.Table.Package pkg:packageIdSet){
ResourceIds.Table.Package.Type.Entry entry = pkg.getEntry(type, name);
if(entry!=null){
return entry.getResourceId();
Integer resId = pkg.getResourceId(type, name);
if(resId!=null){
return resId;
}
}
EntryGroup entryGroup=getLocalEntryGroup(type, name);
@ -149,15 +105,6 @@
"type="+type+
", name="+name);
}
public int resolveFrameworkResourceId(String type, String name){
EntryBlock entryBlock = getFrameworkEntry(type, name);
if(entryBlock!=null){
return entryBlock.getResourceId();
}
throw new EncodeException("Framework entry not found: " +
"type="+type+
", name="+name);
}
public int resolveFrameworkResourceId(String packageName, String type, String name){
EntryBlock entryBlock = getFrameworkEntry(packageName, type, name);
if(entryBlock!=null){
@ -196,6 +143,22 @@
return null;
}
public EntryBlock getLocalEntryBlock(String type, String name){
EntryBlock entryBlock=mLocalResNameMap.get(type, name);
if(entryBlock!=null){
return entryBlock;
}
loadLocalEntryBlockMap(type);
entryBlock=mLocalResNameMap.get(type, name);
if(entryBlock!=null){
return entryBlock;
}
entryBlock= searchLocalEntryBlock(type, name);
if(entryBlock!=null){
mLocalResNameMap.add(type, name, entryBlock);
}
return entryBlock;
}
private EntryBlock searchLocalEntryBlock(String type, String name){
for(EntryGroup entryGroup : currentPackage.listEntryGroup()){
if(type.equals(entryGroup.getTypeName()) &&
name.equals(entryGroup.getSpecName())){
@ -205,7 +168,7 @@
SpecTypePair specTypePair=currentPackage.searchByTypeName(type);
if(specTypePair!=null){
for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){
for(EntryBlock entryBlock:typeBlock.listEntries()){
for(EntryBlock entryBlock:typeBlock.listEntries(true)){
if(name.equals(entryBlock.getName())){
return entryBlock;
}
@ -220,7 +183,7 @@
specTypePair=packageBlock.searchByTypeName(type);
if(specTypePair!=null){
for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){
for(EntryBlock entryBlock:typeBlock.listEntries()){
for(EntryBlock entryBlock:typeBlock.listEntries(true)){
if(name.equals(entryBlock.getName())){
return entryBlock;
}
@ -231,6 +194,20 @@
}
return null;
}
private void loadLocalEntryBlockMap(String type){
ResNameMap<EntryBlock> localMap = mLocalResNameMap;
for(PackageBlock packageBlock:currentPackage.getTableBlock().listPackages()){
SpecTypePair specTypePair=packageBlock.searchByTypeName(type);
if(specTypePair!=null){
for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){
for(EntryBlock entryBlock:typeBlock.listEntries(true)){
localMap.add(entryBlock.getTypeName(),
entryBlock.getName(), entryBlock);
}
}
}
}
}
public EntryBlock getFrameworkEntry(String type, String name){
for(FrameworkTable table:frameworkTables){
EntryBlock entryBlock = table.searchEntryBlock(type, name);
@ -282,16 +259,12 @@
}
return null;
}
public EncodeMaterials setForceCreateNamespaces(boolean force) {
this.mForceCreateNamespaces = force;
return this;
}
public EncodeMaterials addPackageIds(Collection<ResourceIds.Table.Package> packageIdList) {
this.packageIdSet.addAll(packageIdList);
return this;
}
public EncodeMaterials addPackageIds(ResourceIds.Table.Package packageIds) {
packageIds.loadEntryMap();
this.packageIdSet.add(packageIds);
return this;
}

View File

@ -99,16 +99,23 @@ public class XMLFileEncoder {
int count=element.getAttributeCount();
for(int i=0;i<count;i++){
XMLAttribute attribute=element.getAttributeAt(i);
if(attribute instanceof SchemaAttr){
continue;
}
if(SchemaAttr.looksSchema(attribute.getName(), attribute.getValue())){
continue;
}
EntryBlock entryBlock=materials.getAttributeBlock(attribute.getName());
int resourceId=0;
if(entryBlock!=null){
resourceId=entryBlock.getResourceId();
String name=attribute.getNameWoPrefix();
int resourceId=decodeUnknownAttributeHex(name);
EntryBlock entryBlock=null;
if(resourceId==0){
entryBlock=getAttributeBlock(attribute);
if(entryBlock!=null){
resourceId=entryBlock.getResourceId();
}
}
ResXmlAttribute xmlAttribute =
resXmlElement.createAttribute(attribute.getNameWoPrefix(), resourceId);
resXmlElement.createAttribute(name, resourceId);
String prefix=attribute.getNamePrefix();
if(prefix!=null){
ResXmlStartNamespace ns = resXmlElement.getStartNamespaceByPrefix(prefix);
@ -198,11 +205,37 @@ public class XMLFileEncoder {
}
}
private void addResourceId(ResIdBuilder idBuilder, XMLAttribute attribute){
EntryBlock entryBlock = materials.getAttributeBlock(attribute.getName());
String name=attribute.getNameWoPrefix();
int id=decodeUnknownAttributeHex(name);
if(id!=0){
idBuilder.add(id, name);
return;
}
EntryBlock entryBlock = getAttributeBlock(attribute);
if(entryBlock!=null){
idBuilder.add(entryBlock.getResourceId(), entryBlock.getName());
}
}
private int decodeUnknownAttributeHex(String name){
if(name.length()==0||name.charAt(0)!='@'){
return 0;
}
name=name.substring(1);
if(!ValueDecoder.isHex(name)){
return 0;
}
return ValueDecoder.parseHex(name);
}
private EntryBlock getAttributeBlock(XMLAttribute attribute){
if(attribute instanceof SchemaAttr){
return null;
}
String name=attribute.getName();
if(name.indexOf(':')<0){
return null;
}
return materials.getAttributeBlock(name);
}
private ResXmlStartNamespace forceCreateNamespace(ResXmlElement resXmlElement,
int resourceId, String prefix){
if(!materials.isForceCreateNamespaces()){

View File

@ -39,8 +39,7 @@ public class FrameworkTable extends TableBlock {
private String mFrameworkTitle;
private String mFrameworkName;
private String mFrameworkVersion;
private Map<String, Map<String, EntryGroup>> mNameGroupMap;
private final Object mMapLock=new Object();
private ResNameMap<EntryGroup> mNameGroupMap;
public FrameworkTable(){
super();
}
@ -57,55 +56,40 @@ public class FrameworkTable extends TableBlock {
* Call this if you plan to search entries frequently
*/
public void loadResourceNameMap(){
synchronized (mMapLock){
if(mNameGroupMap !=null){
return;
}
Map<String, Map<String, EntryGroup>> typeMap=new HashMap<>();
ResNameMap<EntryGroup> resNameMap = mNameGroupMap;
if(resNameMap == null){
resNameMap = new ResNameMap<>();
for(PackageBlock packageBlock:listPackages()){
for(EntryGroup group:packageBlock.listEntryGroup()){
String type=group.getTypeName();
Map<String, EntryGroup> groupMap=typeMap.get(type);
if(groupMap==null){
groupMap=new HashMap<>();
typeMap.put(type, groupMap);
}
groupMap.put(group.getSpecName(), group);
resNameMap.add(group.getTypeName(),
group.getSpecName(),
group);
}
}
mNameGroupMap = typeMap;
mNameGroupMap = resNameMap;
}
}
/**
* Clears resource name map from memory
*/
public void clearResourceNameMap(){
synchronized (mMapLock){
if(mNameGroupMap!=null){
mNameGroupMap.clear();
mNameGroupMap =null;
}
if(mNameGroupMap!=null){
mNameGroupMap.clear();
mNameGroupMap =null;
}
}
private boolean hasResourceGroupMap(){
synchronized (mMapLock){
return mNameGroupMap!=null;
}
return mNameGroupMap!=null;
}
private EntryBlock searchEntryBlockFromMap(String typeName, String entryName){
synchronized (mMapLock){
if(mNameGroupMap ==null){
return null;
}
Map<String, EntryGroup> groupMap = mNameGroupMap.get(typeName);
if(groupMap!=null){
EntryGroup group=groupMap.get(entryName);
if(group!=null){
return group.pickOne();
}
}
if(mNameGroupMap ==null){
return null;
}
EntryGroup entryGroup = mNameGroupMap.get(typeName, entryName);
if(entryGroup!=null){
return entryGroup.pickOne();
}
return null;
}
public EntryBlock searchEntryBlock(String typeName, String entryName){
if(hasResourceGroupMap()){

View File

@ -1,3 +1,18 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.lib.arsc.util;
import java.io.IOException;

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.reandroid.lib.arsc.util;
import java.util.HashMap;
import java.util.Map;
public class ResNameMap<VALUE> {
private final Object mLock = new Object();
private final Map<String, Map<String, VALUE>> mainMap;
public ResNameMap(){
this.mainMap = new HashMap<>();
}
public VALUE get(String type, String name){
synchronized (mLock){
if(type==null || name==null){
return null;
}
Map<String, VALUE> valueMap = mainMap.get(type);
if(valueMap!=null){
return valueMap.get(name);
}
return null;
}
}
public void add(String type, String name, VALUE value){
synchronized (mLock){
if(type==null || name==null || value==null){
return;
}
Map<String, VALUE> valueMap = mainMap.get(type);
if(valueMap==null){
valueMap=new HashMap<>();
mainMap.put(type, valueMap);
}
valueMap.put(name, value);
}
}
public void clear(){
synchronized (mLock){
for(Map<String, VALUE> valueMap:mainMap.values()){
valueMap.clear();
}
mainMap.clear();
}
}
private static final String TYPE_ATTR = "attr";
private static final String TYPE_ID = "id";
}