Gradleでビルドタイプを使用して、1つのデバイスでContentProviderを使用する同じアプリを実行する


124

使用しているリリースバージョンと1台の電話でデバッグバージョンを使用できるように、デバッグアプリにパッケージ名サフィックスを追加するようにGradleを設定しました。私はこれを参照していました:http : //tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

私のbuild.gradleファイルは次のようになります。

...
android
{
    ...
    buildTypes
    {
        debug
        {
            packageNameSuffix ".debug"
            versionNameSuffix " debug"
        }
    }
}

アプリでContentProviderを使い始めるまで、すべてが正常に機能します。私は得ます:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

これは、2つのアプリ(リリースとデバッグ)が同じContentProvider権限を登録しているために発生することを理解しています。

これを解決する可能性が1つあります。正しく理解できれば、ビルド時に使用する別のファイルを指定できるはずです。次に、さまざまな権限をさまざまなリソースファイル(およびマニフェストから権限を文字列リソースとして設定)に入れ、Gradleにデバッグビルドにさまざまなリソースを使用するように指示できるはずです。それは可能ですか?はいの場合、それを達成する方法に関するヒントは素晴らしいでしょう!

または、Gradleを使用してマニフェストを直接変更することは可能ですか?1つのデバイスでContentProviderを使用して同じアプリを実行する方法に関する他のソリューションはいつでも歓迎します。


このユースケースのアップストリームサポートの追跡に関心のある人のために:AOSPバグレポート。「公式」の現在のスタンスは、マニフェストオーバーライドソリューションを使用することです。
2014年

回答:


226

既存の答えはどれも私を満足させませんでしたが、リバティーは近かったです。これが私がやっている方法です。まず第一に、私が取り組んでいる現時点で:

  • Android Studio Beta 0.8.2
  • Gradleプラグイン0.12以降
  • Gradle 1.12

私の目標は、同じを使用して、同じデバイスでDebugバージョンと一緒にReleaseバージョンを実行することContentProviderです。


ではbuild.gradleデバッグビルドのためのアプリのセットサフィックスの:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

のAndroidManifest.xmlファイルセットandroid:authoritiesあなたのプロパティContentProvider

<provider
    android:name="com.example.app.YourProvider"
    android:authorities="${applicationId}.provider"
    android:enabled="true"
    android:exported="false" >
</provider>

あなたには、コードセットのAUTHORITY実装に必要な場所で使用することができますプロパティ:

public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";

ヒント:以前はBuildConfig.PACKAGE_NAME

それでおしまい!それは魅力のように機能します。SyncAdapterを使用する場合は、読み続けてください!


SyncAdapterの更新(2014年11月14日)

もう一度、現在のセットアップから始めます。

  • Android Studio Beta 0.9.2
  • Gradleプラグイン0.14.1
  • Gradle 2.1

基本的に、ビルドごとに値をカスタマイズする必要がある場合は、build.gradleファイルから行うことができます。

  • buildConfigFieldを使用してBuildConfig.javaクラスからアクセスします
  • 使用resValueアクセスにITリソースなどから@文字列/ your_value

リソースの代替として、個別のbuildTypeまたはフレーバーディレクトリを作成し、それらのXMLまたは値をオーバーライドできます。ただし、以下の例では使用しません。


build.gradleファイル次の行を追加します。

defaultConfig {
    resValue "string", "your_authorities", applicationId + '.provider'
    resValue "string", "account_type", "your.syncadapter.type"
    buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type"'
}

buildTypes {
    debug {
        applicationIdSuffix ".debug"
        resValue "string", "your_authorities", defaultConfig.applicationId + '.debug.provider'
        resValue "string", "account_type", "your.syncadapter.type.debug"
        buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type.debug"'
    }
}

BuildConfig.javaクラスに結果が表示されます

public static final String ACCOUNT_TYPE = "your.syncadapter.type.debug";

およびbuild / generated / res / generated / debug / values / generated.xml内

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Automatically generated file. DO NOT MODIFY -->
    <!-- Values from default config. -->
    <item name="account_type" type="string">your.syncadapter.type.debug</item>
    <item name="authorities" type="string">com.example.app.provider</item>

</resources>

