mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-06-13 05:37:41 +02:00
Add FrameworkApk & framework optimize
This commit is contained in:
@ -70,6 +70,12 @@ public class ZipArchive {
|
||||
}
|
||||
}
|
||||
}
|
||||
public void clear(){
|
||||
mEntriesMap.clear();
|
||||
}
|
||||
public int entriesCount(){
|
||||
return mEntriesMap.size();
|
||||
}
|
||||
public InputSource remove(String name){
|
||||
InputSource inputSource=mEntriesMap.remove(name);
|
||||
if(inputSource==null){
|
||||
|
@ -251,9 +251,12 @@ public class ApkModule {
|
||||
|| getApkArchive().getInputSource(TableBlock.FILE_NAME)!=null;
|
||||
}
|
||||
public TableBlock getTableBlock() throws IOException {
|
||||
if(mTableBlock!=null){
|
||||
return mTableBlock;
|
||||
if(mTableBlock==null){
|
||||
mTableBlock=loadTableBlock();
|
||||
}
|
||||
return mTableBlock;
|
||||
}
|
||||
TableBlock loadTableBlock() throws IOException {
|
||||
APKArchive archive=getApkArchive();
|
||||
InputSource inputSource = archive.getInputSource(TableBlock.FILE_NAME);
|
||||
if(inputSource==null){
|
||||
@ -276,12 +279,11 @@ public class ApkModule {
|
||||
}
|
||||
inputStream.close();
|
||||
}
|
||||
mTableBlock=tableBlock;
|
||||
BlockInputSource<TableBlock> blockInputSource=new BlockInputSource<>(inputSource.getName(),tableBlock);
|
||||
BlockInputSource<TableBlock> blockInputSource=new BlockInputSource<>(inputSource.getName(), tableBlock);
|
||||
blockInputSource.setMethod(inputSource.getMethod());
|
||||
blockInputSource.setSort(inputSource.getSort());
|
||||
archive.add(blockInputSource);
|
||||
return mTableBlock;
|
||||
return tableBlock;
|
||||
}
|
||||
public APKArchive getApkArchive() {
|
||||
return apkArchive;
|
||||
@ -362,7 +364,7 @@ public class ApkModule {
|
||||
public void setAPKLogger(APKLogger logger) {
|
||||
this.apkLogger = logger;
|
||||
}
|
||||
private void logMessage(String msg) {
|
||||
void logMessage(String msg) {
|
||||
if(apkLogger!=null){
|
||||
apkLogger.logMessage(msg);
|
||||
}
|
||||
|
@ -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.apk;
|
||||
|
||||
import com.reandroid.archive.InputSource;
|
||||
|
116
src/main/java/com/reandroid/lib/apk/FrameworkApk.java
Normal file
116
src/main/java/com/reandroid/lib/apk/FrameworkApk.java
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.apk;
|
||||
|
||||
import com.reandroid.archive.APKArchive;
|
||||
import com.reandroid.archive.InputSource;
|
||||
import com.reandroid.lib.arsc.chunk.TableBlock;
|
||||
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
|
||||
import com.reandroid.lib.arsc.chunk.xml.ResXmlAttribute;
|
||||
import com.reandroid.lib.arsc.chunk.xml.ResXmlElement;
|
||||
import com.reandroid.lib.arsc.util.FrameworkTable;
|
||||
import com.reandroid.lib.arsc.value.ValueType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/*
|
||||
* Produces compressed framework apk by removing unnecessary files and entries,
|
||||
* basically it keeps only resources.arsc and AndroidManifest.xml
|
||||
*/
|
||||
public class FrameworkApk extends ApkModule{
|
||||
public FrameworkApk(String moduleName, APKArchive apkArchive) {
|
||||
super(moduleName, apkArchive);
|
||||
}
|
||||
public FrameworkApk(APKArchive apkArchive) {
|
||||
this("framework", apkArchive);
|
||||
}
|
||||
@Override
|
||||
public APKArchive getApkArchive() {
|
||||
APKArchive archive=super.getApkArchive();
|
||||
clearFiles(archive);
|
||||
return archive;
|
||||
}
|
||||
private void clearFiles(APKArchive archive){
|
||||
if(archive.entriesCount()==2){
|
||||
return;
|
||||
}
|
||||
InputSource tableSource= archive.getInputSource(TableBlock.FILE_NAME);
|
||||
InputSource manifestSource= archive.getInputSource(AndroidManifestBlock.FILE_NAME);
|
||||
archive.clear();
|
||||
archive.add(tableSource);
|
||||
archive.add(manifestSource);
|
||||
}
|
||||
public FrameworkTable getTableBlock() throws IOException {
|
||||
return (FrameworkTable) super.getTableBlock();
|
||||
}
|
||||
@Override
|
||||
FrameworkTable loadTableBlock() throws IOException {
|
||||
APKArchive archive=getApkArchive();
|
||||
InputSource inputSource = archive.getInputSource(TableBlock.FILE_NAME);
|
||||
if(inputSource==null){
|
||||
throw new IOException("Entry not found: "+TableBlock.FILE_NAME);
|
||||
}
|
||||
InputStream inputStream = inputSource.openStream();
|
||||
FrameworkTable frameworkTable=FrameworkTable.load(inputStream);
|
||||
if(hasAndroidManifestBlock()){
|
||||
optimizeTable(frameworkTable);
|
||||
}
|
||||
BlockInputSource<FrameworkTable> blockInputSource=new BlockInputSource<>(inputSource.getName(), frameworkTable);
|
||||
blockInputSource.setMethod(inputSource.getMethod());
|
||||
blockInputSource.setSort(inputSource.getSort());
|
||||
archive.add(blockInputSource);
|
||||
return frameworkTable;
|
||||
}
|
||||
private void optimizeTable(FrameworkTable table) throws IOException {
|
||||
if(table.isOptimized()){
|
||||
return;
|
||||
}
|
||||
int prev=table.countBytes();
|
||||
logMessage("Optimizing ...");
|
||||
AndroidManifestBlock manifestBlock = getAndroidManifestBlock();
|
||||
String version=String.valueOf(manifestBlock.getVersionCode());
|
||||
String name=manifestBlock.getPackageName();
|
||||
table.optimize(name, version);
|
||||
long diff=prev - table.countBytes();
|
||||
long percent=(diff*100L)/prev;
|
||||
logMessage("Optimized: "+percent+" %");
|
||||
}
|
||||
public static FrameworkApk loadApkFile(File apkFile) throws IOException {
|
||||
APKArchive archive=APKArchive.loadZippedApk(apkFile);
|
||||
return new FrameworkApk(archive);
|
||||
}
|
||||
public static FrameworkApk loadApkFile(File apkFile, String moduleName) throws IOException {
|
||||
APKArchive archive=APKArchive.loadZippedApk(apkFile);
|
||||
return new FrameworkApk(moduleName, archive);
|
||||
}
|
||||
public static boolean isFramework(ApkModule apkModule) throws IOException {
|
||||
if(!apkModule.hasAndroidManifestBlock()){
|
||||
return false;
|
||||
}
|
||||
return isFramework(apkModule.getAndroidManifestBlock());
|
||||
}
|
||||
public static boolean isFramework(AndroidManifestBlock manifestBlock){
|
||||
ResXmlElement root = manifestBlock.getManifestElement();
|
||||
ResXmlAttribute attribute = root.getStartElement()
|
||||
.searchAttributeByName(AndroidManifestBlock.NAME_coreApp);
|
||||
if(attribute==null || attribute.getValueType()!= ValueType.INT_BOOLEAN){
|
||||
return false;
|
||||
}
|
||||
return attribute.getValueAsBoolean();
|
||||
}
|
||||
}
|
@ -316,6 +316,7 @@ public class AndroidManifestBlock extends ResXmlBlock{
|
||||
public static final String NAME_compileSdkVersionCodename = "compileSdkVersionCodename";
|
||||
public static final String NAME_installLocation="installLocation";
|
||||
public static final String NAME_PACKAGE = "package";
|
||||
public static final String NAME_coreApp = "coreApp";
|
||||
public static final String NAME_platformBuildVersionCode = "platformBuildVersionCode";
|
||||
public static final String NAME_platformBuildVersionName = "platformBuildVersionName";
|
||||
public static final String NAME_versionCode = "versionCode";
|
||||
|
@ -31,8 +31,10 @@ public class BlockList<T extends Block> extends Block {
|
||||
super();
|
||||
mItems=new ArrayList<>();
|
||||
}
|
||||
public void remove(T item){
|
||||
mItems.remove(item);
|
||||
public boolean remove(T item){
|
||||
item.setParent(null);
|
||||
item.setIndex(-1);
|
||||
return mItems.remove(item);
|
||||
}
|
||||
public void add(T item){
|
||||
if(item==null){
|
||||
|
@ -15,10 +15,13 @@
|
||||
*/
|
||||
package com.reandroid.lib.arsc.util;
|
||||
|
||||
import com.reandroid.lib.arsc.array.SpecTypePairArray;
|
||||
import com.reandroid.lib.arsc.array.TypeBlockArray;
|
||||
import com.reandroid.lib.arsc.chunk.ChunkType;
|
||||
import com.reandroid.lib.arsc.chunk.PackageBlock;
|
||||
import com.reandroid.lib.arsc.chunk.TableBlock;
|
||||
import com.reandroid.lib.arsc.chunk.TypeBlock;
|
||||
import com.reandroid.lib.arsc.container.SpecTypePair;
|
||||
import com.reandroid.lib.arsc.group.EntryGroup;
|
||||
import com.reandroid.lib.arsc.header.HeaderBlock;
|
||||
import com.reandroid.lib.arsc.io.BlockReader;
|
||||
@ -26,6 +29,7 @@ import com.reandroid.lib.arsc.item.ReferenceItem;
|
||||
import com.reandroid.lib.arsc.item.TableString;
|
||||
import com.reandroid.lib.arsc.pool.TableStringPool;
|
||||
import com.reandroid.lib.arsc.value.EntryBlock;
|
||||
import com.reandroid.lib.arsc.value.ResConfig;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
@ -88,13 +92,6 @@ public class FrameworkTable extends TableBlock {
|
||||
super.readBytes(reader);
|
||||
}
|
||||
@Override
|
||||
public int onWriteBytes(OutputStream stream) throws IOException{
|
||||
int length=super.onWriteBytes(stream);
|
||||
stream.flush();
|
||||
stream.close();
|
||||
return length;
|
||||
}
|
||||
@Override
|
||||
public void onReadBytes(BlockReader reader) throws IOException {
|
||||
super.onReadBytes(reader);
|
||||
reader.close();
|
||||
@ -105,6 +102,9 @@ public class FrameworkTable extends TableBlock {
|
||||
List<EntryBlock> entryBlockList=getEntriesToRemove(group);
|
||||
removeEntryBlocks(entryBlockList);
|
||||
}
|
||||
for(PackageBlock pkg:listPackages()){
|
||||
clearNonDefaultConfigs(pkg);
|
||||
}
|
||||
for(PackageBlock pkg:listPackages()){
|
||||
pkg.removeEmpty();
|
||||
pkg.refresh();
|
||||
@ -115,6 +115,38 @@ public class FrameworkTable extends TableBlock {
|
||||
setFrameworkVersion(frameworkVersion);
|
||||
refresh();
|
||||
}
|
||||
private void clearNonDefaultConfigs(PackageBlock pkg){
|
||||
SpecTypePairArray specTypePairArray = pkg.getSpecTypePairArray();
|
||||
specTypePairArray.sort();
|
||||
List<SpecTypePair> specTypePairList=new ArrayList<>(specTypePairArray.listItems());
|
||||
for(SpecTypePair specTypePair:specTypePairList){
|
||||
clearNonDefaultConfigs(specTypePair);
|
||||
}
|
||||
}
|
||||
private void clearNonDefaultConfigs(SpecTypePair specTypePair){
|
||||
TypeBlockArray typeBlockArray = specTypePair.getTypeBlockArray();
|
||||
if(typeBlockArray.childesCount()<2){
|
||||
return;
|
||||
}
|
||||
List<TypeBlock> typeBlockList=new ArrayList<>(typeBlockArray.listItems());
|
||||
TypeBlock defTypeBlock=null;
|
||||
for(TypeBlock typeBlock:typeBlockList){
|
||||
if(defTypeBlock==null){
|
||||
defTypeBlock=typeBlock;
|
||||
}
|
||||
ResConfig config = typeBlock.getResConfig();
|
||||
if(config.isDefault()){
|
||||
defTypeBlock=typeBlock;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for(TypeBlock typeBlock:typeBlockList){
|
||||
if(typeBlock==defTypeBlock){
|
||||
continue;
|
||||
}
|
||||
typeBlockArray.remove(typeBlock);
|
||||
}
|
||||
}
|
||||
private void optimizeTableString(){
|
||||
removeUnusedTableString();
|
||||
shrinkTableString();
|
||||
@ -253,12 +285,18 @@ public class FrameworkTable extends TableBlock {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public boolean isOptimized(){
|
||||
return getFrameworkVersion()!=null;
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
HeaderBlock headerBlock=getHeaderBlock();
|
||||
if(headerBlock.getChunkType()!= ChunkType.TABLE){
|
||||
return super.toString();
|
||||
}
|
||||
if(!isOptimized()){
|
||||
return "Unoptimized: "+super.toString();
|
||||
}
|
||||
StringBuilder builder=new StringBuilder();
|
||||
builder.append(getClass().getSimpleName());
|
||||
builder.append(": SIZE=").append(headerBlock.getChunkSize());
|
||||
@ -292,6 +330,14 @@ public class FrameworkTable extends TableBlock {
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
public static FrameworkTable load(File file) throws IOException{
|
||||
return load(new FileInputStream(file));
|
||||
}
|
||||
public static FrameworkTable load(InputStream inputStream) throws IOException{
|
||||
FrameworkTable frameworkTable=new FrameworkTable();
|
||||
frameworkTable.readBytes(inputStream);
|
||||
return frameworkTable;
|
||||
}
|
||||
private static final String TITLE_STRING="Framework table";
|
||||
private static final String PROP_TITLE="TITLE";
|
||||
private static final String PROP_NAME="NAME";
|
||||
|
Binary file not shown.
Reference in New Issue
Block a user