aapt y aapt2: ID de recurso fijo y marca PUBLIC

07-14

prefacio

Todo el artículo tinkerse TinkerResourceIdTaskamplía en torno a los puntos de conocimiento en .

  1. aaptaapt2La diferencia entre y (entorno de ejecución y resultados de ejecución);
  2. idfijación de recursos ;
  3. PUBLICmarcar para ;

aaptEl entorno operativo es gradle:2.2.0ygradle-wrapper:3.4.1

aapt2El entorno operativo es gradle:3.3.2ygradle-wrapper:5.6.2

El proyecto android-aapt-sample es mi propia muestra experimental. aaptHay aapt2dos ramas, correspondientes a sus implementaciones.

Resumen de AAPT

Android Studio 3.0A partir de , el compilador que se compila comogoogle recurso está habilitado de forma predeterminada y su aparición brinda soporte para la compilación incremental de recursos. Por supuesto, habrá algunos problemas durante el uso, podemos cerrarlo configurando android.enableAapt2=false en gradle.properties .aapt2aapt2aapt2

recurso

AndroidNacido para hacer mucho trabajo para ser compatible con una variedad de dispositivos diferentes, como tamaño de pantalla, internacionalización, teclado, densidad de píxeles, etc., podemos usar recursos específicos para varios escenarios específicos para hacer compatibilidad en lugar de cambiar una línea de código, asumiendo que hemos adaptado diferentes recursos para varios escenarios, ¿cómo podemos aplicar estos recursos rápidamente? EstaAndroid clase se nos proporciona , y se especifica un índice de recursos ( ), y luego solo necesitamos decirle al sistema que use el recurso correspondiente en diferentes escenarios comerciales. En cuanto a qué archivo específico en el recurso especificado, está determinado por The El sistema se determina de acuerdo con la configuración del desarrollador.Rid

En este escenario, suponiendo que damos un valor idde x, entonces cuando el negocio actual necesita usar este recurso, el estado del teléfono móvil es yel valor Con ( x,y), la ruta específica del archivo de recursos se puede ubicar rápidamente en un levantar la mesa Esta tabla es resources.arsc, se aaptcompila .

De hecho, los recursos binarios (como las imágenes) no necesitan compilarse, pero este comportamiento de "compilación" es para generar resources.arscy binarizar xml.resources.arscarchivos Cuando llamemos a related , encontraremos el archivo correspondiente en esta tabla y lo leeremos.xmlAssetManagerRid

GradleEn el proceso de compilación de recursos, estos comandos aapt2 se llaman y los parámetros pasados ​​también se presentan en este documento, pero los detalles de la llamada están ocultos para los desarrolladores.

aapt2Se divide principalmente en dos pasos, uno se llama compiley el otro se llama link.

Cree un proyecto vacío: solo escriba dos xml, a saber AndroidManifest.xmly activity_main.xml.

Compilar

 mkdir compiled
 aapt2 compile src/main/res/layout/activity_main.xml -o compiled/

En compiledla carpeta, layout_activity_main.xml.flatse genera este archivo, es aapt2específico de , aaptno hay ( aaptel archivo de origen se copia) y aapt2se puede usar para la compilación incremental. Si tenemos muchos archivos, necesitamos llamar compileen De hecho –dir, el parámetro también se puede usar aquí, pero este parámetro no tiene efecto de compilación incremental. Es decir, al pasar un directorio completo, AAPT2todos los archivos del directorio se volverán a compilar incluso si solo ha cambiado un recurso.

Enlace

linkLa carga de trabajo compilees un , la entrada aquí es la suma de múltiples flatarchivos y AndroidManifest.xmlrecursos externos, y la salida es la suma de solo apkrecursos R.java. El comando es el siguiente:

aapt2 link -o out.apk \
-I $ANDROID_HOME/platforms/android-28/android.jar \
compiled/layout_activity_main.xml.flat \
--java src/main/java \
--manifest src/main/AndroidManifest.xml
  • La segunda línea -Ison importlos recursos externos, aquí hay principalmente algunos atributos definidos bajo androidel espacio de nombres , que generalmente usamos @android:xxxse colocan jaren este, de hecho, también podemos proporcionar nuestros propios recursos para que otros los vinculen;
  • La tercera línea es el flatarchivo , si hay más de uno, puede unirlos más tarde;
  • La cuarta línea es el directorio R.javagenerado ;
  • La quinta línea es para especificar AndroidManifest.xml;