あなたの中authenticator.xmlの build.gradleファイルで指定された使用リソース

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accountType="@string/account_type"
                       android:icon="@drawable/ic_launcher"
                       android:smallIcon="@drawable/ic_launcher"
                       android:label="@string/app_name"
/>

あなたにsyncadapter.xml再び同じリソースを使用すると、文字列/当局@過ぎます

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/authorities"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:supportsUploading="false"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
        />

ヒント:これらの生成されたリソースではオートコンプリート(Ctrl + Space)は機能しないため、手動で入力する必要があります


7
最良の答えは私見です。短くてシンプルな例。
2014

はい、それは私が今まで見た中で最高の回避策です。共有してくれてありがとう!新しいパッケージ名を使用するには、preferences.xmlファイルの明示的なインテントを更新する必要があるため、これとは関係のない別の問題がまだあります。code.google.com/p/android/issues/detail?id=57460
Bernd S 14

@BerndSソリューションに関する問題へのコメントを投稿しました。applicationIdを置き換えたり、サフィックスを設定したりしても、Javaパッケージには影響がないことを理解する必要があります。これはアプリの識別子にすぎず、Javaパッケージから切り離されています。stackoverflow.com/questions/24178007/…
Damian Petla

1
@JJDリンクした変更は、カスタムビルドスクリプトなしで機能します。sync_adapter.xml、authenticator.xmlに$ {applicationId}プレースホルダーを使用する場合は、build.gradleスクリプトをカスタマイズする必要があります。build.gradleスクリプトですでに多くのことを行っているので、アイデアに慣れているようです。私の回答の指示に従いましたが、それでも機能しませんでしたか?
Rob Meeuwisse 2014年

1
syncadapterのハウツーで回答を更新しました
Damian Petla

39

新しいAndroidビルドシステムのヒント:ContentProvider権限の名前変更

新しいAndroid Gradleベースのビルドシステムについて、皆さんは聞いたことがあると思います。正直に言うと、この新しいビルドシステムは、以前のビルドシステムに比べて大きな前進です。これはまだ最終版ではありませんが(このドキュメントの執筆時点での最新バージョンは0.4.2です)、ほとんどのプロジェクトで既に安全に使用できます。

私は自分のプロジェクトのほとんどをこの新しいビルドシステムに個人的に切り替えましたが、特定の状況ではサポートが不足していたためにいくつかの問題がありました。その1つは、ContentProvider権限の名前変更のサポートです。

新しいAndroidビルドシステムでは、ビルド時にパッケージ名を変更するだけで、さまざまな種類のアプリに対応できます。この改善の主な利点の1つは、同じデバイスに同時に2つの異なるバージョンのアプリをインストールできることです。例えば:

android {
   compileSdkVersion 17
   buildToolsVersion "17.0.0"

   defaultConfig {
       packageName "com.cyrilmottier.android.app"
       versionCode 1
       versionName "1"
       minSdkVersion 14 // Listen to +Jeff Gilfelt advices :)
       targetSdkVersion 17
   }

   buildTypes {
       debug {
        packageNameSuffix ".debug"
            versionNameSuffix "-debug"
       }
   }
}

このようなGradle構成を使用すると、2つの異なるAPKをアセンブルできます。

•com.cyrilmottier.android.app.debugパッケージ名のデバッグAPK•com.cyrilmottier.android.appパッケージ名のリリースAPK

唯一の問題は、2つのAPKが両方とも同じ権限を持つContentProviderを公開している場合、2つのAPKを同時にインストールできないことです。かなり論理的には、現在のビルドタイプに応じて権限の名前を変更する必要がありますが、これはGradleビルドシステムではサポートされていません(まだ?...すぐに修正されると確信しています)。だからここに行く方法があります:

最初に、プロバイダーのAndroidマニフェストContentProvider宣言を適切なビルドタイプに移動する必要があります。そのためには、次のようにします。

src / debug / AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.cyrilmottier.android.app"
   android:versionCode="1"
   android:versionName="1">

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="com.cyrilmottier.android.app.debug.provider"
           android:exported="false" />

   </application>
</manifest>

src / release / AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.cyrilmottier.android.app"
   android:versionCode="1"
   android:versionName="1">

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="com.cyrilmottier.android.app.provider"
           android:exported="false" />

   </application>
</manifest>

