aapt および aapt2 -- リソース ID が修正され、PUBLIC マークが付けられました

07-14

序文

記事全体はtinkerTinkerResourceIdTaskのナレッジ ポイントを中心に展開されています。

  1. aaptと (実行環境と実行結果)aapt2の違い。
  2. リソースidの固定;
  3. PUBLICマークします。

aapt動作環境はgradle:2.2.0gradle-wrapper:3.4.1

aapt2動作環境はgradle:3.3.2gradle-wrapper:5.6.2

android-aapt-sampleプロジェクトは、私自身の実験的なサンプルです。実装に対応する 2 つのブランチがありaaptます。aapt2

AAPT の概要

Android Studio 3.0からgoogleリソースaapt2としてaapt2、その出現により、リソースのインクリメンタル コンパイルがサポートされます。もちろん、使用中にいくつかの問題が発生します。gradle.propertiesandroid.enableAapt2=false を構成することで閉じることができますaapt2

リソース

Android画面サイズ、国際化、キーボード、ピクセル密度など、さまざまなデバイスと互換性を持たせるために多くの作業を行うために生まれました。さまざまなシナリオにさまざまなリソースを適応させたと仮定すると、これらのリソースをすばやく適用するにはどうすればよいでしょうか? このAndroidクラスは私たちのために提供され、リソース インデックス ( ) が指定されます。次に、対応するリソースをさまざまなビジネス シナリオで使用するようにシステムに指示するだけで済みます。指定されたリソース内のどの特定のファイルについては、によって決定されます。システムは、開発者の構成に応じて決定されます。Rid

このシナリオでは、 の値idx、現在のビジネスでこのリソースを使用する必要がある場合、携帯電話のステータスはyの値 ( x,y) を使用すると、リソース ファイルの特定のパスをすばやく見つけることができます。テーブルアップ。この表はresources.arsc、からaaptまとめた。

実際には、バイナリ リソース (画像など) をコンパイルする必要はありませんが、この「コンパイル」の動作はresources.arscファイルを生成してバイナリ化することでありxmlrelatedを呼び出すと、このテーブルで対応するファイルを見つけて読み取ります。resources.arscxmlAssetManagerRid

Gradleリソースをコンパイルする過程で、これらのaapt2 コマンドが呼び出され、渡されるパラメーターもこのドキュメントで紹介されていますが、呼び出しの詳細は開発者には隠されています。

aapt2主に2段階に分かれており、1つは と呼ばれcompile、もう1つは と呼ばれますlink

空のプロジェクトを作成します。xmlの2 つだけを記述しますAndroidManifest.xmlactivity_main.xml

コンパイル

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

compiledフォルダ内に、layout_activity_main.xml.flatこのファイルが生成され、aapt2固有、aapt存在せず (aaptソース ファイルがコピーされます)、aapt2インクリメンタル コンパイルに使用できます。ファイルがたくさんある場合は、順番compileに を. 実際–dir、ここでも パラメーターを使用できますが、このパラメーターはインクリメンタル コンパイルの効果はありません。つまり、ディレクトリ全体を渡すと、AAPT2リソースが 1 つだけ変更された場合でも、ディレクトリ内のすべてのファイルが再コンパイルされます。

リンク

linkワークロードcompileは、ここでの入力は複数のflatファイルとAndroidManifest.xml外部リソースの合計であり、出力はapkリソースR.javaコマンドは次のとおりです。

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
  • 2 行目-Iimport外部リソースです。主にandroid名前空間。通常使用するものはこの中に@android:xxx配置されますjar。実際、他の人がリンクできるように独自のリソースを提供することもできます。
  • 3 行目は入力flatファイル。複数ある場合は、後でスプライスできます。
  • 4 行目はR.java生成された。
  • 5行目は指定することですAndroidManifest.xml;

Link完了すると、ファイルが生成されout.apk含まれます接尾辞 を使用できるのは、リソース ファイルだけですR.javaout.apkresources.arsc.ap_

コンパイルされたリソースを表示する

Android Studio使用して表示するだけでなくresources.arscaapt2 dump apk情報をID

aapt2 dump out.apk

出力結果は次のとおりです。

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

layout/activity_main対応してIDいるわかります0x7f010000

リソース共有

android.jarあくまでコンパイル用のスタブであり、実際に実行するとAndroid OSランタイムライブラリ(framework.jar)を提供します。android.jarとよく似ていますapkが、classファイル、その後AndroidManifest.xmlresources.arsc. aapt2 dumpこれは、次のコマンドを実行して、それを使用することもできることを意味します。

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

次のような多くの出力が得られます。

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

いくつかのPUBLICフィールドがあります.apkファイル,apk他のリソースから参照できます. 参照方法は です@包名:类型/名字.例: @android:color/red.

リソースを提供したい場合は、まずリソースPUBLICを で、xmlパッケージ名を で引用します。たとえば、定義済みの@com.gemini.app:color/redを参照できます。パッケージ名を指定しない場合、デフォルトは自分自身です。color/red

AAPT2生成方法についてはPUBLIC、興味のある方は引き続きこの記事をお読みください。

ids.xml の概要