LinkCuando se completa, se genera un archivo y out.apkse incluye en formato . Solo los archivos de recursos pueden usar el sufijo .R.javaout.apkresources.arsc.ap_

Ver recursos compilados

Además de usar Android Studiopara ver resources.arsc, también puede usar directamente aapt2 dump apkla información para ver IDel estado y el estado de los recursos:

aapt2 dump out.apk

Los resultados de salida son los siguientes:

Binary APK
Package name=com.geminiwen.hello id=7f
  type layout id=01 entryCount=1
    resource 0x7f010000 layout/activity_main
      () (file) res/layout/activity_main.xml type=XML

Se puede ver layout/activity_mainque el correspondiente IDes 0x7f010000.

El intercambio de recursos

android.jarEs solo un stub para compilar, y cuando se ejecuta realmente, Android OSproporciona una biblioteca de tiempo de ejecución ( framework.jar). android.jarMuy parecido a one apk, excepto que existe como classun archivo , y luego un AndroidManifest.xmly resources.arsc. Esto quiere decir que también podemos usarlo aapt2 dump, ejecuta el siguiente comando:

aapt2 dump $ANDROID_HOME/platforms/android-28/android.jar > test.out

Obtiene una gran cantidad de salida similar a la siguiente:

resource 0x010a0000 anim/fade_in PUBLIC
      () (file) res/anim/fade_in.xml type=XML
    resource 0x010a0001 anim/fade_out PUBLIC
      () (file) res/anim/fade_out.xml type=XML
    resource 0x010a0002 anim/slide_in_left PUBLIC
      () (file) res/anim/slide_in_left.xml type=XML
    resource 0x010a0003 anim/slide_out_right PUBLIC
      () (file) res/anim/slide_out_right.xml type=XML

Tiene algunos PUBLICcampos más. Si el recurso en un apkarchivo está marcado con esta etiqueta, puede ser apkreferenciado por otros. El método de referencia es @包名:类型/名字, por ejemplo: @android:color/red.

Si queremos proporcionar nuestros recursos, primero marque nuestros recursos PUBLICcon y luego xmlcite el nombre de su paquete en , por ejemplo: @com.gemini.app:color/redpuede referirse a su color/reddefinido , si no especifica un nombre de paquete, el valor predeterminado es usted mismo.

En cuanto a AAPT2cómo generarlo PUBLIC, los que estén interesados ​​pueden seguir leyendo este artículo.

descripción general de ids.xml

ids.xml: proporciona un recurso único para los recursos relacionados de la aplicación id. ides obtener xmllos parámetros requeridos por el objeto en, es decir, Object = findViewById(R.id.id_name);en id_name.

android.R.idEstos valores se pueden referenciar en el código .
Si IDids.xml se define en , se puede definir de la siguiente manera en , de lo contrario .layout@id/price_edit@+id/price_edit

ventaja

  1. La asignación de nombres es conveniente, podemos nombrar algunos controles específicos primero y citarlos directamente al usarlos id, lo que ahorra un proceso de asignación de nombres.
  2. Optimice la eficiencia de la compilación:
    • idDespués de agregarlo, se R.javagenerará en;
    • Con ids.xmlla administración unificada, la compilación única se puede usar varias veces.
      Sin embargo "@+id/btn_next", el formulario utilizado se volverá a detectar cada vez que se guarde el archivo (Ctrl+s) . Si existe, no se generará. Si no existe, necesita para ser agregado Por lo tanto, se reduce la eficiencia de compilación.后R.javaidid

ids.xmlcontenido del documento:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="forecast_list" type="id"/>
<!--    <item name="app_name" type="string" />-->
</resources>

Algunas personas pueden tener curiosidad de que hay una línea de código comentada arriba. Si abre el comentario, encontrará que la compilación informará un error:

