एक ही डिवाइस पर ContentProvider का उपयोग करने वाले एक ही ऐप को चलाने के लिए Gradle में बिल्ड प्रकारों का उपयोग करना


124

मैंने अपने डिबग ऐप में पैकेज का नाम प्रत्यय जोड़ने के लिए ग्रैडल की स्थापना की है ताकि मेरे पास रिलीज़ संस्करण हो सकता है जो मैं उपयोग कर रहा हूं और एक फोन पर डीबग संस्करण। मैं इसका उल्लेख कर रहा था: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

मेरी बिल्ड.ग्रेड फ़ाइल इस तरह दिखती है:

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

जब तक मैं अपने ऐप में एक ContentProvider का उपयोग शुरू नहीं करता, तब तक सब कुछ ठीक रहता है। मुझे मिला:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

मैं समझता हूं कि ऐसा इसलिए होता है क्योंकि दो ऐप्स (रिलीज़ और डीबग) एक ही ContentProvider प्राधिकरण पंजीकृत कर रहे हैं।

मैं इसे हल करने के लिए एक संभावना देखता हूं। यदि मैं सही ढंग से समझता हूं, तो आपको भवन बनाते समय विभिन्न फाइलों को निर्दिष्ट करने में सक्षम होना चाहिए। तब मुझे अलग-अलग संसाधन फ़ाइलों (और मैनिफ़ेस्ट सेट अथॉरिटी से स्ट्रिंग रिसोर्स के रूप में) में अलग-अलग अधिकारियों को रखने में सक्षम होना चाहिए और ग्रैड को डिबग बिल्ड के लिए अलग-अलग संसाधन का उपयोग करने के लिए कहना चाहिए। क्या यह संभव है? यदि हाँ तो कैसे प्राप्त करने के बारे में कोई संकेत भयानक होगा!

या हो सकता है कि ग्रेडेल का उपयोग करके मेनिफेस्ट को सीधे संशोधित करना संभव हो? किसी एक डिवाइस पर ContentProvider के साथ एक ही ऐप को चलाने का कोई अन्य समाधान हमेशा स्वागत है।


इस उपयोग के मामले के लिए अपस्ट्रीम समर्थन पर नज़र रखने के इच्छुक लोगों के लिए: AOSP बग रिपोर्ट । "आधिकारिक" वर्तमान रुख प्रकट ओवरराइडिंग समाधान का उपयोग करना है
डेसिम

जवाबों:


226

मौजूदा जवाबों में से किसी ने भी मुझे संतुष्ट नहीं किया, हालांकि लिबर्टी करीब थी। तो यह है कि मैं यह कैसे कर रहा हूँ। सबसे पहले मैं जिस समय के साथ काम कर रहा हूं:

  • एंड्रॉइड स्टूडियो बीटा 0.8.2
  • ग्रेड प्लगइन 0.12। +
  • ढाल 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 के लिए अद्यतन (14.11.2014)

एक बार फिर मैं अपने वर्तमान सेटअप के साथ शुरू करूंगा:

  • एंड्रॉयड स्टूडियो बीटा 0.9.2
  • ग्रेड प्लगइन 0.14.1
  • गाद २.१

मूल रूप से, यदि आपको विभिन्न बिल्ड के लिए कुछ मानों को अनुकूलित करने की आवश्यकता है, तो आप इसे बिल्ड.ग्रेड फ़ाइल से कर सकते हैं:

  • वर्ग से इसे एक्सेस करने के लिए buildConfigField का उपयोग करेंBuildConfig.java
  • संसाधनों से इसे एक्सेस करने के लिए resValue का उपयोग करें जैसे @ स्ट्रिंग / your_value

संसाधनों के लिए एक विकल्प के रूप में, आप अलग बिल्डटाइप या स्वाद निर्देशिका बना सकते हैं और XMLs या उनके भीतर मानों को ओवरराइड कर सकते हैं। हालांकि, मैं नीचे दिए गए उदाहरण में इसका उपयोग नहीं करने जा रहा हूं।