Gradleは、名前が同じで権限が異なるContentProviderをマージする方法を認識していないため、src / main /のAndroidManifest.xmlからContentProvider宣言を必ず削除してください。

最後に、コードの権限にアクセスする必要があるかもしれません。これは、BuildConfigファイルとbuildConfigメソッドを使用して非常に簡単に行うことができます。

android {   
   // ...

    final PROVIDER_DEBUG = "com.cyrilmottier.android.app.debug.provider"
    final PROVIDER_RELEASE = "com.cyrilmottier.android.app.provider"

   buildTypes {
       debug {
           // ...
           buildConfigField "String", "PROVIDER_AUTHORITY", PROVIDER_DEBUG
       }

       release {
           buildConfigField "String", "PROVIDER_AUTHORITY", PROVIDER_RELEASE
       }
   }
}

この回避策のおかげで、ProviderContractでBuildConfig.PROVIDER_AUTHORITYを使用して、2つの異なるバージョンのアプリを同時にインストールできます。


オリジナルのGoogle+:https : //plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ


1
Gradleを実行できない人にとっては、構文エラーが原因です。これが答えです:stackoverflow.com/questions/20678118/…–
Renan Franca

23

Cyrilの例は、ビルドタイプが少ない場合はうまく機能しますが、多くの異なるAndroidManifest.xmlを維持する必要があるため、多くのビルドタイプや製品フレーバーがある場合はすぐに複雑になります。

私たちのプロジェクトは、3つの異なるビルドタイプと6つのフレーバー、合計18のビルドバリアントで構成されているため、代わりにContentProvider権限に「.res-auto」のサポートを追加しました。

/**
 * Version 1.1.
 *
 * Add support for installing multiple variants of the same app which have a
 * content provider. Do this by overriding occurrences of ".res-auto" in
 * android:authorities with the current package name (which should be unique)
 *
 * V1.0 : Initial version
 * V1.1 : Support for ".res-auto" in strings added, 
 *        eg. use "<string name="auth">.res-auto.path.to.provider</string>"
 *
 */
def overrideProviderAuthority(buildVariant) {
    def flavor = buildVariant.productFlavors.get(0).name
    def buildType = buildVariant.buildType.name
    def pathToManifest = "${buildDir}/manifests/${flavor}/${buildType}/AndroidManifest.xml"

    def ns = new groovy.xml.Namespace("http://schemas.android.com/apk/res/android", "android")
    def xml = new XmlParser().parse(pathToManifest)
    def variantPackageName = xml.@package

    // Update all content providers
    xml.application.provider.each { provider ->
        def newAuthorities = provider.attribute(ns.authorities).replaceAll('.res-auto', variantPackageName)
        provider.attributes().put(ns.authorities, newAuthorities)
    }

    // Save modified AndroidManifest back into build dir
    saveXML(pathToManifest, xml)

    // Also make sure that all strings with ".res-auto" are expanded automagically
    def pathToValues = "${buildDir}/res/all/${flavor}/${buildType}/values/values.xml"
    xml = new XmlParser().parse(pathToValues)
    xml.findAll{it.name() == 'string'}.each{item ->
        if (!item.value().isEmpty() && item.value()[0].startsWith(".res-auto")) {
            item.value()[0] = item.value()[0].replace(".res-auto", variantPackageName)
        }
    }
    saveXML(pathToValues, xml)
}

def saveXML(pathToFile, xml) {
    def writer = new FileWriter(pathToFile)
    def printer = new XmlNodePrinter(new PrintWriter(writer))
    printer.preserveWhitespace = true
    printer.print(xml)
}

// Post processing of AndroidManifest.xml for supporting provider authorities
// across build variants.
android.applicationVariants.all { variant ->
    variant.processManifest.doLast {
        overrideProviderAuthority(variant)
    }
}

サンプルコードはここにあります:https : //gist.github.com/cmelchior/6988275


ビルドフレーバーにも同じ問題があったため、プロジェクトにも非常によく似たものを使用するように切り替えました。このアプローチは今のところ非常にうまく機能しています。
MantasV 2013年

2
FileWriterは、少なくとも私のMac OSでは、utf-8ファイルで問題を引き起こします。関連する行を次のように変更しました:def writer = new OutputStreamWriter(new FileOutputStream(pathToFile)、 "UTF-8")
Reza Mohammadi