Execution failed for task ':app:mergeDebugResources'.
> [string/app_name] /Users/tanzx/AndroidStudioProjects/AaptDemo/app/src/main/res/values/strings.xml	[string/app_name] /Users/tanzx/AndroidStudioProjects/AaptDemo/app/src/main/res/values/ids.xml: Error: Duplicate resources

Debido a que app_nameel recurso para ha valuesido declarado en .

descripción general de public.xml

Instrucciones oficiales relacionadas Sitio web oficial: Seleccione los recursos que se harán públicos .

Traducción original: Todos los recursos de la biblioteca son públicos por defecto. Para hacer que todos los recursos sean implícitamente privados, debe definir al menos una propiedad específica como pública. Los recursos incluyen todos los archivos, como imágenes, en res/el directorio . Para evitar que los usuarios de su biblioteca accedan a recursos exclusivamente internos, debe utilizar este mecanismo de identificación privada automática declarando uno o más recursos públicos. Alternativamente, puede hacer que todos los recursos sean privados agregando una <public />etiqueta , que no hace que ningún recurso sea público, pero hace que todo (todos los recursos) sea privado.

Al hacer que la propiedad sea implícitamente privada, no solo puede evitar que los usuarios de su biblioteca obtengan sugerencias de finalización de código de los recursos internos de la biblioteca, sino que también puede cambiar el nombre o eliminar recursos privados sin romper los clientes de su biblioteca. El sistema filtra los recursos privados de la finalización del código y Lint te avisará si intentas hacer referencia a recursos privados.

Al crear la biblioteca, el complemento Gradle de Android toma la definición de recurso público y la extrae en public.txtun archivo , que luego se empaqueta en un archivo AAR.

El resultado real de la prueba es solo que el código está automáticamente incompleto si no regresa, y el compilador informa rojo. Si está marcado lint, ¡no hay advertencias para la compilación~!

Ahora, la mayoría de las explicaciones son: el archivo RES/value/public.xml se usa para IDasignar a Androidlos recursos.

stackoverfloew:¿Cuál es el uso del archivo res/values/public.xml en Android?

public.xmlcontenido del documento:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public name="forecast_list" id="0x7f040001" type="id" />
    <public name="app_name" id="0x7f070002" type="string" />
    <public name="string3" id="0x7f070003" type="string" />
</resources>

ID de recurso fijo

La identificación del recurso fijo es extremadamente importante en la revisión y el complemento. En hotfix, al construir patch, es necesario mantener patchlos recursos del paquete consistentes idcon los recursos del paquete de referencia id; en el complemento, si el complemento necesita hacer referencia a los recursos del host, los recursos del host necesitan ser idarreglados Por lo tanto, los recursos idson fijos en estos dos Este escenario es particularmente importante.

En Android Gradle Plugin 3.0.0, está habilitado de forma predeterminada aapt2, y el método de reparación de recursos original de aapt public.xmltambién será inválido. Es necesario encontrar un nuevo método de reparación de recursos en lugar de simplemente deshabilitar aapt2. Por lo tanto, este artículo analiza aapt和aapt2cómo reparar los recursos respectivamente id.

aaptFijo id_

Configuración del entorno del proyecto (PD: se quejan de que aapt ha sido reemplazado por aapt2, casi no hay información relacionada con aapt, ¡y el entorno es demasiado laborioso para construir ~!)

com.android.tools.build:gradle:2.2.0

distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip

compileSdkVersion 24

buildToolsVersion '24.0.0'

Primero genere el archivo correspondiente valuedebajo del archivo de acuerdo con el contenido de la ids.xmlsuma anterior y el nombre del archivo.public.xml

Compilar el resultado directamente

inserte la descripción de la imagen aquí

Al compilar directamente R文件el contenido, podemos ver que los recursos que queremos configurar idno se generan como esperábamos.

Copie public.xmlel archivo en build/intermediates/res/mergedel directorio correspondiente