उदाहरण


में 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";

और निर्माण में / उत्पन्न / Res / उत्पन्न / डिबग / मान / उत्पन्न। 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>

अपने प्रमाणक । 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"
/>

अपने Syncadcape.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। अच्छा लघु और सरल उदाहरण।
बजे

हां, यह अब तक का सबसे अच्छा वर्कअराउंड है। साझा करने के लिए बहुत - बहुत धन्यवाद! इसके बाद भी एक और समस्या है, क्योंकि मुझे नए पैकेज नाम का उपयोग करने के लिए प्राथमिकताओं में स्पष्ट आशय को अद्यतन करने की आवश्यकता है। code.google.com/p/android/issues/detail?id=57460
बेरंड एस

@BerndS मैंने आपके मुद्दे पर समाधान के साथ एक टिप्पणी पोस्ट की है। आपको यह समझने की जरूरत है कि एप्लिकेशनआईडी को बदलने या प्रत्यय स्थापित करने से जावा पैकेज प्रभावित नहीं होते हैं। यह आपके ऐप का सिर्फ एक पहचानकर्ता है और इसे जावा पैकेज से डिकॉय किया गया है। एक अन्य प्रश्न के मेरे उत्तर को देखें stackoverflow.com/questions/24178007/…
Damian Petla

1
@JJD आपके द्वारा लिंक किए जाने वाले संशोधन बिना किसी कस्टम बिल्ड स्क्रिप्ट के काम करेंगे। यदि आप Sync_adapter.xml, Authentator.xml के लिए $ {applicationId} प्लेसहोल्डर का उपयोग करना चाहते हैं, तो आपको अपनी build.gradle स्क्रिप्ट को अनुकूलित करना होगा। मैं देखता हूं कि आपने अपनी बिल्ड.ग्रेड लिपि में पहले ही बहुत कुछ कर लिया है ताकि आप विचार के साथ सहज रहें। क्या आपने मेरे उत्तर में निर्देशों का पालन किया है और क्या यह अभी भी काम नहीं किया है?
रोब मिउविसे

1
मैंने अपने जवाब को कैसे-समन्वयनकर्ता के लिए अद्यतन किया है
डेमियन पेटला

39

नया एंड्रॉइड बिल्ड सिस्टम टिप: ContentProvider प्राधिकरण का नाम बदल रहा है

मुझे लगता है कि आप सभी ने नए एंड्रॉइड ग्रैडल-आधारित बिल्ड सिस्टम के बारे में सुना होगा। चलो ईमानदार रहें, यह नया बिल्ड सिस्टम पिछले एक की तुलना में बहुत बड़ा कदम है। यह अभी तक अंतिम नहीं है (इस लेखन के रूप में, नवीनतम संस्करण 0.4.2 है) लेकिन आप इसे पहले से ही अपनी अधिकांश परियोजनाओं में सुरक्षित रूप से उपयोग कर सकते हैं।

मैंने अपने अधिकांश प्रोजेक्ट को इस नए बिल्ड सिस्टम में बदल दिया है और कुछ विशेष परिस्थितियों में समर्थन की कमी के कारण कुछ मुद्दे थे। जिनमें से एक ContentProvider प्राधिकरण का नाम बदलने का समर्थन है

नया एंड्रॉइड बिल्ट सिस्टम आपको बिल्ड समय पर केवल पैकेज नाम को संशोधित करके विभिन्न प्रकार के ऐप से निपटने की सुविधा देता है। इस सुधार का एक मुख्य लाभ यह है कि आप एक ही समय में एक ही डिवाइस पर अपने ऐप के दो अलग-अलग संस्करण स्थापित कर सकते हैं। उदाहरण के लिए:

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

इस तरह के ग्रैडल कॉन्फ़िगरेशन का उपयोग करके, आप दो अलग-अलग APK इकट्ठा कर सकते हैं:

