Skip to content

Commit

Permalink
Keep the compression of each entries in the resources.ap_ which gener…
Browse files Browse the repository at this point in the history
…ated by the base aapt, refer to #215 #172 #220
  • Loading branch information
galenlin committed Jul 18, 2016
1 parent a1dc49e commit 535370d
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ import com.android.build.gradle.internal.transforms.ProGuardTransform
import com.android.build.gradle.tasks.MergeManifests
import com.android.build.gradle.tasks.MergeSourceSetFolders
import com.android.build.gradle.tasks.ProcessAndroidResources
import com.android.sdklib.BuildToolInfo
import groovy.io.FileType
import net.wequick.gradle.aapt.Aapt
import net.wequick.gradle.aapt.SymbolParser
import net.wequick.gradle.transform.StripAarTransform
import net.wequick.gradle.util.JNIUtils
import net.wequick.gradle.util.ZipUtils
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.DependencySet
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.file.FileTree
import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency
import org.gradle.api.tasks.compile.JavaCompile

Expand Down Expand Up @@ -941,10 +944,16 @@ class AppPlugin extends BundlePlugin {
aaptTask.doLast { ProcessAndroidResources it ->
// Unpack resources.ap_
File apFile = it.packageOutputFile
FileTree apFiles = project.zipTree(apFile)
File unzipApDir = new File(apFile.parentFile, 'ap_unzip')
unzipApDir.delete()
project.copy {
from project.zipTree(apFile)
from apFiles
into unzipApDir

include 'AndroidManifest.xml'
include 'resources.arsc'
include 'res/**/*'
}

// Modify assets
Expand All @@ -955,13 +964,15 @@ class AppPlugin extends BundlePlugin {
File rJavaFile = new File(sourceOutputDir, "${small.packagePath}/R.java")
def rev = android.buildToolsRevision
int noResourcesFlag = 0
def filteredResources = new HashSet()
def updatedResources = new HashSet()
Aapt aapt = new Aapt(unzipApDir, rJavaFile, symbolFile, rev)
if (small.retainedTypes != null && small.retainedTypes.size() > 0) {
aapt.filterResources(small.retainedTypes)
aapt.filterResources(small.retainedTypes, filteredResources)
Log.success "[${project.name}] split library res files..."

aapt.filterPackage(small.retainedTypes, small.packageId, small.idMaps,
small.retainedStyleables)
small.retainedStyleables, updatedResources)

Log.success "[${project.name}] slice asset package and reset package id..."

Expand Down Expand Up @@ -997,11 +1008,11 @@ class AppPlugin extends BundlePlugin {
Log.success "[${project.name}] split library R.java files..."
} else {
noResourcesFlag = 1
if (aapt.deleteResourcesDir()) {
if (aapt.deleteResourcesDir(filteredResources)) {
Log.success "[${project.name}] remove resources dir..."
}

if (aapt.deletePackage()) {
if (aapt.deletePackage(filteredResources)) {
Log.success "[${project.name}] remove resources.arsc..."
}

Expand All @@ -1012,16 +1023,33 @@ class AppPlugin extends BundlePlugin {

int abiFlag = getABIFlag()
int flags = (abiFlag << 1) | noResourcesFlag
if (aapt.writeSmallFlags(flags)) {
if (aapt.writeSmallFlags(flags, updatedResources)) {
Log.success "[${project.name}] add flags: ${Integer.toBinaryString(flags)}..."
}

// Repack resources.ap_
apFile.delete()
project.ant.zip(baseDir: unzipApDir, destFile: apFile)
String aaptExe = small.aapt.buildTools.getPath(BuildToolInfo.PathId.AAPT)

// Delete filtered entries.
// Cause there is no `aapt update' command supported, so for the updated resources
// we also delete first and run `aapt add' later.
filteredResources.addAll(updatedResources)
ZipUtils.with(apFile).deleteAll(filteredResources)

// Re-add updated entries.
// $ aapt add resources.ap_ file1 file2 ...
project.exec {
executable aaptExe
workingDir unzipApDir
args 'add', apFile.path
args updatedResources

// store the output instead of printing to the console
standardOutput = new ByteArrayOutputStream()
}
}
}


/**
* Hook javac task to split libraries' R.class
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package net.wequick.gradle

import com.android.build.gradle.api.BaseVariant
import com.android.sdklib.BuildToolInfo
import net.wequick.gradle.aapt.Aapt
import org.gradle.api.Project
import org.gradle.api.tasks.Copy
Expand Down Expand Up @@ -73,13 +74,7 @@ class AssetPlugin extends BundlePlugin {
// Generate AndroidManifest.xml
Aapt aapt = new Aapt(destDir, null, null, android.buildToolsRevision)
def aaptTask = project.processReleaseResources
def aaptExe
aaptTask.buildTools.mPaths.each { k, v ->
if ((String) k == 'AAPT') { // k.class = `com.android.sdklib.BuildToolInfo$PathId'
aaptExe = v
return
}
}
def aaptExe = aaptTask.buildTools.getPath(BuildToolInfo.PathId.AAPT)
def cf = android.defaultConfig
def baseAsset = new File(android.getSdkDirectory(),
"platforms/android-${cf.targetSdkVersion.getApiLevel()}/android.jar")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import org.gradle.api.Project
public class Aapt {

public static final int ID_DELETED = -1
public static final String FILE_ARSC = 'resources.arsc'
public static final String FILE_MANIFEST = 'AndroidManifest.xml'

private File mAssetDir
private File mJavaFile
Expand All @@ -43,23 +45,29 @@ public class Aapt {
* @param pp new package id
* @param idMaps
*/
void filterPackage(List retainedTypes, int pp, Map idMaps, List retainedStyleables) {
File arscFile = new File(mAssetDir, 'resources.arsc')
void filterPackage(List retainedTypes, int pp, Map idMaps, List retainedStyleables,
Set outUpdatedResources) {
File arscFile = new File(mAssetDir, FILE_ARSC)
def arscEditor = new ArscEditor(arscFile, mToolsRevision)

// Filter R.txt
if (mSymbolFile != null) filterRtext(mSymbolFile, retainedTypes, retainedStyleables)
// Filter resources.arsc
arscEditor.slice(pp, idMaps, retainedTypes)
outUpdatedResources.add(FILE_ARSC)

resetAllXmlPackageId(mAssetDir, pp, idMaps)
resetAllXmlPackageId(mAssetDir, pp, idMaps, outUpdatedResources)
}

def writeSmallFlags(int flags) {
def writeSmallFlags(int flags, Set outUpdatedResources) {
if (flags == 0) return false

def e = new AXmlEditor(new File(mAssetDir, 'AndroidManifest.xml'))
return e.setSmallFlags(flags)
def e = new AXmlEditor(new File(mAssetDir, FILE_MANIFEST))
if (e.setSmallFlags(flags)) {
outUpdatedResources.add(FILE_MANIFEST)
return true
}
return false
}

/**
Expand All @@ -69,20 +77,21 @@ public class Aapt {
* @param idMaps
*/
void resetPackage(int pp, String ppStr, Map idMaps) {
File arscFile = new File(mAssetDir, 'resources.arsc')
File arscFile = new File(mAssetDir, FILE_ARSC)
def arscEditor = new ArscEditor(arscFile, null)

// Modify R.java
resetRjava(mJavaFile, ppStr)
// Modify resources.arsc
arscEditor.reset(pp, idMaps)

resetAllXmlPackageId(mAssetDir, pp, idMaps)
resetAllXmlPackageId(mAssetDir, pp, idMaps, null)
}

boolean deletePackage() {
File arscFile = new File(mAssetDir, 'resources.arsc')
boolean deletePackage(Set outFilteredResources) {
File arscFile = new File(mAssetDir, FILE_ARSC)
if (arscFile.exists()) {
outFilteredResources.add(FILE_ARSC)
return arscFile.delete()
}
return false
Expand All @@ -101,7 +110,7 @@ public class Aapt {

void manifest(Project project, Map options) {
// Create source file
File tempManifest = new File(mAssetDir, 'AndroidManifest.xml')
File tempManifest = new File(mAssetDir, FILE_MANIFEST)
tempManifest.write("""<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="${options.packageName}"
android:versionName="${options.versionName}"
Expand All @@ -126,12 +135,15 @@ public class Aapt {
* Filter resources with specific types
* @param retainedTypes
*/
void filterResources(List retainedTypes) {
void filterResources(List retainedTypes, Set outFilteredResources) {
def resDir = new File(mAssetDir, 'res')
resDir.listFiles().each { typeDir ->
def type = retainedTypes.find { typeDir.name.startsWith(it.name) }
if (type == null) {
// Split whole type
typeDir.listFiles().each {
outFilteredResources.add("res/$typeDir.name/$it.name" as String)
}
typeDir.deleteDir()
return
}
Expand All @@ -141,6 +153,7 @@ public class Aapt {
def entry = type.entries.find { entryFile.name.startsWith("${it.name}.") }
if (entry == null) {
// Split specify entry
outFilteredResources.add("res/$typeDir.name/$entryFile.name" as String)
entryFile.delete()
retainedEntryCount--
}
Expand All @@ -152,20 +165,29 @@ public class Aapt {
}
}

boolean deleteResourcesDir() {
boolean deleteResourcesDir(Set outFilters) {
def resDir = new File(mAssetDir, 'res')
if (resDir.exists()) {
resDir.listFiles().each { dir ->
dir.listFiles().each { file ->
outFilters.add("res/$dir.name/$file.name" as String)
}
}
return resDir.deleteDir()
}
return false
}

/** Reset package id for *.xml */
private void resetAllXmlPackageId(File dir, int pp, Map idMaps) {
private static void resetAllXmlPackageId(File dir, int pp, Map idMaps, Set outUpdatedResources) {
int len = dir.canonicalPath.length() + 1 // bypass '/'
dir.eachFileRecurse(FileType.FILES) { file ->
if (file.name.endsWith('.xml')) {
def editor = new AXmlEditor(file)
editor.setPackageId(pp, idMaps)
if (outUpdatedResources != null) {
outUpdatedResources.add(file.canonicalPath.substring(len))
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2015-present wequick.net
*
* 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 net.wequick.gradle.util

import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream

/**
* Class to operate on zip file
*/
public final class ZipUtils {

private static final int BUFFER_SIZE = 1024

private byte[] buffer = new byte[BUFFER_SIZE]

private File file

public static ZipUtils with(File file) {
ZipUtils zu = new ZipUtils()
zu.file = file
return zu
}

/**
* Delete zip entries from a zip file (Copy entries excludes the deletes)
* @param file the zip file
* @param deletes the entries to delete
*/
public ZipUtils deleteAll(Set<String> deletes) {
ZipFile zf = new ZipFile(file)
File temp = new File(file.parentFile, 'temp.zip')
ZipOutputStream os = new ZipOutputStream(new FileOutputStream(temp))

def entries = zf.entries()
while (entries.hasMoreElements()) {
ZipEntry ze = entries.nextElement()
if (!deletes.contains(ze.name)) {
writeEntry(zf, os, ze)
}
}
zf.close()
os.flush()
os.close()

temp.renameTo(file)
return this
}

private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
throws IOException
{
ZipEntry ze2 = new ZipEntry(ze.getName());
ze2.setMethod(ze.getMethod());
ze2.setTime(ze.getTime());
ze2.setComment(ze.getComment());
ze2.setExtra(ze.getExtra());
if (ze.getMethod() == ZipEntry.STORED) {
ze2.setSize(ze.getSize());
ze2.setCrc(ze.getCrc());
}
os.putNextEntry(ze2);
writeBytes(zf, ze, os);
}

/**
* Writes all the bytes for a given entry to the specified output stream.
*/
private synchronized void writeBytes(ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
int n;

InputStream is = null;
try {
is = zf.getInputStream(ze);
long left = ze.getSize();

while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
os.write(buffer, 0, n);
left -= n;
}
} finally {
if (is != null) {
is.close();
}
}
}
}

0 comments on commit 535370d

Please sign in to comment.