Menggunakan tipe build di Gradle untuk menjalankan aplikasi yang sama yang menggunakan ContentProvider di satu perangkat

124

Saya telah menyiapkan Gradle untuk menambahkan akhiran nama paket ke aplikasi debug saya sehingga saya bisa memiliki versi rilis yang saya gunakan dan versi debug di satu ponsel. Saya merujuk ini: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

File build.gradle saya terlihat seperti ini:

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

Semuanya berfungsi dengan baik sampai saya mulai menggunakan ContentProvider di aplikasi saya. Saya mendapat:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Saya memahami bahwa ini terjadi karena dua aplikasi (rilis dan debug) mendaftarkan otoritas Penyedia Konten yang sama.

Saya melihat satu kemungkinan untuk menyelesaikan ini. Jika saya mengerti dengan benar, Anda harus dapat menentukan file yang berbeda untuk digunakan saat membangun. Kemudian saya harus dapat menempatkan otoritas yang berbeda dalam file sumber daya yang berbeda (dan dari otoritas set Manifes sebagai sumber daya string) dan memberi tahu Gradle untuk menggunakan sumber daya yang berbeda untuk debug build. Apakah itu mungkin? Jika ya, maka petunjuk tentang cara mencapainya akan luar biasa!

Atau mungkinkah mengubah Manifes secara langsung menggunakan Gradle? Solusi lain tentang cara menjalankan aplikasi yang sama dengan ContentProvider di satu perangkat selalu diterima.

MantasV
sumber
Bagi mereka yang tertarik melacak dukungan upstream untuk kasus penggunaan ini: laporan bug AOSP . Sikap "resmi" saat ini adalah menggunakan solusi utama yang nyata .
desseim

Jawaban:

226

Tidak ada jawaban yang memuaskan saya, namun Liberty dekat. Jadi begini cara saya melakukannya. Pertama-tama saat saya bekerja dengan:

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

Tujuan saya adalah menjalankan Debugversi bersama dengan Releaseversi pada perangkat yang sama menggunakan yang sama ContentProvider.


Dalam build.gradle akhiran set aplikasi Anda untuk build Debug:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Dalam file AndroidManifest.xml set android:authoritiesproperti Anda ContentProvider:

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

Di properti kumpulan kodeAUTHORITY yang dapat digunakan di mana pun diperlukan dalam implementasi Anda:

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

Tip: SebelumnyaBuildConfig.PACKAGE_NAME

Itu dia! Ini akan bekerja seperti pesona. Teruskan membaca jika Anda menggunakan SyncAdapter!


Pembaruan untuk SyncAdapter (14.11.2014)

Sekali lagi saya akan mulai dengan pengaturan saya saat ini:

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

Pada dasarnya, jika Anda perlu menyesuaikan beberapa nilai untuk berbagai build, Anda dapat melakukannya dari file build.gradle:

  • gunakan buildConfigField untuk mengaksesnya dariBuildConfig.java kelas
  • gunakan resValue untuk mengaksesnya dari sumber daya misalnya @ string / your_value

Sebagai alternatif sumber daya, Anda bisa membuat direktori buildType atau ragam terpisah dan mengganti XML atau nilai di dalamnya. Namun, saya tidak akan menggunakannya dalam contoh di bawah ini.

Contoh


Dalam file build.gradle tambahkan yang berikut ini:

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

Anda akan melihat hasil di kelas BuildConfig.java

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

dan di 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>

Di authenticator.xml Anda menggunakan sumber daya yang ditentukan dalam file 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"
/>

Di syncadapter.xml Anda, gunakan sumber daya yang sama lagi dan @ string / otoritas juga

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

Kiat: pelengkapan otomatis (Ctrl + Spasi) tidak berfungsi untuk sumber daya yang dihasilkan ini sehingga Anda harus mengetiknya secara manual