• com.cyrilmottier.android.app.debug पैकेज नाम के साथ एक डीबग एपीके • com.cyrilmottier.android.app पैकेज नाम के साथ एक रिलीज APK

एकमात्र समस्या यह है कि आप दोनों एपीके को एक ही समय में स्थापित नहीं कर पाएंगे, यदि वे दोनों एक ही अधिकारियों के साथ एक कंटेंटप्रॉइडर को उजागर करते हैं। बहुत तार्किक रूप से हमें वर्तमान बिल्ड प्रकार के आधार पर प्राधिकरण का नाम बदलने की आवश्यकता है ... लेकिन यह ग्रेडल बिल्ड सिस्टम द्वारा समर्थित नहीं है (अभी तक? ... मुझे यकीन है कि यह जल्द ही तय हो जाएगा)। तो यहाँ जाने का एक तरीका है:

सबसे पहले हमें प्रदाता एंड्रॉइड मैनिफ़ेस्ट ContentProvider घोषणा को उचित बिल्ड प्रकार पर ले जाना होगा। ऐसा करने के लिए, हमारे पास बस:

src / डिबग / 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 / रिलीज / 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>

AndroidManifest.xml से src / main / में ContentProvider घोषणा को हटाना सुनिश्चित करें क्योंकि ग्रैडल को पता नहीं है कि एक ही नाम वाले ContentProviders को कैसे मर्ज किया जाए लेकिन एक अलग प्राधिकरण।

अंत में हमें कोड में प्राधिकरण तक पहुंचने की आवश्यकता हो सकती है। 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
       }
   }
}

इस समाधान के लिए धन्यवाद आप BuildConfig.PROVIDER_AUTHORITY को अपने प्रदाता में उपयोग कर पाएंगे और एक ही समय में अपने ऐप के दो अलग-अलग संस्करण इंस्टॉल कर पाएंगे।


Google+ पर मूल: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ


1
किसी ऐसे व्यक्ति के लिए, जो गतिहीनता नहीं चला सकता है, सिंटेक्स त्रुटि को शांत कर सकता है। यहाँ जवाब है: stackoverflow.com/questions/20678118/…
रेनन फ्रांका

23

जबकि सिरिल का उदाहरण बहुत अच्छा काम करता है यदि आपके पास केवल कुछ बिल्ड प्रकार हैं, तो यह जल्दी से जटिल हो जाता है यदि आपके पास कई बिल्ड प्रकार और / या उत्पाद जायके हैं जैसे कि आपको बहुत से अलग-अलग AndroidManifest.xml के बनाए रखने की आवश्यकता है।

हमारी परियोजना में 3 अलग-अलग बिल्ड प्रकार और 6 फ्लेवर हैं जो कुल 18 बिल्ड वेरिएंट हैं, इसलिए इसके बजाय हमने कंटेंटप्रॉइडर प्राधिकरणों में ".res-auto" के लिए समर्थन जोड़ा, जो कि वर्तमान packagename का विस्तार करता है और अलग-अलग AndroidManestest.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


मैंने अपनी परियोजना के लिए भी कुछ इसी तरह का उपयोग करने के लिए स्विच किया, क्योंकि मेरे पास निर्माण जायके के साथ एक ही मुद्दा था। यह दृष्टिकोण अभी के लिए बहुत अच्छा काम करता है।
मंटासव

2
FileWriter utf-8 फाइलों पर कम से कम, मेरे Mac OS पर परेशानी पैदा करता है। मैंने संबंधित लाइन को बदल दिया: डिफ राइटर = नया आउटपुटस्ट्रीमवेयर (नया फाइलऑटप्यूटस्ट्रीम (pathToFile), "UTF-8")
रेजा मोहम्मदी

यह वास्तव में बहुत अच्छा है, धन्यवाद! मैंने स्वरूपित तारों के साथ टूटने को रोकने के लिए एक छोटा सा बदलाव किया है। gist.github.com/paour/8475929
पियरे-ल्यूक पाऊर

