Tek bir cihazda ContentProvider kullanan aynı uygulamayı çalıştırmak için Gradle'da derleme türlerini kullanma


124

Gradle'ı hata ayıklama uygulamama paket adı son eki ekleyecek şekilde ayarladım, böylece kullandığım sürüm sürümüne ve bir telefonda hata ayıklama sürümüne sahip olabilirim. Buna referans veriyordum : http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Build.gradle dosyam şöyle görünüyor:

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

Uygulamamda bir ContentProvider kullanmaya başlayana kadar her şey yolunda gidiyor. Alırım:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Bunun, iki uygulamanın (yayınlama ve hata ayıklama) aynı ContentProvider yetkisini kaydetmesi nedeniyle olduğunu anlıyorum.

Bunu çözmek için bir olasılık görüyorum. Doğru anladıysam, oluştururken kullanmak için farklı dosyalar belirtebilmelisiniz. Daha sonra farklı kaynak dosyalarına farklı otoriteler koyabilmeliyim (ve Manifest'ten dize kaynağı olarak ayarlanan otoriteden) ve Gradle'a hata ayıklama derlemesi için farklı kaynaklar kullanmasını söylemeliyim. Mümkün mü? Eğer evet ise, o zaman bunu nasıl başaracağınıza dair herhangi bir ipucu harika olurdu!

Veya Gradle kullanarak Manifest'i doğrudan değiştirmek mümkün olabilir mi? Aynı uygulamanın ContentProvider ile tek bir cihazda nasıl çalıştırılacağına dair başka herhangi bir çözüm her zaman memnuniyetle karşılanır.


Bu kullanım örneği için yukarı akış desteğini izlemek isteyenler için: AOSP hata raporu . "Resmi" mevcut duruş, açık bir şekilde ağır basan çözümü kullanmaktır .
desseim

Yanıtlar:


226

Mevcut cevapların hiçbiri beni tatmin etmedi, ancak Liberty yakındı. Yani ben böyle yapıyorum. Öncelikle şu anda çalışıyorum:

  • Android Studio Beta 0.8.2
  • Gradle eklentisi 0.12. +
  • Gradle 1.12

Benim hedef çalıştırmaktır Debugbirlikte sürümü Releaseaynı kullanılarak aynı cihaz üzerinde sürümü ContentProvider.


Uygulamanızın build.gradle içinde Hata ayıklama derlemesi için son eki ayarlayın:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

In AndroidManifest.xml dosya kümesi android:authorities, aramalarınızdan mülkiyet ContentProvider:

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

Uygulamanızda ihtiyaç duyulan her yerde kullanılabilecek kod seti AUTHORITYözelliğinizde:

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

İpucu: ÖncedenBuildConfig.PACKAGE_NAME

Bu kadar! Bir cazibe gibi çalışacak. SyncAdapter kullanıyorsanız okumaya devam edin!


SyncAdapter Güncellemesi (14.11.2014)

Bir kez daha mevcut kurulumuma başlayacağım:

  • Android Studio Beta 0.9.2
  • Gradle eklentisi 0.14.1
  • Gradle 2.1

Temel olarak, farklı yapılar için bazı değerleri özelleştirmeniz gerekiyorsa, bunu build.gradle dosyasından yapabilirsiniz:

  • kullanmak buildConfigField bu bilgilere erişmek içinBuildConfig.java sınıfın
  • Kullanım resValue erişmek BT kaynaklarını örn gelen @ dize / your_value

Kaynaklar için bir alternatif olarak, ayrı buildType veya lezzet dizinleri oluşturabilir ve bunların içindeki XML'leri veya değerleri geçersiz kılabilirsiniz. Ancak, aşağıdaki örnekte kullanmayacağım.

Misal


Gelen build.gradle dosyasına aşağıdakileri ekleyin:

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

Sonuçları BuildConfig.java sınıfında göreceksiniz

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

ve derleme / oluşturulmuş / res / oluşturulmuş / hata ayıklama / değerler / oluşturulan.xml'de

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

Senin içinde authenticator.xml kullanımı kaynak build.gradle dosyasında belirtilen

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

Senin içinde syncadapter.xml yine aynı kaynağı kullanmak ve @ dize / yetkilileri de

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

İpucu: Otomatik tamamlama (Ctrl + Boşluk) bu oluşturulan kaynaklar için çalışmaz, bu nedenle bunları manuel olarak yazmanız gerekir


7
En iyi cevap IMHO. Güzel, kısa ve basit bir örnek.
2014

Evet, şimdiye kadar gördüğüm en iyi çözüm bu. Paylaştığınız için çok teşekkür ederim! Yeni paket adını kullanmak için bir Preferences.xml dosyasındaki açık bir Intent'i güncellemem gerektiğinden yine de bununla ilgisi olmayan başka bir sorun var. code.google.com/p/android/issues/detail?id=57460
Bernd S

@BerndS Çözüm ile ilgili sorununuz için bir yorum gönderdim. Uygulama kimliğini değiştirerek veya son eki ayarlayarak değiştirmenin java paketlerini etkilemediğini anlamanız gerekir. Yalnızca uygulamanızın bir tanımlayıcısıdır ve java paketlerinden ayrıştırılmıştır. Başka bir soruya verdiğim yanıtı görün stackoverflow.com/questions/24178007/…
Damian Petla

1
@JJD Bağlandığınız değişiklikler herhangi bir özel yapım komut dosyası olmadan çalışacaktır. Sync_adapter.xml için $ {applicationId} yer tutucularını kullanmak istiyorsanız, authenticator.xml build.gradle komut dosyanızı özelleştirmelisiniz. Build.gradle betiğinizde zaten çok şey yaptığınızı görüyorum, bu yüzden fikirden memnunsunuz. Benim yönergeleri takip ettiniz cevap ve hala işe yaramadı?
Rob Meeuwisse

1
Yanıtımı syncadapter için nasıl yapılır ile güncelledim
Damian Petla

39

Yeni Android derleme sistemi ipucu: ContentProvider yetkili yeniden adlandırma

Sanırım hepiniz yeni Android Gradle tabanlı derleme sistemini duymuşsunuzdur. Dürüst olalım, bu yeni inşa sistemi bir öncekine kıyasla ileriye doğru büyük bir adımdır. Henüz nihai değil (bu yazı itibariyle en son sürüm 0.4.2'dir) ancak zaten birçok projenizde güvenle kullanabilirsiniz.

Şahsen, projemin çoğunu bu yeni inşa sistemine geçirdim ve bazı özel durumlarda destek eksikliği nedeniyle bazı sorunlar yaşadım. Bunlardan biri ContentProvider yetkisini yeniden adlandırma desteğidir

Yeni Android yerleşik sistem, yalnızca derleme zamanında paket adını değiştirerek farklı uygulama türleriyle başa çıkmanıza olanak tanır. Bu iyileştirmenin ana avantajlarından biri, artık uygulamanızın iki farklı sürümünü aynı cihaza aynı anda yükleyebilmenizdir. Örneğin:

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

Böyle bir Gradle yapılandırması kullanarak iki farklı APK'yi bir araya getirebilirsiniz:

• com.cyrilmottier.android.app.debug paket adıyla bir hata ayıklama APK'sı • com.cyrilmottier.android.app paket adına sahip bir yayın APK'sı

Bununla ilgili tek sorun, her ikisi de aynı yetkililere bir ContentProvider'ı ifşa ederse, iki APK'yi aynı anda yükleyemeyeceğinizdir. Oldukça mantıklı bir şekilde, otoriteyi mevcut yapı türüne bağlı olarak yeniden adlandırmamız gerekiyor… ancak bu Gradle inşa sistemi tarafından desteklenmiyor (henüz? ... Yakında düzeltileceğinden eminim). İşte gitmenin bir yolu:

Öncelikle, sağlayıcı Android manifest ContentProvider bildirimini uygun derleme türüne taşımamız gerekir. Bunu yapmak için basitçe sahip olacağız:

src / debug / AndroidManifest.xml'sinde

<?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 / açma / 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>

ContentProvider bildirimini src / main / içindeki AndroidManifest.xml'den kaldırdığınızdan emin olun çünkü Gradle aynı ada ancak farklı bir yetkiye sahip ContentProviders'ı nasıl birleştireceğini bilmiyor.

Son olarak, koddaki otoriteye erişmemiz gerekebilir. Bu, BuildConfig dosyası ve buildConfig yöntemi kullanılarak oldukça kolay bir şekilde yapılabilir:

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

Bu geçici çözüm sayesinde, ProviderContract'ınızda BuildConfig.PROVIDER_AUTHORITY kullanabilecek ve aynı anda uygulamanızın iki farklı sürümünü yükleyebileceksiniz.


Google + 'da Orijinal: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ


1
Gradle'ı çalıştıramayan biri için, sintaxy hatası nedeniyle. İşte cevap: stackoverflow.com/questions/20678118/…
Renan Franca

23

Cyril'in örneği, yalnızca birkaç yapı türüne sahipseniz harika çalışsa da, birçok farklı AndroidManifest.xml dosyasını korumanız gerektiğinden, birçok yapı türünüz ve / veya ürün çeşidiniz varsa, hızla karmaşıklaşır.

Projemiz 3 farklı derleme türü ve toplamda 18 derleme varyantı olan 6 tattan oluşuyor, bu nedenle ContentProvider yetkililerine mevcut paket adına genişleyen ve farklı AndroidManifest.xml'i koruma ihtiyacını ortadan kaldıran ".res-auto" desteği ekledik.

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

Örnek kod burada bulunabilir: https://gist.github.com/cmelchior/6988275


Ben de projem için çok benzer bir şey kullanmaya geçtim, çünkü yapı tatlarıyla aynı sorunu yaşadım. Bu yaklaşım şimdilik çok iyi çalışıyor.
MantasV

2
FileWriter, utf-8 dosyalarında, en azından Mac OS işletim sistemimde sorun çıkarıyor. İlgili satırı şu şekilde değiştirdim: def writer = new OutputStreamWriter (yeni FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi

Bu gerçekten harika, teşekkürler! Biçimlendirilmiş dizelerle kırılmayı önlemek için küçük bir değişiklik yaptım. gist.github.com/paour/8475929
Pierre-Luc Paour

Bu çok yardımcı oldu, ancak işlemden sonra derleme klasöründe processManifest aşamasında values.xml dosyası olmadığı için temizlemeden sonra oluşturulamayacağı bir sorunla karşılaştım. Bu, processResources aşamasına kadar mevcut değildir, bu noktada manifest'i değiştirmek için çok geçtir, bu nedenle hem manifest hem de değerler dosyalarındaki .res-auto'yu değiştirmek için, biri varyant tarafından çağrılan 2 işleve ihtiyacınız olduğunu düşünüyorum. processManifest.doLast, diğeri variant.processResources.doLast tarafından çağrılır.
Niall

20

Eklenti sürüm 0.8.3'ten (aslında 0.8.1 ama düzgün çalışmıyordu) bu yana, derleme dosyasında kaynakları tanımlayabilirsiniz, böylece bu daha temiz bir çözüm olabilir çünkü dizeler dosyaları veya ek hata ayıklama / yayınlama yapmanız gerekmez. klasörler.

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
Dikkat edin, kaynak tabanlı yetkililer yalnızca Android 2.2.1 ve sonraki sürümlerde çalışır: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour

Açıklama için teşekkürler.
rciovati

1
bu aynı zamanda android için searchable.xml'de de çok kullanışlıdır: searchSuggestAuthority, çünkü orada $ {applicationId} kullanamazsınız
user114676

13

Bundan bahseden var mı bilmiyorum. Aslında, android gradle eklentisi 0.10+ sonrasında, manifest birleşmesi bu işlev için resmi destek sağlayacaktır: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

AndroidManifest.xml'de, $ {packageName} öğesini şu şekilde kullanabilirsiniz:

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

Ve build.gradle'ınızda şunlara sahip olabilirsiniz:

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

Tam örneğe buradan bakın: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

ve burada: https://code.google.com/p/anymemo/source/browse/build.gradle#41


Bu harika bir haber, ancak otoriteye başvurması gereken <aranabilir> öğeler söz konusu olduğunda tam bir çözüm gibi görünmüyor, çünkü bunlar manifestin parçası değiller (ancak mevcut birleştirme stratejileri bu dosyalar için işe yarıyor, Manifest'in aksine).
Pierre-Luc Paour

1
Bunun için tat kullanmak zorunda değilsiniz, aynı zamanda yapı türleriyle de çalışır. Ayrıca, paketinize statik bir referans almak için BuildConfig.PACKAGE_NAME'i kullanabileceğinizi belirtmek güzel olur. Bu, yetkinin içerik sağlayıcıyı sorgulamak için çalışma zamanında bilinmesi gereken içerik sağlayıcılar için yararlıdır.
Matt Wolfe

1
Ayrıca android için $ {packageName} yerine $ {applicationId} kullanılacak şekilde güncellenmelidir: yetkililer
Bernd S

8

Yer ${applicationId}tutucuları xml'de ve BuildConfig.APPLICATION_IDkodda kullanın .

Manifest dışındaki xml dosyalarındaki yer tutucuları etkinleştirmek için derleme komut dosyasını genişletmeniz gerekecektir. Xml dosyalarının farklı sürümlerini sağlamak için derleme varyantı başına bir kaynak dizini kullanabilirsiniz, ancak bakım çok hızlı bir şekilde zahmetli hale gelecektir.

AndroidManifest.xml

Manifestteki kutunun dışında applicationId yer tutucusunu kullanabilirsiniz. Sağlayıcınızı şu şekilde beyan edin:

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

${applicationId}Biraz not alın . Bu, derleme sırasında, oluşturulmakta olan derleme varyantı için gerçek uygulama kimliği ile değiştirilir.

Kodda

İçerik Sağlayıcınızın, kodda yetki dizesini oluşturması gerekir. BuildConfig sınıfını kullanabilir.

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

BuildConfig.APPLICATION_IDBiraz not alın . Oluşturulmakta olan yapı varyantı için gerçek uygulama kimliği ile oluşturulmuş bir sınıftır.

res / xml / files, örneğin syncadapter.xml, accountauthenticator.xml

Bir Eşitleme Bağdaştırıcısı kullanmak istiyorsanız, res / xml / dizinindeki xml dosyalarında ContentProvider ve AccountManager için meta veri sağlamanız gerekecektir. ApplicationId yer tutucusu burada desteklenmemektedir. Ancak, derleme komut dosyasını hacklemek için kendiniz genişletebilirsiniz.

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

Yine ${applicationId},. Bu sadece aşağıdaki gradle komut dosyasını modülünüzün köküne eklerseniz ve build.gradle'dan uygularsanız çalışır.

build.gradle

Build.gradle modül komut dosyasından ekstra derleme komut dosyasını uygulayın. Android gradle eklentisinin altında iyi bir yer var.

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

android {
    compileSdkVersion 21
    // etc.

birikmesi processApplicationId.gradle

Res / xml / yer tutucu derleme komut dosyası için çalışma kaynağı aşağıdadır. Github'da daha iyi belgelenmiş bir sürüm mevcuttur . İyileştirmeler ve uzantılar açıktır.

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

Bence kaynak dizeleri için yer tutucu desteği eklemeye gerek yok. Yukarıdaki kullanım durumu için en azından gerekli değildir. Bununla birlikte, betiği yalnızca res / xml / dizinindeki değil, aynı zamanda res / values ​​/ dizinindeki yer tutucuları da değiştirecek şekilde kolayca değiştirebilirsiniz.


6

Cyril ve rciovati'nin bir karışımını tercih ederim. Bence daha basit, sadece iki modifikasyonunuz var.

build.gradleBakışlar ister:

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

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

Kod:

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

4

@ChristianMelchior'un örneğine dayanarak, işte benim çözümüm, önceki çözümlerde iki sorunu düzeltir:

  • yapı dizinindeki values.xml dosyasını değiştiren çözümler, kaynakların tam olarak yeniden oluşturulmasına neden olur (tüm çekilebilir öğelerin aapt dahil)

  • bilinmeyen bir nedenden ötürü, IntelliJ (ve muhtemelen Android Studio) kaynakları güvenilir bir şekilde işlemez ve yapının değiştirilmemiş .res-autosağlayıcı yetkililerini içermesine neden olur

Bu yeni çözüm, yeni bir görev oluşturarak işleri daha Gradle yöntemiyle yapar ve girdi ve çıktı dosyalarını tanımlayarak artımlı derlemelere izin verir.

  1. variantsdize kaynaklarını içeren bir kaynak xml dosyası gibi biçimlendirilmiş bir dosya oluşturun (örnekte onu bir dizine koydum ). Bunlar, uygulamanın kaynaklarıyla birleştirilecek .res-autove değerlerde herhangi bir oluşum , varyantın paket adıyla değiştirilecektir, örneğin<string name="search_provider">.res-auto.MySearchProvider</string>

  2. eklemek build_extras.gradledosyayı bu özünden projenize ve ana onu referans build.gradleekleyerek apply from: './build_extras.gradle'yukarıda bir yerlerde androidbloğu

  3. android.defaultConfigbloğuna ekleyerek varsayılan bir paket adı belirlediğinizden emin olun .build.gradle

  4. içinde AndroidManifest.xmlve diğer yapılandırma dosyalarında ( xml/searchable.xmlotomatik tamamlama arama sağlayıcıları için olduğu gibi @string/search_provider) sağlayıcıya başvurun (örneğin )

  5. aynı adı almanız gerekiyorsa BuildConfig.PACKAGE_NAMEdeğişkeni kullanabilirsiniz, örneğinBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Güncelleme: Bu yöntem yalnızca Android 2.2.1 ve sonraki sürümlerde çalışır. Daha önceki platformlar için, kendi sorunları olan bu yanıta bakın , çünkü yeni manifesto birleşmesi hala sınırlarda çok sert.


Varyantlar dizininizi nereye koyuyorsunuz? Birkaç Android modülüne bağlı olan büyük bir Android Studio projem var - ana uygulamam ve birkaç Android Kitaplığı modülü. Ben komut satırından inşa edebilirsiniz, ama Android Studio içinden yapı çalıştığınızda o arar variants/res-auto-values.xmlgöre /Applications/Android Studio.app/bin/. yani /Applications/Android Studio.app/bin/variants/res-auto-values.xml. için FileNotFoundException alamıyorum . Mac'te koşuyorum. Bu harika bir çözüm, ancak ekibin diğer üyeleri için IDE'de çalışmasını çok isterim.
user1978019

1
Kendi sorunumu çözdüm. Gradle System.getProperty("user.dir"), Android Studio derlemesi tarafından çağrıldığında farklı bir sonuç döndüren kullanarak yolları çözüyor gibi görünüyor . Çözüm, ile döndürülen proje dizinine göre yolu kullanmaktır gradle.startParameter.getProjectDir(). Yorumumu Paour'un bağlantılı özetinde de görün.
user1978019

Dikkat edin, kaynak tabanlı yetkililer yalnızca Android 2.2.1 ve sonraki sürümlerde çalışır: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour


2

Ne yazık ki, android eklentisinin mevcut sürümü (0.4.1) bunun için iyi bir çözüm sağlamıyor gibi görünüyor. Henüz bu denemek için zamanım olmadı, ama bu sorun için olası bir geçici çözüm bir dize kaynağı kullanmak olacaktır @string/provider_authorityve kullanımı o manifest'te: android:authority="@string/provider_authority". Daha sonra res/values/provider.xml, her yapı türünün res klasöründe, yetkiyi geçersiz kılması gereken bir var, sizin durumunuzda busrc/debug/res

Xml dosyasını anında oluşturmaya baktım, ancak yine, eklentinin mevcut sürümünde bunun için iyi bir kanca görünmüyor. Yine de bir özellik talebinde bulunmanızı öneririm, daha fazla insanın aynı sorunla karşılaşacağını hayal edebiliyorum.


Merhaba Marcus, cevabınız için teşekkürler. Önerdiğiniz çözüm şimdilik kendim için düşünebildiğim tek çözüm. Ama benim sorunum, bunu Gradle ile nasıl başaracağımı bilmiyorum.
MantasV

2

Bu gönderideki cevap benim için çalışıyor.

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

Ben 3 farklı tat kullanıyorum, böylece kevinrschultz'un dediği gibi her tatta içerik sağlayıcı ile 3 manifest oluşturuyorum:

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

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

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

Ana Manifest'iniz sağlayıcıları içermez:

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

Ve sağlayıcı dahil her lezzette tezahürünüz.

Bedava:

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

Ücretli:

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

Diğer:

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

Çözümüm, içinde yer tutucu değiştirme kullanmaktır AndroidManifest.xml. Ayrıca kolları packageNameSuffixsahip böylece niteliklerini debugve releasediğer özel aynı cihaz üzerinde inşa yanı sıra.

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

Üzerinde var gist sonra gelişip gelişmeyeceğini görmek istersen .

Çoklu kaynaklardan ve XML ayrıştırma yaklaşımlarından daha zarif bir yaklaşım buldum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.