Damian Petla
sumber
7
Jawaban terbaik IMHO. Contoh singkat dan sederhana yang bagus.
rekire
Ya, itulah solusi terbaik yang pernah saya lihat sejauh ini. Terima kasih banyak telah berbagi! Masih ada masalah lain yang tidak terkait dengan ini karena saya perlu memperbarui Intent eksplisit di file preferensi.xml untuk menggunakan nama paket baru. code.google.com/p/android/issues/detail?id=57460
Bernd S
@BerndS Saya telah memposting komentar untuk masalah Anda dengan solusi. Anda perlu memahami bahwa mengubah applicationId dengan menggantinya atau menyetel sufiks tidak memengaruhi paket java. Ini hanya pengenal aplikasi Anda dan dipisahkan dari paket java. Lihat jawaban saya untuk pertanyaan lain stackoverflow.com/questions/24178007/…
Damian Petla
1
@JJD Modifikasi yang Anda tautkan akan berfungsi tanpa skrip build kustom apa pun. Jika Anda ingin menggunakan placeholder $ {applicationId} untuk sync_adapter.xml, authenticator.xml Anda harus menyesuaikan skrip build.gradle Anda. Saya melihat bahwa Anda telah melakukan banyak hal dalam skrip build.gradle Anda sehingga Anda merasa nyaman dengan idenya. Apakah Anda mengikuti instruksi dalam jawaban saya dan apakah masih tidak berhasil?
Rob Meeuwisse
1
Saya telah memperbarui jawaban saya dengan cara untuk syncadapter
Damian Petla
39

Kiat sistem build Android baru: penggantian nama otoritas ContentProvider

Saya kira Anda semua telah mendengar tentang sistem build berbasis Android Gradle yang baru. Jujur saja, sistem build baru ini merupakan langkah maju yang besar dibandingkan dengan yang sebelumnya. Ini belum final (pada tulisan ini, versi terbaru adalah 0.4.2) tetapi Anda sudah dapat menggunakannya dengan aman di sebagian besar proyek Anda.

Saya secara pribadi telah mengalihkan sebagian besar proyek saya ke sistem build baru ini dan mengalami beberapa masalah karena kurangnya dukungan dalam beberapa situasi tertentu. Salah satunya adalah dukungan untuk penggantian nama otoritas ContentProvider

Sistem baru Android yang dibangun memungkinkan Anda menangani berbagai jenis aplikasi hanya dengan mengubah nama paket pada waktu pembuatan. Salah satu keuntungan utama dari peningkatan ini adalah Anda sekarang dapat menginstal dua versi berbeda dari aplikasi Anda pada perangkat yang sama pada waktu yang sama. Misalnya:

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

Dengan menggunakan konfigurasi Gradle, Anda dapat menyusun dua APK berbeda:

• APK debug dengan nama paket com.cyrilmottier.android.app.debug • APK rilis dengan nama paket com.cyrilmottier.android.app

Satu-satunya masalah adalah Anda tidak akan dapat menginstal dua APK secara bersamaan jika keduanya mengekspos ContentProvider dengan otoritas yang sama. Secara logis kita perlu mengganti nama otoritas tergantung pada jenis build saat ini… tetapi ini tidak didukung oleh sistem build Gradle (belum? ... Saya yakin ini akan segera diperbaiki). Jadi, inilah cara untuk pergi:

Pertama, kita perlu memindahkan deklarasi ContentProvider manifes Android penyedia ke jenis build yang sesuai. Untuk melakukan itu kami hanya memiliki:

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>

Pastikan untuk menghapus deklarasi ContentProvider dari AndroidManifest.xml di src / main / karena Gradle tidak tahu cara menggabungkan ContentProviders yang memiliki nama yang sama tetapi otoritas yang berbeda.

Akhirnya kita mungkin perlu mengakses otoritas dalam kode. Ini dapat dilakukan dengan mudah menggunakan file BuildConfig dan metode 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
       }
   }
}

Berkat solusi ini, Anda akan dapat menggunakan BuildConfig.PROVIDER_AUTHORITY di ProviderContract Anda dan menginstal dua versi aplikasi yang berbeda pada saat yang sama.


Asli di Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

Cyril Mottier
sumber
1
Untuk seseorang yang tidak bisa menjalankan gradle, karena kesalahan sintaxy. Inilah jawabannya: stackoverflow.com/questions/20678118/…
Renan Franca
23

