129

デバッグ アプリにパッケージ名のサフィックスを追加するように Gradle をセットアップしたので、使用しているリリース バージョンとデバッグ バージョンを 1 台の電話で使用できます。私はこれを参照していました: 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 を使用して同じアプリを実行する方法に関する他のソリューションは、いつでも歓迎します。

4

14 に答える 14

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>

src/main/ の AndroidManifest.xml から ContentProvider 宣言を必ず削除してください。Gradle は、同じ名前で別の権限を持つ 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

于 2013-06-04T10:40:11.070 に答える
23

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

私たちのプロジェクトは、3 つの異なるビルド タイプと 6 つのフレーバー、合計 18 のビルド バリアントで構成されているため、代わりに ContentProvider オーソリティに「.res-auto」のサポートを追加しました。これは、現在のパッケージ名に拡張され、異なる AndroidManifest.xml を維持する必要がなくなります。

/**
 * 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

于 2013-10-15T08:45:25.103 に答える
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>
于 2014-03-14T10:37:49.497 に答える
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

于 2014-05-08T22:50:14.743 に答える
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/ ディレクトリのプレースホルダーも置き換えることができます。

于 2014-11-13T19:51:42.167 に答える
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>
于 2014-12-05T10:18:26.243 に答える
2

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

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

于 2013-05-28T12:42:01.777 に答える