afterEvaluate {
    
    
    for (variant in android.applicationVariants) {
    
    
        def scope = variant.getVariantData().getScope()
        String mergeTaskName = scope.getMergeResourcesTask().name
        def mergeTask = tasks.getByName(mergeTaskName)
        mergeTask.doLast {
    
    
            copy {
    
    
                int i=0
                from(android.sourceSets.main.res.srcDirs) {
    
    
                    include 'values/public.xml'
                    rename 'public.xml', (i++ == 0? "public.xml": "public_${i}.xml")
                }
                into(mergeTask.outputDir)
            }
        }
    }
}

inserte la descripción de la imagen aquí

Esta vez podemos ver directamente que los recursos idse generan de acuerdo a nuestras necesidades.

¿Por qué es esto?

  1. android gradleLas siguientes versiones del complemento 1.3se pueden public.xmlcolocar directamente en el resdirectorio del código fuente para participar en la compilación;

  2. android gradleLas versiones de complementos se ignoran al 1.3+realizar tareas , por lo que no hay contenido relevante en el directorio debajo del directorio completado . Por lo tanto, debe insertarse en el directorio bajo el directorio completo a través del script en tiempo de compilación . La razón por la que esto es factible es que es compatible por sí mismo , pero el complemento filtra los recursos cuando los preprocesa .mergeResourcepublic.xmlmergebuildrespublic.xmlpublic.xmlmergebuildresaaptpublic.xmlgradle(merge)public.xml

aapt2Fijo id_

Después de aapt2compilar (compilar los archivos de recursos en formato binario), mergelos recursos encontrados se compilaron previamente y flatse generaron los archivos. En este momento, public.xmlcopiar los archivos a este directorio provocará errores de compilación.

Pero en la fase de vinculaciónaapt2 de , observamos las opciones de vinculación relevantes :

opciones ilustrar
--emit-ids path Genera un archivo en la ruta dada que contiene una lista de nombres de tipos de recursos y sus asignaciones de ID. Funciona bien --stable-idscon .
--stable-ids outputfilename.ext Utilice un archivo --emit-idsgenerado que contenga los nombres de los tipos de recursos y una lista de ID asignados a ellos. Esta opción mantiene estables los ID asignados, incluso si elimina recursos o agrega otros nuevos durante la vinculación.

El descubrimiento --emit-idsy --stable-idsla colocación de comandos pueden lograr idla solución.

android {
    
    
  aaptOptions {
    
    
        File publicTxtFile = project.rootProject.file('public.txt')
        //public文件存在,则应用,不存在则生成
        if (publicTxtFile.exists()) {
    
    
            project.logger.error "${publicTxtFile} exists, apply it."
            //aapt2添加--stable-ids参数应用
            aaptOptions.additionalParameters("--stable-ids", "${publicTxtFile}")
        } else {
    
    
            project.logger.error "${publicTxtFile} not exists, generate it."
            //aapt2添加--emit-ids参数生成
            aaptOptions.additionalParameters("--emit-ids", "${publicTxtFile}")
        }
    }
}
  1. Compile por primera vez, primero --emit-idsgenerando en el directorio raíz del proyecto public.txt;
  2. Luego cambie public.txtel interior correspondiente ida lo que desea arreglar id;
  3. Vuelva a compilar y corrija los recursos por --stable-idsy bajo el directorio raíz ;public.txtid

--emit-idscompilar resultado

inserte la descripción de la imagen aquí

Modifique public.txtel contenido del archivo y vuelva a compilar

inserte la descripción de la imagen aquí

R.txt · público.txt

El producto intermedio que normalmente empaquetamos y generamos es que build/intermediates/symbols/debug/R.txtnecesitamos convertirlo a public.txt.

R.txtFormato ( int type name id) o ( int[] styleable name {id,id,xxxx})

public.txtformato ( applicationId:type/name = id)

R.txtPor lo tanto, el tipo en el archivo debe filtrarse durante el proceso de conversión styleable.

android {
    
    
    aaptOptions {
    
    
        File rFile = project.rootProject.file('R.txt')
        List<String> sortedLines = new ArrayList<>()
        // 一行一行读取
        rFile.eachLine {
    
    line ->
            //rLines.add(line)
            String[] test = line.split(" ")
            String type = test[1]
            String name = test[2]
            String idValue = test[3]
            if ("styleable" != type) {
    
    
                sortedLines.add("${applicationId}:${type}/${name} = ${idValue}")
            }
        }
        Collections.sort(sortedLines)
        File publicTxtFile = project.rootProject.file('public.txt')
        if (!publicTxtFile.exists()) {
    
    
            publicTxtFile.createNewFile()
            sortedLines?.each {
    
    
                publicTxtFile.append("${it}\n")
            }
        }
    }
}