これは本当に素晴らしいです、ありがとう!フォーマットされた文字列の破損を防ぐために、小さな変更を加えました。gist.github.com/paour/8475929
Pierre-Luc

これは非常に役に立ちましたが、processManifestステージのビルドフォルダーにvalues.xmlファイルがなかったため、クリーンアップ後にビルドできないという問題に遭遇しました。それはprocessResourcesステージまで存在しません。その時点ではマニフェストを変更するには遅すぎるため、マニフェストファイルと値ファイルの両方で.res-autoを置き換えるには、バリアントによって呼び出される2つの関数が必要になると思います。 processManifest.doLast、もう1つはvariant.processResources.doLastによって呼び出されます。
Niall 2014

20

プラグインバージョン0.8.3(実際には0.8.1ですが、正しく機能していません)以降、ビルドファイル内でリソースを定義できるため、文字列ファイルや追加のデバッグ/リリースを作成する必要がないため、よりクリーンなソリューションになる可能性があります。フォルダ。

build.gradle

android {
    buildTypes {
        debug{
            resValue "string", "authority", "com.yourpackage.debug.provider"
        }
        release {
            resValue "string", "authority", "com.yourpackage.provider"
        }
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.yourpackage"
   android:versionCode="1"
   android:versionName="1">

   <application>

       <provider
           android:name=".provider.Provider1"
           android:authorities="@string/authority"
           android:exported="false" />

   </application>
</manifest>

2
:アンドロイド2.2.1でのみ作業、リソースベースの権限を用心し、後でgithub.com/android/platform_frameworks_base/commit/...
ピエール・リュックPaour

説明をありがとう。
rciovati 2014年

1
これは、Android用searchable.xmlにも非常に便利です:searchSuggestAuthority、あなたは$ {APPLICATIONIDを}が使用できないため
user114676

13

誰かが言及しているかどうかはわかりません。実際にAndroid Gradleプラグイン0.10以降では、マニフェストマージがこの機能の公式サポートを提供します:http ://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

AndroidManifest.xmlでは、次のように$ {packageName}を使用できます。

<provider
    android:name=".provider.DatabasesProvider"
    android:authorities="${packageName}.databasesprovider"
    android:exported="true"
    android:multiprocess="true" />

そして、あなたのbuild.gradleであなたは持つことができます:

productFlavors {
    free {
        packageName "org.pkg1"
    }
    pro {
        packageName "org.pkg2"
    }
}

こちらの完全な例をご覧くださいhttps : //code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

そしてここ:https : //code.google.com/p/anymemo/source/browse/build.gradle#41


これは素晴らしいニュースですが、オーソリティを参照する必要がある<searchable>要素の場合、完全な解決策ではないようです。これらはマニフェストの一部ではないためです(ただし、既存のマージ戦略はこれらのファイルで機能しますが、マニフェストとは異なります)。
Pierre-Luc Paour 2014年

1
このためにフレーバーを使用する必要はありません。ビルドタイプでも機能します。また、BuildConfig.PACKAGE_NAMEを使用してパッケージへの静的参照を取得できることも言及しておくとよいでしょう。これは、コンテンツプロバイダーにクエリを実行するために実行時に権限を知る必要があるコンテンツプロバイダーにとって便利です。
Matt Wolfe 14年

1
android:authoritiesに$ {packageName}ではなく$ {applicationId}を使用するように更新する必要もあります
Bernd S

8

${applicationId}xmlとBuildConfig.APPLICATION_IDコードでプレースホルダーを使用します。

ビルドスクリプトを拡張して、マニフェスト以外のxmlファイルのプレースホルダーを有効にする必要があります。ビルドバリアントごとにソースディレクトリを使用して、異なるバージョンのxmlファイルを提供することもできますが、メンテナンスが非常に面倒になります。

AndroidManifest.xml

マニフェストですぐに使えるapplicationIdプレースホルダーを使用できます。次のようにプロバイダーを宣言します。

<provider
    android:name=".provider.DatabaseProvider"
    android:authorities="${applicationId}.DatabaseProvider"
    android:exported="false" />

${applicationId}ビットに注意してください。これは、ビルド時に、ビルドされるビルドバリアントの実際のapplicationIdに置き換えられます。

コードで

ContentProviderはコードで認証文字列を作成する必要があります。BuildConfigクラスを使用できます。

public class DatabaseContract {
    /** The authority for the database provider */
    public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".DatabaseProvider";
    // ...
}

BuildConfig.APPLICATION_IDビットに注意してください。これは、ビルドされるビルドバリアントの実際のapplicationIdを持つ生成クラスです。

res / xml /ファイル、たとえばsyncadapter.xml、accountauthenticator.xml

同期アダプターを使用する場合は、res / xml /ディレクトリーのxmlファイルでContentProviderおよびAccountManagerのメタデータを提供する必要があります。ここでは、applicationIdプレースホルダーはサポートされていません。ただし、ビルドスクリプトを自分で拡張してハッキングすることもできます。

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:allowParallelSyncs="false"
    android:contentAuthority="${applicationId}.DatabaseProvider"
    android:isAlwaysSyncable="true"
    android:supportsUploading="true"
    android:userVisible="true" />

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:icon="@drawable/ic_launcher"
    android:label="@string/account_authenticator_label"
    android:smallIcon="@drawable/ic_launcher" />

繰り返しますが、 ${applicationId}。これは、以下のGradleスクリプトをモジュールのルートに追加し、build.gradleから適用した場合にのみ機能します。

build.gradle

モジュールbuild.gradleスクリプトから追加のビルドスクリプトを適用します。良い場所は、Android gradleプラグインの下です。

apply plugin: 'com.android.application'
apply from: './build-processApplicationId.gradle'

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

以下は、res / xml /プレースホルダービルドスクリプトの作業用ソースです。より良いドキュメント化されたバージョンはgithubで入手できます。改善や拡張は大歓迎です。

def replace(File file, String target, String replacement) {
    def result = false;

    def reader = new FileReader(file)
    def lines = reader.readLines()
    reader.close()

    def writer = new FileWriter(file)
    lines.each { line ->
        String replacedLine = line.replace(target, replacement)
        writer.write(replacedLine)
        writer.write("\n")
        result = result || !replacedLine.equals(line)
    }
    writer.close()

    return result
}

def processXmlFile(File file, String applicationId) {
    if (replace(file, "\${applicationId}", applicationId)) {
        logger.info("Processed \${applicationId} in $file")
    }
}

def processXmlDir(File dir, String applicationId) {
    dir.list().each { entry ->
        File file = new File(dir, entry)
        if (file.isFile()) {
            processXmlFile(file, applicationId)
        }
    }
}

android.applicationVariants.all { variant ->
    variant.mergeResources.doLast {
        def applicationId = variant.mergedFlavor.applicationId + (variant.buildType.applicationIdSuffix == null ? "" : variant.buildType.applicationIdSuffix)
        def path = "${buildDir}/intermediates/res/${variant.dirName}/xml/"
        processXmlDir(new File(path), applicationId)
    }
}

Strings.xml

私の意見では、リソース文字列のプレースホルダーサポートを追加する必要はありません。上記のユースケースでは、少なくとも必要ありません。ただし、スクリプトを簡単に変更して、res / xml /ディレクトリのプレースホルダーだけでなく、res / values /ディレクトリのプレースホルダーも置き換えることができます。


6

Cyrilとrciovatiの混合を好む。私はもっ​​と単純だと思います、あなたは2つの修正をしているだけです。

build.gradle次のようになります。

android {
    ...
    productFlavors {
        production {
            packageName "package.name.production"
            resValue "string", "authority", "package.name.production.provider"
            buildConfigField "String", "AUTHORITY", "package.name.production.provider"
        }

        testing {
            packageName "package.name.debug"
            resValue "string", "authority", "package.name.debug.provider"
            buildConfigField "String", "AUTHORITY", "package.name.debug.provider"
        }
    }
    ...
}

そしてAndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="package.name" >

    <application
        ...>

        <provider android:name=".contentprovider.Provider" android:authorities="@string/authority" />

    </application>
</manifest>

5

gradle.build

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.example.awsomeapp"
        minSdkVersion 9
        targetSdkVersion 23
        versionCode 1
        versionName "1.0.0"
    }

    productFlavors
    {
        prod {
            applicationId = "com.example.awsomeapp"
        }

        demo {
            applicationId = "com.example.awsomeapp.demo"
            versionName = defaultConfig.versionName + ".DEMO"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            debuggable false
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }

        debug {
            applicationIdSuffix ".debug"
            versionNameSuffix = ".DEBUG"
            debuggable true
        }
    }

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // rename the apk
            def file = output.outputFile;
            def newName;
            newName = file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk");
            newName = newName.replace(project.name, "awsomeapp");
            output.outputFile = new File(file.parent, newName);
        }

        //Generate values Content Authority and Account Type used in Sync Adapter, Content Provider, Authenticator
        def valueAccountType = applicationId + '.account'
        def valueContentAuthority = applicationId + '.authority'

        //generate fields in Resource string file generated.xml
        resValue "string", "content_authority", valueContentAuthority
        resValue "string", "account_type", valueAccountType

        //generate fields in BuildConfig class
        buildConfigField "String", "ACCOUNT_TYPE", '"'+valueAccountType+'"'
        buildConfigField "String", "CONTENT_AUTHORITY", '"'+valueContentAuthority+'"'

        //replace field ${valueContentAuthority} in AndroidManifest.xml
        mergedFlavor.manifestPlaceholders = [ valueContentAuthority: valueContentAuthority ]
    }
}

