Gradle에서 빌드 유형을 사용하여 한 기기에서 ContentProvider를 사용하는 동일한 앱 실행


124

디버그 앱에 패키지 이름 접미사를 추가하도록 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]

두 앱 (릴리스 및 디버그)이 동일한 ContentProvider 권한을 등록하기 때문에 이런 일이 발생한다는 것을 이해합니다.

이 문제를 해결할 수있는 한 가지 가능성이 있습니다. 내가 올바르게 이해했다면 빌드 할 때 사용할 다른 파일을 지정할 수 있어야합니다. 그런 다음 다른 리소스 파일 (및 Manifest에서 문자열 리소스로 권한 설정)에 다른 권한을 배치하고 Gradle에 디버그 빌드에 다른 리소스를 사용하도록 지시 할 수 있어야합니다. 가능합니까? 그렇다면 그것을 달성하는 방법에 대한 힌트는 굉장 할 것입니다!

아니면 Gradle을 사용하여 Manifest를 직접 수정할 수 있습니까? 한 장치에서 ContentProvider를 사용하여 동일한 앱을 실행하는 방법에 대한 다른 솔루션은 언제나 환영합니다.


이 사용 사례에 대한 업스트림 지원을 추적하는 데 관심이있는 사용자 : AOSP 버그 보고서 . "공식적인"현재 입장은 매니페스트 우선 솔루션 을 사용 하는 것 입니다.
세임 2014-06-13

답변:


226

기존 답변 중 어느 것도 나를 만족시키지 않았지만 Liberty는 가까웠습니다. 그래서 이것이 내가하는 방법입니다. 우선 현재 저는 다음과 같이 일하고 있습니다.

  • Android 스튜디오 베타 0.8.2
  • Gradle 플러그인 0.12. +
  • Gradle 1.12

목표 는 동일한 .NET Framework를 사용하여 동일한 장치에서 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 스튜디오 베타 0.9.2
  • Gradle 플러그인 0.14.1
  • Gradle 2.1

기본적으로 다른 빌드에 대해 일부 값을 사용자 정의해야하는 경우 build.gradle 파일에서 수행 할 수 있습니다.

  • buildConfigField 를 사용 하여 BuildConfig.java클래스 에서 액세스
  • resValue 를 사용 하여 리소스에서 액세스합니다 (예 : @ string / 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
최고의 답변 IMHO. 짧고 간단한 예입니다.
rekire

예, 이것이 제가 지금까지 본 것 중 가장 좋은 해결 방법입니다. 공유 해주셔서 감사합니다! 새 패키지 이름을 사용하려면 preferences.xml 파일에서 명시 적 의도를 업데이트해야하므로 이와 관련되지 않은 또 다른 문제가 있습니다. code.google.com/p/android/issues/detail?id=57460
Bernd S

@BerndS 솔루션 문제에 대한 의견을 게시했습니다. applicationId를 바꾸거나 접미사를 설정하여 변경하는 것은 Java 패키지에 영향을 미치지 않는다는 것을 이해해야합니다. 앱의 식별자 일 뿐이며 자바 패키지에서 분리됩니다. 다른 질문에 대한 내 대답을 참조하십시오 stackoverflow.com/questions/24178007/...
데미안 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) 대부분의 프로젝트에서 이미 안전하게 사용할 수 있습니다.

저는 대부분의 프로젝트를이 새로운 빌드 시스템으로 개인적으로 전환했으며 특정 상황에서 지원이 부족하여 문제가있었습니다. 그중 하나는 ContentProvider 권한 이름 변경에 대한 지원입니다.

새로운 Android 빌드 시스템을 사용하면 빌드시 패키지 이름을 수정하기 만하면 다양한 유형의 앱을 처리 할 수 ​​있습니다. 이 개선 사항의 주요 이점 중 하나는 이제 동일한 장치에 동시에 두 가지 버전의 앱을 설치할 수 있다는 것입니다. 예를 들면 :

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 구성을 사용하면 두 개의 서로 다른 APK를 어셈블 할 수 있습니다.

• com.cyrilmottier.android.app.debug 패키지 이름이있는 디버그 APK • com.cyrilmottier.android.app 패키지 이름이있는 릴리스 APK