ids.xml: アプリケーションの関連リソースに固有のリソースを提供しますidオブジェクトが必要とするパラメータをid取得することですxmlObject = findViewById(R.id.id_name);id_name

これらの値はコードでandroid.R.id参照できます。IDが で定義されている
場合は で、そうでない場合は で次のように定義できますids.xmllayout@id/price_edit@+id/price_edit

アドバンテージ

  1. 名前付けは便利です。最初に特定のコントロールに名前を付け、それらを使用するときにそれらを直接引用できますid。これにより、名前付けプロセスが節約されます。
  2. コンパイル効率を最適化します。
    • 追加するidと、次のようR.javaに生成されます。
    • 一元管理することでids.xml、1回の編集で複数回利用することができます.
      ただし"@+id/btn_next"、ファイルを保存するたびに利用されているフォームが再検出されます.存在する場合は生成されません.存在しない場合は生成が必要です(Ctrl+s.追加予定したがって、コンパイル効率が低下します。后R.javaidid

ids.xmlドキュメントの内容:

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

上記のコード行にコメントがあることに興味がある人もいるかもしれません. コメントを開くと、コンパイルでエラーが報告されることがわかります:

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

app_nameのリソースが で宣言されているためですvalue

public.xml の概要

公式関連の指示公式ウェブサイト: 公開するリソースを選択します

元の翻訳: ライブラリ内のすべてのリソースは、デフォルトで公開されています。すべてのリソースを暗黙的にプライベートにするには、少なくとも 1 つの特定のプロパティをパブリックとして定義する必要があります。リソースには、プロジェクトのres/ディレクトリ。ライブラリのユーザーが内部のみのリソースにアクセスできないようにするには、1 つ以上のパブリック リソースを宣言して、この自動プライベート識別メカニズムを使用する必要があります。または、空の<public />タグを。これにより、リソースは公開されず、すべて (すべてのリソース) が非公開になります。

プロパティを暗黙的に非公開にすることで、ライブラリのユーザーが内部ライブラリ リソースからコード補完候補を取得できないようにするだけでなく、ライブラリのクライアントを壊すことなく、非公開リソースの名前を変更または削除することもできます。システムはプライベート リソースをコード補完から除外します。プライベート リソースを参照しようとすると、Lint によって警告が表示されます。

ライブラリをビルドするとき、Android Gradle プラグインはパブリック リソース定義を取得してpublic.txtファイル、それを AAR ファイルにパッケージ化します。

実際のテスト結果は、コードが返されない場合、コードが自動的に不完全であるということだけであり、コンパイラは赤を報告します。チェックを入れるとlint、コンパイルの警告が出ません~!

現在、ほとんどの説明は次のとおりです。ファイルRES/value/public.xmlは、固定リソースをリソースにID割り当てるAndroid

stackoverflow: Android での res/values/public.xml ファイルの使用は何ですか?

public.xmlドキュメントの内容:

<?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

固定リソース ID は、ホットフィックスとプラグインで非常に重要です。ホットフィックスでは、ビルド時に、パッケージのリソースと参照パッケージのリソースの一貫性patchを保つ必要があります。プラグインでは、プラグインがホストのリソースを参照する必要がある場合、ホストのリソースこのシナリオは特に重要ですpatchidididid

ではAndroid Gradle Plugin 3.0.0、デフォルトで有効になっておりaapt2、aapt 独自のリソース固定方法もpublic.xml無効になるため、単純に無効にするのではなく、新しいリソース固定方法を見つける必要がありますaapt2. そのため、この記事でaapt和aapt2はそれぞれのリソース固定方法について説明しますid.

aapt固定id_

プロジェクトの環境設定(PS: aapt が aapt2 に置き換わったこと、aapt 関連の情報がほとんどないこと、環境を構築するのが面倒すぎることを訴える ~!)

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'

まず、上記valueの内容とファイル名に従い、ファイル配下に対応するファイルを生成します。ids.xmlpublic.xml

結果を直接コンパイルする

ここに画像の説明を挿入

コンテンツを直接コンパイルするとR文件、設定したいリソースがid期待どおりに生成されていないことがわかります。

public.xmlファイルをbuild/intermediates/res/merged対応するディレクトリにコピーします

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

ここに画像の説明を挿入

id今回は、必要に応じてリソースが生成されていることを直接確認できます。

どうしてこれなの?

  1. android gradle次のバージョンのプラグインは、ソース コード ディレクトリに1.3直接配置して、コンパイルに参加できます。public.xmlres

  2. android gradle1.3+タスクの実行時にプラグインのバージョンはmergeResource無視されるpublic.xmlため、merge完了したディレクトリのbuildresのディレクトリにpublic.xml関連するコンテンツはありません。そのため、コンパイル時にスクリプトを介して、完成したディレクトリの下のディレクトリpublic.xmlに挿入する必要がありますこれが実現可能である理由は、プラグイン自体がサポートされているためですが、プラグインはリソースの前処理時にリソースをフィルタリングします。mergebuildresaaptpublic.xmlgradle(merge)public.xml

aapt2固定id_

aapt2コンパイル(リソースファイルをバイナリ形式にコンパイル)後、merge見つかったリソースはプリコンパイルされ、flatファイルが生成されます.このとき、public.xmlファイルをこのディレクトリにコピーすると、コンパイルエラーが発生します.

しかし、 のリンク フェーズではaapt2関連するリンク オプションを確認します

オプション 例証する
--emit-ids path リソースタイプ名とその ID マッピングのリストを含むファイルを指定されたパスに生成します。--stable-idsうまく動作します。
--stable-ids outputfilename.ext リソース タイプの名前とそれらに割り当てられた ID のリストを含む、で--emit-ids生成された。このオプションは、リンク中にリソースを削除したり、新しいリソースを追加したりしても、割り当てられた ID を安定させます。

検出--emit-idsとコマンドのコロケーションにより、問題を--stable-ids解決できます。id

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. 最初に--emit-idsプロジェクトのルート ディレクトリに生成して、初めてコンパイルしますpublic.txt
  2. 次に、public.txt対応する内部をid修正したいものに変更しますid
  3. 再度コンパイルし、--stable-idsルート ディレクトリとその下のpublic.txtリソースを修正しますid

--emit-idsコンパイル結果

ここに画像の説明を挿入

public.txtファイルの内容を変更して再度コンパイルする

ここに画像の説明を挿入

R.txt转public.txt

通常、パッケージ化して生成する中間製品は、build/intermediates/symbols/debug/R.txtそれを に変換する必要がありますpublic.txt

R.txtフォーマット ( int type name id) または ( int[] styleable name {id,id,xxxx})

public.txtフォーマット ( applicationId:type/name = id)

R.txtしたがって、変換プロセス中にファイル内のタイプを除外する必要があります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")
            }
        }
    }
}