Marca PÚBLICA

En AAPT概述esta parte dijimos que si los recursos en un apkarchivo están PUBLICmarcados, pueden ser referenciados apkpor @包名:类型/名字, por ejemplo: @android:color/red.

Después de leer las dos partes anteriores de " aaptReparación iden curso" a " aapt2Reparación en curso ", sabemos que los métodos de reparación y reparación son diferentes.idaaptaapt2id

De hecho, si usamos aapt2 dump build/intermediates/res/resources-debug.ap_comandos para ver información relevante sobre los recursos generados.

aaptLa información del recurso se fija haciendo una public.xmletiqueta con:idPUBLIC

inserte la descripción de la imagen aquí

aapt22. El uso del método de fijación anterior no está marcado iden la figura a continuación .PUBLIC

La razón todavía es causada por la diferencia entre aapty , y no es igual a , si desea agregar una marca en , todavía tiene que encontrar otra forma.aapt2aapt2public.txtaaptpublic.xmlaapt2PUBLIC

Mirando hacia atrás

revisar

  1. aaptPara idfijación y PUBLICfijación de precios, public.xmlcopie a ${mergeResourceTask.outputDir};
  2. aapt2Por el contrario aapt, se realiza la optimización de la compilación incremental. AAPT2analizará el archivo y generará un archivo binario .flatintermedio .

inserte la descripción de la imagen aquí

pensar

¿Puedo usarme aapt2para public.xmlcompilar como public.arsc.flat, y copiarlo como aaptun ${mergeResourceTask.outputDir};

Manos

android {
    
    
    //将public.txt转化为public.xml,并对public.xml进行aapt2的编译将结果复制到${ergeResourceTask.outputDir}
  //下面大部分代码是copy自tinker的源码
  applicationVariants.all {
    
     def variant ->
      def mergeResourceTask = project.tasks.findByName("merge${variant.getName().capitalize()}Resources")
      if (mergeResourceTask) {
    
    
          mergeResourceTask.doLast {
    
    
              //目标转换文件,注意public.xml上级目录必须带values目录,否则aapt2执行时会报非法文件路径
              File publicXmlFile = new File(project.buildDir, "intermediates/res/public/${variant.getDirName()}/values/public.xml")
              //转换public.txt文件为publicXml文件,最后一个参数true标识固定资源id
              convertPublicTxtToPublicXml(project.rootProject.file('public.txt'), publicXmlFile, false)
              def variantData = variant.getMetaClass().getProperty(variant, 'variantData')
              def variantScope = variantData.getScope()
              def globalScope = variantScope.getGlobalScope()
              def androidBuilder = globalScope.getAndroidBuilder()
              def targetInfo = androidBuilder.getTargetInfo()
              def mBuildToolInfo = targetInfo.getBuildTools()
              Map<BuildToolInfo.PathId, String> mPaths = mBuildToolInfo.getMetaClass().getProperty(mBuildToolInfo, "mPaths") as Map<BuildToolInfo.PathId, String>
                //通过aapt2 compile命令自己生成public.arsc.flat并输出到${mergeResourceTask.outputDir}
              project.exec(new Action<ExecSpec>() {
    
    
                  @Override
                  void execute(ExecSpec execSpec) {
    
    
                      execSpec.executable "${mPaths.get(BuildToolInfo.PathId.AAPT2)}"
                      execSpec.args("compile")
                      execSpec.args("--legacy")
                      execSpec.args("-o")
                      execSpec.args("${mergeResourceTask.outputDir}")
                      execSpec.args("${publicXmlFile}")
                  }
              })
          }
      }
  }
}