यह बहुत मददगार था, लेकिन मैं एक समस्या में भाग गया, जहां यह साफ होने के बाद नहीं बनेगा, क्योंकि processManifest स्टेज पर बिल्ड फ़ोल्डर में कोई मान नहीं था। xml फ़ाइल। यह तब तक मौजूद नहीं है जब तक कि processResources स्टेज, जिस बिंदु पर प्रकट को संशोधित करने के लिए बहुत देर हो चुकी है, इसलिए मैनिफ़ेस्ट और मान दोनों फ़ाइलों में .res-auto को बदलने के लिए, मुझे लगता है कि आपको 2 फ़ंक्शन की आवश्यकता होगी, एक जिसे वेरिएंट कहा जाता है। processManifest.doLast, जिसे variant.processResources.doLast द्वारा बुलाया जाता है।
Niall

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/…
पियरे-ल्यूक पाऊर

स्पष्टीकरण के लिए धन्यवाद।
रोस्कोवती

1
यह Android के लिए खोजा। xml में भी बहुत उपयोगी है: searchSuggestAuthority, क्योंकि वहां आप $ {applicationId} का उपयोग नहीं कर सकते
user114676

13

मुझे नहीं पता कि कोई इसका उल्लेख करे। दरअसल एंड्रॉइड ग्रेडल प्लगइन 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


यह बहुत अच्छी खबर है, लेकिन ऐसा नहीं लगता कि यह <खोज योग्य तत्वों के मामले में एक पूर्ण समाधान है, जिसे ऑटोरिटी को संदर्भित करने की आवश्यकता है, क्योंकि ये प्रकटीकरण का हिस्सा नहीं हैं (लेकिन मौजूदा मर्ज की रणनीति इन फ़ाइलों के लिए काम करती है, मेनिफेस्ट के विपरीत)।
पियरे-ल्यूक पौर

1
आपको इसके लिए फ्लेवर का उपयोग नहीं करना है, यह बिल्ड प्रकारों के साथ भी काम करता है। इसके अलावा, यह उल्लेख करना अच्छा होगा कि आप अपने पैकेज का एक स्थिर संदर्भ प्राप्त करने के लिए BuildConfig.PACKAGE_NAME का उपयोग कर सकते हैं। यह सामग्री प्रदाताओं के लिए उपयोगी है, जहां सामग्री प्रदाता को क्वेरी करने के लिए प्राधिकरण को रनटाइम पर जाना चाहिए।
मैट वोल्फ

1
एंड्रॉइड के लिए $ {packageName} के बजाय $ {applicationId} का उपयोग करने के लिए भी अपडेट किया जाना चाहिए: अधिकारियों
एस

8

${applicationId}Xml और BuildConfig.APPLICATION_IDकोड में प्लेसहोल्डर्स का उपयोग करें ।

आपको प्रकट के अलावा xml फ़ाइलों में प्लेसहोल्डर्स को सक्षम करने के लिए बिल्ड स्क्रिप्ट का विस्तार करना होगा। आप xml फ़ाइलों के विभिन्न संस्करणों को प्रदान करने के लिए प्रति संस्करण एक स्रोत निर्देशिका का उपयोग कर सकते हैं, लेकिन रखरखाव बहुत जल्दी बोझिल हो जाएगा।

AndroidManifest.xml

आप एप्लिकेशन बॉक्स के प्लेसहोल्डर का उपयोग मैनिफ़ेस्ट में बॉक्स से बाहर कर सकते हैं। अपने प्रदाता को इस तरह घोषित करें:

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

ध्यान दें ${applicationId}। इसे बिल्ड वेरिएंट के लिए वास्तविक एप्लिकेशनआईड के साथ बिल्ड टाइम पर बदल दिया जाता है।

कोड में

आपके ContentProvider को कोड में प्राधिकरण स्ट्रिंग का निर्माण करने की आवश्यकता है। यह BuildConfig क्लास का उपयोग कर सकता है।

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