PUBLICマーク

この部分ではファイルAAPT概述内のリソースがマークされている場合、他のから述べましたapkPUBLICapk@包名:类型/名字@android:color/red

上記の「aapt修正id中」から「aapt2修正中」までのid2つの部分を読んだ後、修正と修正の方法が異なることaaptがわかりました。aapt2id

実際、aapt2 dump build/intermediates/res/resources-debug.ap_コマンドを使用して、生成されたリソースに関する関連情報を表示するとします。

aaptリソース情報は、次のタグをpublic.xml作成することで修正されます。idPUBLIC

ここに画像の説明を挿入

2. 上記のaapt2固定方法を使用することは、id下の図ではPUBLICマークされていません。

その理由は依然としてaaptaapt2の違いによるものであり、aapt2は とpublic.txt等しくありませんaaptにマークを追加したいpublic.xml場合は、別の方法を見つける必要があります。aapt2PUBLIC

思い返す

レビュー

  1. aaptリソースのid固定とPUBLIC価格設定については、public.xmlにコピーしてください${mergeResourceTask.outputDir}
  2. aapt2対照的にaapt、増分コンパイルの最適化が行われます。AAPT2ファイルを解析し、拡張子が . の.flat中間。

ここに画像の説明を挿入

考え

aapt2自分自身を使用しpublic.xmlて としてコンパイルしpublic.arsc.flat、それをaapt; のますか${mergeResourceTask.outputDir}?

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}")
                  }
              })
          }
      }
  }
}

public.txtファイルをpublic.xmlファイルに変換します。

  • public.txtstyleableにはタイプ リソースがありますpublic.xmlが、 には存在しないため、変換中にタイプが検出された場合はstyleable無視する必要があります。
  • vectorベクトル グラフィック リソース用の内部リソースがある場合は、それらも無視する必要があります. では、aapt2その名前は$で始まり、その後にメイン リソースの名前が続き、その後に __ 番号のインクリメント インデックスが続きます. これらのリソースは外部から参照することはできず、必要なのは修正予定.タグをid追加する必要はなく、シンボルはでは違法であるため、無視してください。PUBLIC$public.xml
  • aapt2リソースには固定された方法があるためid、変換プロセス中に直接失われる可能性がありid、簡単な宣言で十分です (PS: ここでは、withId固定する必要があるかどうかはパラメーターによって制御されますid)。
  • aapt2コンパイルされたファイルの親ディレクトリはフォルダーpublic.xmlでなければなりません。そうしないと、コンパイル プロセス中に不正なパスが報告されます。values
/**
 * 转换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>")
}

上記の思考と実践のプロセスを通じて、aapt2マーキングの問題を解決しただけでなくPUBLIC、新しい修正方法も発見しましたaapt2id

発生する可能性のあるエラー:

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

解決策は、gradleバージョンをgradle:3.3.2および変更することですgradle-wrapper:5.6.2。結局のところ、 は最新バージョンの をサポートtinkerしていませんgradle

参考:

Github:ティンカー

android public.xml の使用

Android-Gradle に関する注意事項

aapt2 アダプテーションのリソース ID は固定です

記事はここですべて説明します。他に連絡が必要な場合は、メッセージを残してください〜!〜!

著者の記事をもっと読みたい場合は、私の個人ブログと公開アカウントをチェックしてください。
ブックシティの活性化

おすすめ

転載: blog.csdn.net/stven_king/article/details/118736228