유일한 문제는 두 APK가 동일한 권한을 가진 ContentProvider를 노출하는 경우 동시에 두 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를 사용하고 동시에 두 가지 버전의 앱을 설치할 수 있습니다.


Google+ 원본 : https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ


1
gradle을 실행할 수없는 사람에게는 sintaxy 오류가 발생합니다. 답은 다음과 같습니다. stackoverflow.com/questions/20678118/…
Renan Franca 2014 년

23

Cyril의 예제는 빌드 유형이 몇 개만있는 경우 훌륭하게 작동하지만, 다양한 AndroidManifest.xml을 많이 유지해야하기 때문에 빌드 유형 및 / 또는 제품 버전이 많으면 빠르게 복잡해집니다.

우리 프로젝트는 3 가지 다른 빌드 유형과 총 18 개의 빌드 변형을 포함하는 6 가지 버전으로 구성되어 있습니다. 대신 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


빌드 플레이버와 동일한 문제가 있었기 때문에 내 프로젝트에서도 매우 유사한 것을 사용하도록 전환했습니다. 이 접근 방식은 현재 매우 잘 작동합니다.
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 Paour 2014 년

이것은 매우 도움이되었지만 processManifest 단계의 빌드 폴더에 values.xml 파일이 없기 때문에 정리 후 빌드되지 않는 문제가 발생했습니다. 그것은 processResources 단계까지 존재하지 않습니다.이 시점에서 매니페스트를 수정하기에는 너무 늦었으므로 매니페스트 및 값 파일에서 .res-auto를 대체하려면 variant에 의해 호출되는 2 개의 함수가 필요하다고 생각합니다. processManifest.doLast, 다른 하나는 variant.processResources.doLast에 의해 호출됩니다.
Niall 2015

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
리소스 기반 권한은 Android 2.2.1 이상에서만 작동합니다. github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour 2014 년

설명해 주셔서 감사합니다.
rciovati

1
이것은 또한 android : searchSuggestAuthority에 대한 searchable.xml에서도 매우 유용합니다. $ {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


이것은 좋은 소식입니다. 그러나 이것이 매니페스트의 일부가 아니기 때문에 autority를 ​​참조해야하는 <searchable> 요소의 경우 완전한 솔루션이 아닌 것 같습니다 (그러나 기존 병합 전략은 이러한 파일에 대해 작동합니다. 매니페스트와 달리).
Pierre-Luc Paour 2014 년

1
이를 위해 플레이버를 사용할 필요가 없으며 빌드 유형에서도 작동합니다. 또한 BuildConfig.PACKAGE_NAME을 사용하여 패키지에 대한 정적 참조를 얻을 수 있다는 점을 언급하는 것이 좋습니다. 이는 콘텐츠 제공자를 쿼리하기 위해 런타임에 권한을 알아야하는 콘텐츠 제공자에게 유용합니다.
Matt Wolfe

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 / placeholder 빌드 스크립트의 작업 소스입니다. 더 나은 문서화 된 버전은 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의 혼합물을 선호합니다. 더 간단하다고 생각합니다. 두 가지 수정 만 있습니다.

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의 샘플을 기반으로 한 내 솔루션은 이전 솔루션의 두 가지 문제를 해결합니다.

  • 빌드 디렉토리에서 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. in 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 라이브러리 모듈)에 의존하는 하나의 큰 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 스튜디오 빌드에서 호출하면 다른 결과를 반환합니다. 해결책은 .NET과 함께 반환되는 프로젝트 디렉터리에 상대적인 경로를 사용하는 것입니다 gradle.startParameter.getProjectDir(). Paour의 연결된 요점에서도 내 의견을 참조하십시오.
user1978019 2014 년

리소스 기반 권한은 Android 2.2.1 이상에서만 작동합니다. github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour 2014 년


2

안타깝게도 현재 버전 (0.4.1)의 Android 플러그인은 이에 대한 좋은 해결책을 제공하지 않는 것 같습니다. 아직 시도 할 시간이 없었지만이 문제에 대한 가능한 해결 방법은 문자열 리소스를 사용 @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

왜 이것을 추가하지 않습니까?

type.packageNameSuffix = ". $ type.name"


0

내 해결책은 AndroidManifest.xml. 또한 핸들은 packageNameSuffix당신이 가질 수 있도록 속성 debugrelease다른 사용자가 같은 장치에 구축뿐만 아니라.

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.