Add FrameworkApk & framework optimize

This commit is contained in:
REAndroid
2022-12-24 11:47:00 -05:00
parent 45f7e8a064
commit 1c59826985
8 changed files with 203 additions and 15 deletions

View File

@ -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){

View File

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

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.apk;
import com.reandroid.archive.InputSource;

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

View File

@ -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";

View File

@ -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){

View File

@ -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";