authenticator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:smallIcon="@drawable/ic_launcher" />

sync_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/content_authority"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
              android:supportsUploading="true"/>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0.0" package="com.example.awsomeapp">

    <uses-permission android:name="android.permission.GET_ACCOUNTS"/><!-- SyncAdapter and GCM requires a Google account. -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>

    <!-- GCM Creates a custom permission so only this app can receive its messages. -->
    <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>

    <application....
    .......

        <!-- Stub Authenticator --> 
        <service 
                android:name="com.example.awsomeapp.service.authenticator.CAuthenticatorService"
                android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>
            <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/>
        </service>
        <!--  -->

        <!-- Sync Adapter -->
        <service
                android:name="com.example.awsomeapp.service.sync.CSyncService"
                android:exported="true"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" />
        </service>
        <!--  -->

        <!-- Content Provider -->
        <provider android:authorities="${valueContentAuthority}"
            android:exported="false" 
            android:name="com.example.awsomeapp.database.contentprovider.CProvider">
        </provider>
        <!--  --> 
    </application>
</manifest>

コード:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;

4

@ChristianMelchiorのサンプルに基づいて、これが私の解決策です。これは、以前の解決策の2つの問題を修正します。

  • ビルドディレクトリのvalues.xmlを変更するソリューションは、リソースの完全な再構築を引き起こします(すべてのドローアブルの一部を含む)

  • 不明な理由により、IntelliJ(およびおそらくAndroid Studio)はリソースを確実に処理しないため、ビルドに置き換えられていない.res-autoプロバイダー権限が含まれます