ध्यान दें BuildConfig.APPLICATION_ID। यह निर्मित वेरिएंट के लिए वास्तविक एप्लिकेशनआईडी के साथ एक उत्पन्न वर्ग है।

res / xml / files, उदा। 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}। यह केवल तभी काम करता है जब आप नीचे दी गई स्क्रिप्ट को अपने मॉड्यूल की जड़ में जोड़ते हैं और इसे build.gradle से लागू करते हैं।

build.gradle

मॉड्यूल build.gradle स्क्रिप्ट से अतिरिक्त बिल्ड स्क्रिप्ट लागू करें। एक अच्छा स्थान एंड्रॉइड ग्रेडल प्लगइन के नीचे है।

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

android {
    compileSdkVersion 21
    // etc.

निर्माण processApplicationId.gradle

नीचे एक res / xml / प्लेसहोल्डर बिल्ड स्क्रिप्ट के लिए काम कर रहा स्रोत है। एक बेहतर प्रलेखित संस्करण जीथब पर उपलब्ध है । सुधार और विस्तार का स्वागत है।

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

मेरी राय में संसाधन स्ट्रिंग के लिए प्लेसहोल्डर समर्थन को जोड़ने की कोई आवश्यकता नहीं है। उपरोक्त उपयोग के मामले के लिए कम से कम इसकी आवश्यकता नहीं है। हालाँकि आप आसानी से स्क्रिप्ट को रिज / xml / डायरेक्टरी में प्लेसहोल्डर्स को न केवल बदल सकते हैं, बल्कि रेस / वैल्यू / डायरेक्टरी में भी बदल सकते हैं।


6

मैं बल्कि सिरिल और रोसोवती के बीच मिश्रण को प्राथमिकता दूंगा। मुझे लगता है कि अधिक सरल है, आपके पास केवल दो संशोधन हैं।

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 द्वारा नमूना के आधार पर, यहां मेरा समाधान है, जो पिछले समाधानों में दो मुद्दों को हल करता है:

  • निर्माण निर्देशिका में मानों को बदलने वाले समाधान। xml संसाधनों का एक पूर्ण पुनर्निर्माण का कारण बनता है (सभी ड्राफ़्ट के aapt सहित)

  • एक अज्ञात कारण से, IntelliJ (और शायद एंड्रॉइड स्टूडियो) संसाधनों को विश्वसनीय रूप से संसाधित नहीं करता है, जिससे बिल्ड में अन-रिप्लेस्ड .res-autoप्रदाता प्राधिकरण होते हैं

यह नया समाधान एक नया कार्य बनाकर ग्रेडल तरीके से चीजों को अधिक करता है और इनपुट और आउटपुट फ़ाइलों को परिभाषित करके वृद्धिशील बनाता है।

  1. एक फ़ाइल बनाएँ (उदाहरण में मैंने इसे एक variantsनिर्देशिका में रखा ), संसाधन xml फ़ाइल की तरह स्वरूपित, जिसमें स्ट्रिंग संसाधन शामिल हैं। इन्हें ऐप के संसाधनों में मिला दिया जाएगा, और .res-autoमूल्यों की किसी भी घटना को उदाहरण के लिए वेरिएंट के पैकेज नाम से बदल दिया जाएगा।<string name="search_provider">.res-auto.MySearchProvider</string>

  2. जोड़ने build_extras.gradleसे फ़ाइल इस सार अपनी परियोजना के लिए और मुख्य से इसे संदर्भ build.gradleजोड़कर apply 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


अपडेट: यह विधि केवल एंड्रॉइड 2.2.1 और बाद में काम करती है। पहले के प्लेटफार्मों के लिए, इस उत्तर को देखें , जिसमें समस्याओं का अपना सेट है, क्योंकि नया प्रकट विलय अभी भी किनारों के आसपास बहुत मोटा है ...


आप अपना वेरिएंट निर्देशिका कहां रख रहे हैं? मेरे पास एक बड़ा एंड्रॉइड स्टूडियो प्रोजेक्ट है जो कई एंड्रॉइड मॉड्यूल पर निर्भर करता है - मेरा मुख्य ऐप और कई एंड्रॉइड लाइब्रेरी मॉड्यूल। मैं कमांड लाइन से निर्माण कर सकता हूं, लेकिन जब मैं एंड्रॉइड स्टूडियो के अंदर से निर्माण करने की कोशिश करता हूं तो यह variants/res-auto-values.xmlरिश्तेदार के लिए दिखता है /Applications/Android Studio.app/bin/। यानी मुझे इसके लिए कोई FileNotFoundException नहीं मिली /Applications/Android Studio.app/bin/variants/res-auto-values.xml। मैं एक मैक पर चल रहा हूँ। यह एक महान समाधान है, लेकिन मैं इसे टीम के अन्य सदस्यों के लिए IDE में काम करना पसंद करूंगा।
user1978019

1
मेरी अपनी समस्या तय की। ग्रैड का उपयोग करके पथों को हल करने के लिए प्रकट होता है System.getProperty("user.dir"), जो एंड्रॉइड स्टूडियो द्वारा निर्मित किए जाने पर एक अलग परिणाम देता है। समाधान परियोजना निर्देशिका के सापेक्ष पथ का उपयोग करना है, जिसे वापस लौटा दिया गया है gradle.startParameter.getProjectDir()। मेरी टिप्पणी पाऊर की लिंक्ड जिस्ट में भी देखें।
user1978019

खबरदार, संसाधन-आधारित प्राधिकरण केवल एंड्रॉइड 2.2.1 और बाद में काम करते हैं: github.com/android/platform_frameworks_base/commit/…
पियरे-ल्यूक पाओर

3

मैंने गितुब नमूना परियोजना के साथ एक ब्लॉगपोस्ट लिखा है जो साइरस की तुलना में इस समस्या (और इसी तरह की अन्य समस्याओं) से थोड़ा अलग तरीके से निपटता है।

http://brad-android.blogspot.com/2013/08/android-gradle-building-unique-build.html


2

दुर्भाग्य से, एंड्रॉइड प्लगइन का वर्तमान संस्करण (0.4.1) इसके लिए एक अच्छा समाधान प्रदान नहीं करता है। मेरे पास अभी तक यह प्रयास करने का समय नहीं है, लेकिन इस समस्या के लिए एक संभावित समाधान एक स्ट्रिंग संसाधन का उपयोग करना होगा @string/provider_authority, और इसे प्रकट में उपयोग करना होगा android:authority="@string/provider_authority":। फिर आपके पास res/values/provider.xmlप्रत्येक बिल्ड प्रकार के Res फ़ोल्डर में है जो प्राधिकरण को ओवरराइड करना चाहिए, आपके मामले में यह होगाsrc/debug/res

मैंने मक्खी पर xml फ़ाइल उत्पन्न करने में देखा है, लेकिन फिर से, प्लगइन के वर्तमान संस्करण में इसके लिए कोई अच्छा हुक नहीं लगता है। मैं हालांकि एक सुविधा अनुरोध में डालने की सलाह दूंगा, मैं सोच सकता हूं कि अधिक लोग इसी मुद्दे पर चलेंगे।


हाय मार्कस, आपके उत्तर के लिए धन्यवाद। आपका सुझाया हुआ समाधान केवल वही है जिसके बारे में मैं अभी सोच सकता हूँ। लेकिन मेरी समस्या यह है, मुझे नहीं पता कि ग्रैडल को कैसे हासिल किया जाए।
मंटासव

2

इस पोस्ट में जवाब मेरे लिए काम करता है।

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

मैं 3 अलग-अलग स्वादों का उपयोग करता हूं इसलिए मैं प्रत्येक स्वाद में सामग्री प्रदाता के साथ 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 पार्सिंग दृष्टिकोणों की तुलना में अधिक सुरुचिपूर्ण दृष्टिकोण पाया।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.