Meskipun contoh Cyril berfungsi dengan baik jika Anda hanya memiliki beberapa jenis build, ini akan menjadi rumit dengan cepat jika Anda memiliki banyak jenis build dan / atau ragam produk karena Anda perlu mempertahankan banyak AndroidManifest.xml yang berbeda.

Proyek kami terdiri dari 3 jenis build berbeda dan 6 ragam dengan total 18 varian build, jadi kami menambahkan dukungan untuk ".res-auto" di otoritas ContentProvider, yang memperluas ke nama paket saat ini dan menghilangkan kebutuhan untuk mempertahankan AndroidManifest.xml yang berbeda

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

Kode contoh dapat ditemukan di sini: https://gist.github.com/cmelchior/6988275

Christian Melchior
sumber
Saya beralih menggunakan sesuatu yang sangat mirip untuk proyek saya juga, karena saya memiliki masalah yang sama dengan build flavours. Pendekatan ini bekerja dengan sangat baik untuk saat ini.
MantasV
2
FileWriter membuat masalah pada file utf-8, setidaknya di Mac OS saya. Saya mengubah baris terkait menjadi: def writer = new OutputStreamWriter (new FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi
Ini sangat bagus, terima kasih! Saya telah membuat perubahan kecil untuk mencegah kerusakan dengan string yang diformat. gist.github.com/paour/8475929
Pierre-Luc Paour
Ini sangat membantu, tetapi saya mengalami masalah di mana itu tidak akan dibangun setelah pembersihan karena tidak ada file values.xml di folder build pada tahap processManifest. Itu tidak akan ada sampai tahap processResources, pada titik mana sudah terlambat untuk memodifikasi manifes, jadi untuk mengganti .res-auto di file manifes dan nilai, saya pikir Anda akan memerlukan 2 fungsi, satu dipanggil berdasarkan varian. processManifest.doLast, yang lain dipanggil oleh variant.processResources.doLast.
Niall
20

Karena plugin versi 0.8.3 (sebenarnya 0.8.1 tetapi tidak berfungsi dengan benar) Anda dapat menentukan sumber daya dalam file build sehingga ini bisa menjadi solusi yang lebih bersih karena Anda tidak perlu membuat file string atau debug / rilis tambahan folder.

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>
rciovati.dll
sumber
2
Waspadalah, otoritas berbasis sumber daya hanya berfungsi di Android 2.2.1 dan yang lebih baru: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour
Terimakasih atas klarifikasinya.
rciovati
1
ini juga sangat berguna di searchable.xml untuk android: searchSuggestAuthority, karena di sana Anda tidak dapat menggunakan $ {applicationId}
user114676
13

Saya tidak tahu apakah ada yang menyebutkannya. Sebenarnya setelah plugin gradle android 0.10+, penggabungan manifes akan memberikan dukungan resmi untuk fungsi ini: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

Di AndroidManifest.xml, Anda dapat menggunakan $ {packageName} seperti ini:

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

Dan di build.gradle Anda, Anda dapat memiliki:

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

Lihat contoh lengkapnya di sini: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

dan di sini: https://code.google.com/p/anymemo/source/browse/build.gradle#41

Kebebasan
sumber
Ini adalah berita bagus, tetapi tampaknya ini bukan solusi lengkap dalam kasus <searchable> elemen yang perlu merujuk ke autoritas, karena ini bukan bagian dari manifes (tetapi strategi penggabungan yang ada berfungsi untuk file ini, tidak seperti Manifest).
Pierre-Luc Paour
1
Anda tidak perlu menggunakan rasa untuk ini, ini berfungsi dengan tipe build juga. Selain itu, alangkah baiknya untuk menyebutkan bahwa Anda dapat menggunakan BuildConfig.PACKAGE_NAME untuk mendapatkan referensi statis ke paket Anda. Ini berguna untuk penyedia materi yang otoritasnya perlu diketahui pada waktu proses untuk meminta penyedia materi.
Matt Wolfe
1
Juga harus diperbarui untuk menggunakan $ {applicationId} sebagai ganti $ {packageName} untuk android: otoritas
Bernd S
8

Gunakan ${applicationId}placeholder di xml danBuildConfig.APPLICATION_ID dalam kode.

Anda perlu memperluas skrip build untuk mengaktifkan placeholder di file xml selain manifes. Anda dapat menggunakan direktori sumber per varian build untuk menyediakan versi file xml yang berbeda, tetapi pemeliharaan akan menjadi sangat rumit dengan sangat cepat.

AndroidManifest.xml

Anda dapat menggunakan placeholder applicationId di luar kotak di manifes. Nyatakan penyedia Anda seperti ini:

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

Perhatikan ${applicationId}sedikit. Ini diganti pada waktu build dengan applicationId sebenarnya untuk varian build yang sedang dibuat.

Dalam kode

ContentProvider Anda perlu membuat string otoritas dalam kode. Itu bisa menggunakan kelas BuildConfig.

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

Perhatikan BuildConfig.APPLICATION_ID sedikit. Ini adalah kelas yang dihasilkan dengan applicationId sebenarnya untuk varian build yang sedang dibuat.

res / xml / files, misalnya syncadapter.xml, accountauthenticator.xml

Jika Anda ingin menggunakan Adaptor Sinkronisasi, Anda perlu menyediakan meta-data untuk ContentProvider dan AccountManager dalam file xml di direktori res / xml /. Placeholder applicationId tidak didukung di sini. Tetapi Anda dapat memperluas sendiri skrip build untuk meretasnya.

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

Sekali lagi, perhatikan ${applicationId}. Ini hanya berfungsi jika Anda menambahkan skrip gradle di bawah ini ke root modul Anda dan menerapkannya dari build.gradle.

build.gradle

Terapkan skrip build tambahan dari skrip modul build.gradle. Tempat yang bagus adalah di bawah plugin gradle Android.

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

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

Di bawah ini adalah sumber yang berfungsi untuk skrip build res / xml / placeholder. Versi terdokumentasi yang lebih baik tersedia di github . Perbaikan dan ekstensi dipersilakan.

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

Menurut pendapat saya, tidak perlu menambahkan dukungan placeholder untuk string sumber daya. Untuk kasus penggunaan di atas setidaknya tidak diperlukan. Namun, Anda dapat dengan mudah mengubah skrip untuk tidak hanya mengganti placeholder di direktori res / xml /, tetapi juga di direktori res / values ​​/.

Rob Meeuwisse
sumber
6

Saya lebih suka campuran antara Cyril dan rciovati. Menurut saya lebih sederhana, Anda hanya memiliki dua modifikasi.

The build.gradleterlihat seperti:

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

Dan 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>
icastell.dll
sumber
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>

Kode:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
maros136
sumber
4

Berdasarkan sampel oleh @ChristianMelchior, inilah solusi saya, yang memperbaiki dua masalah dalam solusi sebelumnya:

  • solusi yang mengubah values.xml dalam direktori build menyebabkan rekondisi penuh sumber daya (termasuk aapt dari semua drawable)

  • karena alasan yang tidak diketahui, IntelliJ (dan mungkin Android Studio) tidak andal memproses sumber daya, menyebabkan build berisi .res-autootoritas penyedia yang tidak diganti

Solusi baru ini melakukan lebih banyak hal secara Gradle dengan membuat tugas baru dan memungkinkan build inkremental dengan menentukan file input dan output.

  1. buat file (dalam contoh saya taruh di variantsdirektori), diformat seperti file xml sumber daya, yang berisi sumber daya string. Ini akan digabungkan ke dalam sumber daya aplikasi, dan kemunculan apa pun .res-autodi nilai akan diganti dengan nama paket varian, misalnya<string name="search_provider">.res-auto.MySearchProvider</string>

  2. tambahkan build_extras.gradlefile dari inti ini ke proyek Anda dan referensikan dari utama build.gradledengan menambahkan apply from: './build_extras.gradle'suatu tempat di atas androidblok

  3. pastikan Anda menyetel nama paket default dengan menambahkannya ke android.defaultConfigblokbuild.gradle

  4. di AndroidManifest.xmldan file konfigurasi lainnya (seperti xml/searchable.xmluntuk penyedia penelusuran pelengkapan otomatis), rujuk penyedia (misalnya @string/search_provider)

  5. jika Anda ingin mendapatkan nama yang sama, Anda dapat menggunakan BuildConfig.PACKAGE_NAMEvariabel, misalnyaBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Pembaruan: metode ini hanya berfungsi di Android 2.2.1 dan yang lebih baru. Untuk platform sebelumnya, lihat jawaban ini , yang memiliki rangkaian masalahnya sendiri, karena penggabungan manifes baru masih sangat sulit di tepinya…

Pierre-Luc Paour
sumber
Di mana Anda meletakkan direktori varian? Saya memiliki satu proyek Android Studio besar yang bergantung pada beberapa modul Android - aplikasi utama saya dan beberapa modul Android Library. Saya bisa membangun dari baris perintah, tetapi ketika saya mencoba membangun dari dalam Android Studio, tampilannya variants/res-auto-values.xmlrelatif terhadap /Applications/Android Studio.app/bin/. yaitu saya tidak mendapatkan FileNotFoundException untuk /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Saya menggunakan mac. Ini adalah solusi yang bagus, tetapi saya ingin membuatnya berfungsi di IDE untuk anggota tim lainnya.
pengguna1978019
1
Memperbaiki masalah saya sendiri. Gradle tampaknya menyelesaikan jalur yang digunakan System.getProperty("user.dir"), yang mengembalikan hasil berbeda saat dipanggil oleh build Android Studio. Solusinya adalah dengan menggunakan jalur yang berhubungan dengan direktori proyek, yang dikembalikan dengan gradle.startParameter.getProjectDir(). Lihat komentar saya di inti terkait Paour juga.
pengguna1978019
Waspadalah, otoritas berbasis sumber daya hanya bekerja di Android 2.2.1 dan yang lebih baru: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour
2

Sayangnya, versi plugin android saat ini (0.4.1) tampaknya tidak memberikan solusi yang baik untuk ini. Saya belum punya waktu untuk mencoba ini, tetapi solusi yang mungkin untuk masalah ini adalah menggunakan sumber daya string@string/provider_authority , dan penggunaan yang di manifes: android:authority="@string/provider_authority". Anda kemudian memiliki a res/values/provider.xmldi folder res dari setiap jenis build yang harus menggantikan otoritas, dalam kasus Anda ini akan terjadisrc/debug/res

Saya telah mencari cara untuk membuat file xml dengan cepat, tetapi sekali lagi, tampaknya tidak ada kaitan yang bagus untuk itu di versi plugin saat ini. Saya akan merekomendasikan memasukkan permintaan fitur, saya dapat membayangkan lebih banyak orang akan mengalami masalah yang sama ini.

Marcus Forsell Stahre
sumber
Hai Marcus, terima kasih atas balasan Anda. Solusi yang Anda sarankan adalah satu-satunya solusi yang dapat saya pikirkan saat ini. Tapi masalah saya adalah, saya tidak tahu bagaimana mencapainya dengan Gradle.
MantasV
2

Jawaban dalam posting ini berhasil untuk saya.

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

Saya menggunakan 3 rasa berbeda jadi saya membuat 3 manifes dengan penyedia konten di setiap rasa seperti yang dikatakan kevinrschultz:

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

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

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

Manifes utama Anda tidak termasuk penyedia:

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

Dan manifes Anda dalam setiap rasa Anda termasuk penyedia.

Gratis:

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

Dibayar:

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

Lain:

<?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>
jcmore2
sumber
0

Mengapa tidak menambahkan ini saja?

type.packageNameSuffix = ". $ type.name"

guydemossyrock
sumber
0

Solusi saya adalah menggunakan pengganti placeholder di AndroidManifest.xml. Ini juga menangani packageNameSuffixatribut sehingga Anda dapat memiliki debugdan releasejuga build kustom lainnya di perangkat yang sama.

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

Saya memilikinya di a gist jika Anda ingin melihat apakah itu berkembang nanti.

Saya menemukan pendekatan yang lebih elegan daripada beberapa sumber daya dan pendekatan parsing XML.

Saad Farooq
sumber