この新しいソリューションは、新しいタスクを作成することでGradleの方法を強化し、入力ファイルと出力ファイルを定義することで増分ビルドを可能にします。

  1. variants文字列リソースを含むリソースxmlファイルのようにフォーマットされたファイルを作成します(この例ではディレクトリに配置します)。これらはアプリのリソースにマージさ.res-autoれ、値に出現するものはすべてバリアントのパッケージ名に置き換えられます。たとえば、<string name="search_provider">.res-auto.MySearchProvider</string>

  2. この要点build_extras.gradleファイルをプロジェクトに追加し、ブロックの上に追加して、メインから参照しますbuild.gradleapply from: './build_extras.gradle'android

  3. android.defaultConfigブロックに追加して、デフォルトのパッケージ名を必ず設定してくださいbuild.gradle

  4. AndroidManifest.xml及び(例えば、他の構成ファイルxml/searchable.xml自動補完検索プロバイダの場合)、(例えばプロバイダを参照@string/search_provider

  5. 同じ名前を取得する必要がある場合は、BuildConfig.PACKAGE_NAME変数を使用できます。たとえば、BuildConfig.PACKAGE_NAME + ".MySearchProvider"

https://gist.github.com/paour/9189462


更新:このメソッドはAndroid 2.2.1以降でのみ機能します。以前のプラットフォームについては、この回答を参照してください。新しいマニフェストの合併はまだまだ非常に粗いため、それ自体に一連の問題があります。


バリアントディレクトリはどこに置いていますか?私は、いくつかのAndroidモジュール(メインアプリといくつかのAndroidライブラリモジュール)に依存する1つの大きなAndroid Studioプロジェクトを持っています。コマンドラインからビルドできますが、Android Studio内からビルドしようとすると、からのvariants/res-auto-values.xml相対を探し/Applications/Android Studio.app/bin/ます。つまり、のFileNotFoundExceptionが発生しません/Applications/Android Studio.app/bin/variants/res-auto-values.xml。Macで実行しています。これは素晴らしい解決策ですが、チームの他のメンバーのためにIDEで機能させたいです。
user1978019 2014年

1
私自身の問題を修正しました。Gradleはを使用してパスを解決しているように見えますSystem.getProperty("user.dir")。これは、Android Studioビルドによって呼び出されたときに異なる結果を返します。解決策は、で返されるプロジェクトディレクトリへの相対パスを使用することgradle.startParameter.getProjectDir()です。Paourのリンクされた要点にある私のコメントも参照してください。
user1978019 2014年

:アンドロイド2.2.1でのみ作業、リソースベースの権限を用心し、後でgithub.com/android/platform_frameworks_base/commit/...
ピエール・リュックPaour


2

残念ながら、androidプラグインの現在のバージョン(0.4.1)は、これに対する適切な解決策を提供していないようです。私はまだこれを試す時間はありませんでしたが、この問題の考えられる回避策は、文字列リソース@string/provider_authorityを使用し、マニフェストでそれを使用することです:android:authority="@string/provider_authority"。次にres/values/provider.xml、各ビルドタイプのresフォルダーに権限を上書きする必要があります。この場合、これは次のようになります。src/debug/res

私はオンザフライでxmlファイルを生成することを検討しましたが、繰り返しますが、現在のバージョンのプラグインには、それに対する適切なフックはないようです。ただし、機能のリクエストを送信することをお勧めします。この同じ問題に遭遇する人が増えることは想像できます。


こんにちはマーカス、返信ありがとうございます。あなたが提案する解決策は、今のところ私が自分で考えられる唯一の解決策です。しかし、私の問題は、Gradleでそれを実現する方法がわかりません。
MantasV 2013年

2

この投稿の答えは私には役立ちます。

http://www.kevinrschultz.com/blog/2014/03/23/using-android-content-providers-with-multiple-package-names/

私は3つの異なるフレーバーを使用するため、kevinrschultzが言ったように、各フレーバーにコンテンツプロバイダーを持つ3つのマニフェストを作成します。

productFlavors {
    free {
        packageName "your.package.name.free"
    }

    paid {
        packageName "your.package.name.paid"
    }

    other {
        packageName "your.package.name.other"
    }
}

メインマニフェストにプロバイダーが含まれていません:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Permissions -->
<application>
    <!-- Nothing about Content Providers at all -->
    <!-- Activities -->
    ...
    <!-- Services -->
    ...
</application>

そして、プロバイダーを含む各フレーバーのマニフェスト。

自由:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.free"
        android:exported="false" >
    </provider>
</application>
</manifest>

支払い済み:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.paid"
        android:exported="false" >
    </provider>
</application>
</manifest>

その他:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.other"
        android:exported="false" >
    </provider>
</application>
</manifest>


0

私の解決策は、でプレースホルダー置換を使用することAndroidManifest.xmlです。また、packageNameSuffix属性を処理するため、同じデバイス上で、debugおよびreleaseその他のカスタムビルドを使用できます。

applicationVariants.all { variant ->
    def flavor = variant.productFlavors.get(0)
    def buildType = variant.buildType
    variant.processManifest.doLast {
        println '################# Adding Package Names to Manifest #######################'
        replaceInManifest(variant,
            'PACKAGE_NAME',
            [flavor.packageName, buildType.packageNameSuffix].findAll().join()) // ignores null
    }
}

def replaceInManifest(variant, fromString, toString) {
    def flavor = variant.productFlavors.get(0)
    def buildtype = variant.buildType
    def manifestFile = "$buildDir/manifests/${flavor.name}/${buildtype.name}/AndroidManifest.xml"
    def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll(fromString, toString)
    new File(manifestFile).write(updatedContent, 'UTF-8')
}

私はそれを持っています gistそれが後で進化するかどうかを確認したい場合ます。

複数のリソースやXML解析手法よりもエレガントな手法であることがわかりました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.