Convierte public.txtun archivo en public.xmlun archivo.

  • public.txtHay styleableun recurso de tipo en , public.xmlpero no existe en , por lo que si se encuentra un tipo durante la conversión styleable, debe ignorarse;
  • vectorSi hay recursos internos para recursos de gráficos vectoriales, también deben ignorarse. En aapt2, su nombre $comienza con , seguido del nombre del recurso principal, seguido del índice de incremento de número __. Estos recursos no se pueden referenciar externamente y solo necesitan para ser arreglado idNo hay necesidad de agregar PUBLICla etiqueta, y $el símbolo public.xmles ilegal en , así que simplemente ignórelo;
  • Dado que aapt2hay una forma fija de recursos id, se puede perder directamente durante el proceso de conversión id, y una declaración simple es suficiente (PD: aquí, withIdsi necesita ser reparado o no, se controla mediante parámetros id);
  • aapt2El directorio principal del archivo compilado public.xmldebe ser valuesuna carpeta; de lo contrario, se informará una ruta ilegal durante el proceso de compilación;
/**
 * 转换publicTxt为publicXml
 * copy tinker:com.tencent.tinker.build.gradle.task.TinkerResourceIdTask#convertPublicTxtToPublicXml
 */
@SuppressWarnings("GrMethodMayBeStatic")
void convertPublicTxtToPublicXml(File publicTxtFile, File publicXmlFile, boolean withId) {
    
    
    if (publicTxtFile == null || publicXmlFile == null || !publicTxtFile.exists() || !publicTxtFile.isFile()) {
    
    
        throw new GradleException("publicTxtFile ${publicTxtFile} is not exist or not a file")
    }

    GFileUtils.deleteQuietly(publicXmlFile)
    GFileUtils.mkdirs(publicXmlFile.getParentFile())
    GFileUtils.touch(publicXmlFile)

    project.logger.info "convert publicTxtFile ${publicTxtFile} to publicXmlFile ${publicXmlFile}"

    publicXmlFile.append("<!-- AUTO-GENERATED FILE.  DO NOT MODIFY -->")
    publicXmlFile.append("\n")
    publicXmlFile.append("<resources>")
    publicXmlFile.append("\n")
    Pattern linePattern = Pattern.compile(".*?:(.*?)/(.*?)\\s+=\\s+(.*?)")

    publicTxtFile.eachLine {
    
    def line ->
        Matcher matcher = linePattern.matcher(line)
        if (matcher.matches() && matcher.groupCount() == 3) {
    
    
            String resType = matcher.group(1)
            String resName = matcher.group(2)
            if (resName.startsWith('$')) {
    
    
                project.logger.info "ignore to public res ${resName} because it's a nested resource"
            } else if (resType.equalsIgnoreCase("styleable")) {
    
    
                project.logger.info "ignore to public res ${resName} because it's a styleable resource"
            } else {
    
    
                if (withId) {
    
    
                    publicXmlFile.append("\t<public type=\"${resType}\" name=\"${resName}\" id=\"${matcher.group(3)}\" />\n")
                } else {
    
    
                    publicXmlFile.append("\t<public type=\"${resType}\" name=\"${resName}\" />\n")
                }

            }
        }
    }
    publicXmlFile.append("</resources>")
}

A través del proceso anterior de pensamiento y práctica, no solo resolvimos el problema de aapt2marcar PUBLIC, sino que también encontramos un nuevo método de fijación aapt2.id

Errores que se pueden encontrar:

no signature of method com.android.build.gradle.internal.variant.applicationvariantdata.getscope() is applicable for argument types: () values: []

La solución es modificar gradlela versión a gradle:3.3.2y gradle-wrapper:5.6.2, después de todo, tinkerno es compatible con la última versión de gradle.

referencia:

Github: juguetear

uso de public.xml de Android

Notas de Android-Gradle

La identificación del recurso para la adaptación aapt2 es fija

El artículo se cuenta aquí, si tiene otras necesidades para comunicarse, ¡puede dejar un mensaje ~! ~!

Si quieres leer más artículos del autor, puedes consultar mi blog personal y cuenta pública:
Revitalizar la Ciudad del Libro

Supongo que te gusta

Origin blog.csdn.net/stven_king/article/details/118736228
Recomendado
Clasificación