การใช้ประเภทการสร้างใน Gradle เพื่อเรียกใช้แอปเดียวกับที่ใช้ ContentProvider บนอุปกรณ์เดียว


124

ฉันได้ตั้งค่า Gradle เพื่อเพิ่มคำต่อท้ายชื่อแพ็กเกจให้กับแอปดีบักของฉันเพื่อที่ฉันจะได้มีเวอร์ชันที่วางจำหน่ายที่ฉันใช้และเวอร์ชันดีบักในโทรศัพท์เครื่องเดียว ฉันกำลังอ้างถึงสิ่งนี้: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

ไฟล์ build.gradle ของฉันมีลักษณะดังนี้:

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

ทุกอย่างทำงานได้ดีจนกว่าฉันจะเริ่มใช้ ContentProvider ในแอปของฉัน ฉันเข้าใจ:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

ฉันเข้าใจว่าสิ่งนี้เกิดขึ้นเนื่องจากสองแอป (รุ่นและการแก้ไขข้อบกพร่อง) กำลังลงทะเบียนสิทธิ์ ContentProvider เดียวกัน

ฉันเห็นความเป็นไปได้อย่างหนึ่งที่จะแก้ปัญหานี้ ถ้าฉันเข้าใจถูกต้องคุณควรระบุไฟล์ต่างๆที่จะใช้ในการสร้างได้ จากนั้นฉันควรจะสามารถใส่หน่วยงานที่แตกต่างกันในไฟล์ทรัพยากรที่แตกต่างกัน (และจาก Manifest set authority เป็นทรัพยากรสตริง) และบอก Gradle ให้ใช้ทรัพยากรที่แตกต่างกันสำหรับ debug build เป็นไปได้หรือไม่ ถ้าใช่คำแนะนำใด ๆ เกี่ยวกับวิธีการบรรลุสิ่งนั้นจะยอดเยี่ยมมาก!

หรืออาจเป็นไปได้ที่จะแก้ไข Manifest โดยตรงโดยใช้ Gradle? โซลูชันอื่น ๆ เกี่ยวกับวิธีเรียกใช้แอปเดียวกันกับ ContentProvider บนอุปกรณ์เดียวยินดีต้อนรับเสมอ


สำหรับผู้ที่สนใจในการติดตามสนับสนุนต้นน้ำสำหรับกรณีการใช้งานนี้: AOSP รายงานข้อผิดพลาด จุดยืนในปัจจุบัน "อย่างเป็นทางการ" คือการใช้วิธีการลบล้างรายการ
desseim

คำตอบ:


226

ไม่มีคำตอบใดที่ทำให้ฉันพอใจ แต่ Liberty อยู่ใกล้ นี่คือวิธีที่ฉันทำ ก่อนอื่นในขณะนี้ฉันทำงานกับ:

  • Android Studio เบต้า 0.8.2
  • ปลั๊กอิน Gradle 0.12. +
  • Gradle 1.12

เป้าหมายของฉันคือเรียกใช้Debugเวอร์ชันพร้อมกับReleaseเวอร์ชันบนอุปกรณ์เดียวกันโดยใช้เวอร์ชันเดียวกันContentProviderเวอร์ชันบนเครื่องเดียวกันโดยใช้แบบเดียวกัน


ในbuild.gradleของ app set ต่อท้ายสำหรับ Debug build:

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)

อีกครั้งฉันจะเริ่มต้นด้วยการตั้งค่าปัจจุบันของฉัน:

  • Android Studio เบต้า 0.9.2
  • ปลั๊กอิน Gradle 0.14.1
  • Gradle 2.1

โดยพื้นฐานแล้วหากคุณต้องการปรับแต่งค่าบางอย่างสำหรับงานสร้างต่างๆคุณสามารถทำได้จากไฟล์ build.gradle:

  • ใช้buildConfigFieldเพื่อเข้าถึงจากBuildConfig.javaคลาส
  • ใช้resValueเพื่อเข้าถึงจากทรัพยากรเช่น@ string / your_value

คุณสามารถสร้าง buildType หรือไดเร็กทอรีรสชาติแยกต่างหากและแทนที่ XML หรือค่าภายในทรัพยากรได้ อย่างไรก็ตามฉันจะไม่ใช้มันในตัวอย่างด้านล่าง

ตัวอย่าง


ในไฟล์build.gradle ให้เพิ่มสิ่งต่อไปนี้:

defaultConfig {
    resValue "string", "your_authorities", applicationId + '.provider'
    resValue "string", "account_type", "your.syncadapter.type"
    buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type"'
}

buildTypes {
    debug {
        applicationIdSuffix ".debug"
        resValue "string", "your_authorities", defaultConfig.applicationId + '.debug.provider'
        resValue "string", "account_type", "your.syncadapter.type.debug"
        buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type.debug"'
    }
}

คุณจะเห็นผลลัพธ์ในคลาสBuildConfig.java

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

และในบิลด์ / สร้าง / 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>

ในauthenticator.xmlใช้ทรัพยากรที่ระบุในไฟล์ build.gradle

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accountType="@string/account_type"
                       android:icon="@drawable/ic_launcher"
                       android:smallIcon="@drawable/ic_launcher"
                       android:label="@string/app_name"
/>

ในsyncadapter.xmlของคุณให้ใช้ทรัพยากรเดียวกันอีกครั้งและ@ string / authorityด้วย

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/authorities"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:supportsUploading="false"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
        />

เคล็ดลับ: การเติมข้อความอัตโนมัติ (Ctrl + Space) ใช้ไม่ได้กับทรัพยากรที่สร้างขึ้นเหล่านี้ดังนั้นคุณต้องพิมพ์ด้วยตนเอง


7
คำตอบที่ดีที่สุด IMHO ตัวอย่างสั้น ๆ และเรียบง่าย
rekire

ใช่นั่นเป็นวิธีแก้ปัญหาที่ดีที่สุดที่ฉันเคยเห็นมา ขอบคุณมากสำหรับการแบ่งปัน! ยังคงมีปัญหาอื่นที่ไม่เกี่ยวข้องกับสิ่งนี้เนื่องจากฉันต้องอัปเดต Intent ที่ชัดเจนในไฟล์การตั้งค่า. xml เพื่อใช้ชื่อแพ็กเกจใหม่ code.google.com/p/android/issues/detail?id=57460
Bernd S

@BerndS ฉันได้โพสต์ความคิดเห็นเกี่ยวกับปัญหาของคุณพร้อมวิธีแก้ปัญหา คุณต้องเข้าใจว่าการเปลี่ยน applicationId โดยการแทนที่หรือการตั้งค่าส่วนต่อท้ายไม่มีผลต่อแพ็คเกจจาวา มันเป็นเพียงตัวระบุของแอปของคุณและแยกออกจากแพ็คเกจจาวา ดูคำตอบของฉันสำหรับคำถามอื่นstackoverflow.com/questions/24178007/…
Damian Petla

1
@JJD การแก้ไขที่คุณเชื่อมโยงจะใช้งานได้โดยไม่ต้องสร้างสคริปต์การสร้างที่กำหนดเอง หากคุณต้องการใช้ตัวยึดตำแหน่ง $ {applicationId} สำหรับ sync_adapter.xml, authenticator.xml คุณต้องปรับแต่งสคริปต์ build.gradle ของคุณ ฉันเห็นว่าคุณได้ทำหลายอย่างในสคริปต์ build.gradle ของคุณแล้วดังนั้นคุณจึงรู้สึกสบายใจกับแนวคิดนี้ คุณทำตามคำแนะนำในคำตอบของฉันและยังไม่ได้ผลหรือไม่?
Rob Meeuwisse

1
ฉันได้อัปเดตคำตอบของฉันด้วยวิธีการสำหรับ syncadapter
Damian Petla

39

เคล็ดลับระบบบิวด์ Android ใหม่: การเปลี่ยนชื่อผู้มีอำนาจ ContentProvider

ฉันเดาว่าทุกคนคงเคยได้ยินเกี่ยวกับระบบสร้างที่ใช้ Android Gradle ใหม่ พูดตามตรงระบบสร้างใหม่นี้เป็นก้าวที่ยิ่งใหญ่เมื่อเทียบกับระบบก่อนหน้านี้ ยังไม่สิ้นสุด (ขณะที่เขียนนี้เวอร์ชันล่าสุดคือ 0.4.2) แต่คุณสามารถใช้งานได้อย่างปลอดภัยในโครงการส่วนใหญ่ของคุณแล้ว

ฉันได้เปลี่ยนโปรเจ็กต์ส่วนใหญ่เป็นระบบบิลด์ใหม่นี้แล้วและมีปัญหาบางอย่างเนื่องจากการขาดการสนับสนุนในบางสถานการณ์ หนึ่งในนั้นคือการสนับสนุนสำหรับการเปลี่ยนชื่อผู้มีอำนาจของ ContentProvider

ระบบที่สร้างขึ้นใหม่ของ Android ช่วยให้คุณจัดการกับแอปประเภทต่างๆได้เพียงแค่แก้ไขชื่อแพ็กเกจในเวลาสร้าง ข้อได้เปรียบหลักอย่างหนึ่งของการปรับปรุงนี้คือตอนนี้คุณสามารถติดตั้งแอปของคุณได้สองเวอร์ชันที่แตกต่างกันในอุปกรณ์เครื่องเดียวกันในเวลาเดียวกัน ตัวอย่างเช่น:

android {
   compileSdkVersion 17
   buildToolsVersion "17.0.0"

   defaultConfig {
       packageName "com.cyrilmottier.android.app"
       versionCode 1
       versionName "1"
       minSdkVersion 14 // Listen to +Jeff Gilfelt advices :)
       targetSdkVersion 17
   }

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

ด้วยการใช้การกำหนดค่า Gradle คุณสามารถรวบรวม APK ที่แตกต่างกันสองรายการ:

• APK ดีบักที่มีชื่อแพ็กเกจ com.cyrilmottier.android.app.debug • APK รุ่นที่มีชื่อแพ็คเกจ com.cyrilmottier.android.app

ปัญหาเดียวที่เกิดขึ้นคือคุณจะไม่สามารถติดตั้ง APK สองรายการพร้อมกันได้หากทั้งคู่เปิดเผย ContentProvider ที่มีหน่วยงานเดียวกัน ค่อนข้างมีเหตุผลเราจำเป็นต้องเปลี่ยนชื่อผู้มีอำนาจขึ้นอยู่กับประเภทการสร้างปัจจุบัน ... แต่ระบบการสร้าง Gradle ไม่รองรับ (หรือยัง? ... ฉันแน่ใจว่าจะได้รับการแก้ไขในไม่ช้า) นี่คือวิธีที่จะไป:

ก่อนอื่นเราต้องย้ายการประกาศ ContentProvider ของผู้ให้บริการ Android ไปยังประเภทการสร้างที่เหมาะสม ในการดำเนินการนั้นเราจะมี:

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>

ตรวจสอบให้แน่ใจว่าได้ลบการประกาศ ContentProvider ออกจาก AndroidManifest.xml ใน src / main / เนื่องจาก Gradle ไม่ทราบวิธีการรวม 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 ใน ProviderContract ของคุณและติดตั้งแอปของคุณสองเวอร์ชันในเวลาเดียวกัน


ต้นฉบับบน Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ


1
สำหรับคนที่ไม่สามารถเรียกใช้ gradle ได้เนื่องจากข้อผิดพลาดของ sintaxy นี่คือคำตอบ: stackoverflow.com/questions/20678118/…
Renan Franca

23

แม้ว่าตัวอย่างของ Cyril จะใช้งานได้ดีหากคุณมีงานสร้างเพียงไม่กี่ประเภท แต่ก็มีความซับซ้อนอย่างรวดเร็วหากคุณมีประเภทการสร้างและ / หรือรสชาติของผลิตภัณฑ์จำนวนมากเนื่องจากคุณต้องการรักษา AndroidManifest.xml ที่แตกต่างกันจำนวนมาก

โครงการของเราประกอบด้วย 3 ประเภทการสร้างที่แตกต่างกันและ 6 รสชาติรวม 18 รูปแบบการสร้างดังนั้นเราจึงเพิ่มการรองรับ ".res-auto" ในหน่วยงาน ContentProvider ซึ่งขยายเป็นชื่อแพ็กเกจปัจจุบันและไม่จำเป็นต้องดูแล AndroidManifest.xml ที่แตกต่างกัน

/**
 * Version 1.1.
 *
 * Add support for installing multiple variants of the same app which have a
 * content provider. Do this by overriding occurrences of ".res-auto" in
 * android:authorities with the current package name (which should be unique)
 *
 * V1.0 : Initial version
 * V1.1 : Support for ".res-auto" in strings added, 
 *        eg. use "<string name="auth">.res-auto.path.to.provider</string>"
 *
 */
def overrideProviderAuthority(buildVariant) {
    def flavor = buildVariant.productFlavors.get(0).name
    def buildType = buildVariant.buildType.name
    def pathToManifest = "${buildDir}/manifests/${flavor}/${buildType}/AndroidManifest.xml"

    def ns = new groovy.xml.Namespace("http://schemas.android.com/apk/res/android", "android")
    def xml = new XmlParser().parse(pathToManifest)
    def variantPackageName = xml.@package

    // Update all content providers
    xml.application.provider.each { provider ->
        def newAuthorities = provider.attribute(ns.authorities).replaceAll('.res-auto', variantPackageName)
        provider.attributes().put(ns.authorities, newAuthorities)
    }

    // Save modified AndroidManifest back into build dir
    saveXML(pathToManifest, xml)

    // Also make sure that all strings with ".res-auto" are expanded automagically
    def pathToValues = "${buildDir}/res/all/${flavor}/${buildType}/values/values.xml"
    xml = new XmlParser().parse(pathToValues)
    xml.findAll{it.name() == 'string'}.each{item ->
        if (!item.value().isEmpty() && item.value()[0].startsWith(".res-auto")) {
            item.value()[0] = item.value()[0].replace(".res-auto", variantPackageName)
        }
    }
    saveXML(pathToValues, xml)
}

def saveXML(pathToFile, xml) {
    def writer = new FileWriter(pathToFile)
    def printer = new XmlNodePrinter(new PrintWriter(writer))
    printer.preserveWhitespace = true
    printer.print(xml)
}

// Post processing of AndroidManifest.xml for supporting provider authorities
// across build variants.
android.applicationVariants.all { variant ->
    variant.processManifest.doLast {
        overrideProviderAuthority(variant)
    }
}

สามารถดูโค้ดตัวอย่างได้ที่นี่: https://gist.github.com/cmelchior/6988275


ฉันเปลี่ยนไปใช้สิ่งที่คล้ายกันมากสำหรับโครงการของฉันเช่นกันเพราะฉันมีปัญหาเดียวกันกับการสร้างรสชาติ วิธีนี้ใช้ได้ดีมากในตอนนี้
MantasV

2
FileWriter สร้างปัญหากับไฟล์ utf-8 อย่างน้อยบน Mac OS ของฉัน ฉันเปลี่ยนบรรทัดที่เกี่ยวข้องเป็น: def writer = OutputStreamWriter ใหม่ (FileOutputStream (pathToFile ใหม่), "UTF-8")
Reza Mohammadi

มันยอดเยี่ยมมากขอบคุณ! ฉันได้ทำการเปลี่ยนแปลงเล็กน้อยเพื่อป้องกันการแตกด้วยสตริงที่จัดรูปแบบแล้ว gist.github.com/paour/8475929
Pierre-Luc Paour

สิ่งนี้มีประโยชน์มาก แต่ฉันพบปัญหาที่มันไม่สามารถสร้างได้หลังจากล้างเพราะไม่มีไฟล์ values.xml ในโฟลเดอร์ build ที่ขั้นตอน processManifest สิ่งนี้ไม่มีอยู่จนกว่าขั้นตอน processResources จะสายเกินไปที่จะแก้ไขไฟล์ Manifest ดังนั้นในการแทนที่. 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
ระวังเจ้าหน้าที่ที่ใช้ทรัพยากรจะทำงานบน Android 2.2.1 ขึ้นไปเท่านั้น: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour

ขอขอบคุณสำหรับการชี้แจง.
rciovati

1
สิ่งนี้มีประโยชน์มากใน searchchable.xml สำหรับ Android: searchSuggestAuthority เนื่องจากคุณไม่สามารถใช้ $ {applicationId} ได้
user114676

13

ฉันไม่รู้ว่ามีใครพูดถึงหรือเปล่า จริงๆแล้วหลังจาก android gradle plugin 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


นี่เป็นข่าวดี แต่ดูเหมือนว่าจะไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์ในกรณีขององค์ประกอบ <searchable> ซึ่งจำเป็นต้องอ้างอิงความเป็นอัตโนมัติเนื่องจากสิ่งเหล่านี้ไม่ได้เป็นส่วนหนึ่งของรายการ (แต่กลยุทธ์การผสานที่มีอยู่จะใช้ได้กับไฟล์เหล่านี้ ซึ่งแตกต่างจาก Manifest)
Pierre-Luc Paour

1
คุณไม่จำเป็นต้องใช้รสชาติสำหรับสิ่งนี้มันใช้ได้กับประเภทการสร้างเช่นกัน นอกจากนี้ยังเป็นการดีที่จะระบุว่าคุณสามารถใช้ BuildConfig.PACKAGE_NAME เพื่อรับข้อมูลอ้างอิงแบบคงที่ไปยังแพ็คเกจของคุณได้ สิ่งนี้มีประโยชน์สำหรับผู้ให้บริการเนื้อหาที่จำเป็นต้องทราบหน่วยงานที่รันไทม์เพื่อสอบถามผู้ให้บริการเนื้อหา
Matt Wolfe

1
ควรอัปเดตให้ใช้ $ {applicationId} แทน $ {packageName} สำหรับ android: authority
Bernd S

8

ใช้${applicationId}ตัวยึดตำแหน่งใน xml และBuildConfig.APPLICATION_IDในโค้ด

คุณจะต้องขยายบิลด์สคริปต์เพื่อเปิดใช้งานตัวยึดในไฟล์ xml นอกเหนือจากไฟล์ Manifest คุณสามารถใช้ซอร์สไดเร็กทอรีต่อตัวแปรบิวด์เพื่อจัดเตรียมไฟล์ xml เวอร์ชันต่างๆ แต่การดูแลรักษาจะยุ่งยากอย่างรวดเร็ว

AndroidManifest.xml

คุณสามารถใช้ตัวยึด applicationId นอกกรอบในรายการ ประกาศผู้ให้บริการของคุณดังนี้:

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

สังเกต${applicationId}บิต สิ่งนี้จะถูกแทนที่ในเวลาสร้างด้วย applicationId จริงสำหรับตัวแปรการสร้างที่กำลังสร้าง

ในรหัส

ContentProvider ของคุณจำเป็นต้องสร้างสตริงสิทธิ์ในโค้ด สามารถใช้คลาส BuildConfig

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

สังเกตBuildConfig.APPLICATION_IDบิต เป็นคลาสที่สร้างขึ้นพร้อมด้วย applicationId จริงสำหรับตัวแปรการสร้างที่กำลังสร้าง

res / xml / files เช่น syncadapter.xml, accountauthenticator.xml

ถ้าคุณต้องการใช้ Sync Adapter คุณจะต้องให้ข้อมูลเมตาสำหรับ ContentProvider และ AccountManager ในไฟล์ xml ในไดเร็กทอรี res / xml / ที่นี่ไม่รองรับตัวยึดตำแหน่ง applicationId แต่คุณสามารถขยายสคริปต์บิลด์ด้วยตัวคุณเองเพื่อแฮ็คได้

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:allowParallelSyncs="false"
    android:contentAuthority="${applicationId}.DatabaseProvider"
    android:isAlwaysSyncable="true"
    android:supportsUploading="true"
    android:userVisible="true" />

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:icon="@drawable/ic_launcher"
    android:label="@string/account_authenticator_label"
    android:smallIcon="@drawable/ic_launcher" />

อีกครั้งโปรดสังเกตไฟล์${applicationId}. สิ่งนี้ใช้ได้เฉพาะเมื่อคุณเพิ่มสคริปต์ gradle ด้านล่างลงในรูทของโมดูลของคุณและใช้จาก build.gradle

build.gradle

ใช้สคริปต์ build พิเศษจากสคริปต์ build.gradle ของโมดูล สถานที่ที่ดีอยู่ด้านล่างปลั๊กอิน Android gradle

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

android {
    compileSdkVersion 21
    // etc.

สร้าง processApplicationId.gradle

ด้านล่างนี้เป็นแหล่งที่ใช้งานได้สำหรับสคริปต์ build res / xml / placeholder รุ่นเอกสารที่ดีกว่าที่มีอยู่บนGitHub ยินดีต้อนรับการปรับปรุงและส่วนขยาย

def replace(File file, String target, String replacement) {
    def result = false;

    def reader = new FileReader(file)
    def lines = reader.readLines()
    reader.close()

    def writer = new FileWriter(file)
    lines.each { line ->
        String replacedLine = line.replace(target, replacement)
        writer.write(replacedLine)
        writer.write("\n")
        result = result || !replacedLine.equals(line)
    }
    writer.close()

    return result
}

def processXmlFile(File file, String applicationId) {
    if (replace(file, "\${applicationId}", applicationId)) {
        logger.info("Processed \${applicationId} in $file")
    }
}

def processXmlDir(File dir, String applicationId) {
    dir.list().each { entry ->
        File file = new File(dir, entry)
        if (file.isFile()) {
            processXmlFile(file, applicationId)
        }
    }
}

android.applicationVariants.all { variant ->
    variant.mergeResources.doLast {
        def applicationId = variant.mergedFlavor.applicationId + (variant.buildType.applicationIdSuffix == null ? "" : variant.buildType.applicationIdSuffix)
        def path = "${buildDir}/intermediates/res/${variant.dirName}/xml/"
        processXmlDir(new File(path), applicationId)
    }
}

strings.xml

ในความคิดของฉันไม่จำเป็นต้องเพิ่มการสนับสนุนตัวยึดตำแหน่งสำหรับสตริงทรัพยากร สำหรับกรณีการใช้งานข้างต้นอย่างน้อยก็ไม่จำเป็น อย่างไรก็ตามคุณสามารถเปลี่ยนสคริปต์ได้อย่างง่ายดายเพื่อไม่เพียงแทนที่ตัวยึดตำแหน่งในไดเร็กทอรี res / xml / แต่ยังอยู่ในไดเร็กทอรี res / values ​​/


6

ฉันค่อนข้างจะชอบส่วนผสมระหว่าง Cyril และ rciovati ฉันคิดว่าง่ายกว่านี้คุณมีการปรับเปลี่ยนเพียงสองครั้ง

build.gradleดูเหมือนว่า:

android {
    ...
    productFlavors {
        production {
            packageName "package.name.production"
            resValue "string", "authority", "package.name.production.provider"
            buildConfigField "String", "AUTHORITY", "package.name.production.provider"
        }

        testing {
            packageName "package.name.debug"
            resValue "string", "authority", "package.name.debug.provider"
            buildConfigField "String", "AUTHORITY", "package.name.debug.provider"
        }
    }
    ...
}

และAndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="package.name" >

    <application
        ...>

        <provider android:name=".contentprovider.Provider" android:authorities="@string/authority" />

    </application>
</manifest>

5

gradle.build

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.example.awsomeapp"
        minSdkVersion 9
        targetSdkVersion 23
        versionCode 1
        versionName "1.0.0"
    }

    productFlavors
    {
        prod {
            applicationId = "com.example.awsomeapp"
        }

        demo {
            applicationId = "com.example.awsomeapp.demo"
            versionName = defaultConfig.versionName + ".DEMO"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
            debuggable false
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }

        debug {
            applicationIdSuffix ".debug"
            versionNameSuffix = ".DEBUG"
            debuggable true
        }
    }

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // rename the apk
            def file = output.outputFile;
            def newName;
            newName = file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk");
            newName = newName.replace(project.name, "awsomeapp");
            output.outputFile = new File(file.parent, newName);
        }

        //Generate values Content Authority and Account Type used in Sync Adapter, Content Provider, Authenticator
        def valueAccountType = applicationId + '.account'
        def valueContentAuthority = applicationId + '.authority'

        //generate fields in Resource string file generated.xml
        resValue "string", "content_authority", valueContentAuthority
        resValue "string", "account_type", valueAccountType

        //generate fields in BuildConfig class
        buildConfigField "String", "ACCOUNT_TYPE", '"'+valueAccountType+'"'
        buildConfigField "String", "CONTENT_AUTHORITY", '"'+valueContentAuthority+'"'

        //replace field ${valueContentAuthority} in AndroidManifest.xml
        mergedFlavor.manifestPlaceholders = [ valueContentAuthority: valueContentAuthority ]
    }
}

authenticator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:smallIcon="@drawable/ic_launcher" />

sync_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/content_authority"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
              android:supportsUploading="true"/>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0.0" package="com.example.awsomeapp">

    <uses-permission android:name="android.permission.GET_ACCOUNTS"/><!-- SyncAdapter and GCM requires a Google account. -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>

    <!-- GCM Creates a custom permission so only this app can receive its messages. -->
    <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>

    <application....
    .......

        <!-- Stub Authenticator --> 
        <service 
                android:name="com.example.awsomeapp.service.authenticator.CAuthenticatorService"
                android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>
            <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/>
        </service>
        <!--  -->

        <!-- Sync Adapter -->
        <service
                android:name="com.example.awsomeapp.service.sync.CSyncService"
                android:exported="true"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" />
        </service>
        <!--  -->

        <!-- Content Provider -->
        <provider android:authorities="${valueContentAuthority}"
            android:exported="false" 
            android:name="com.example.awsomeapp.database.contentprovider.CProvider">
        </provider>
        <!--  --> 
    </application>
</manifest>

รหัส:

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

4

จากตัวอย่างโดย @ChristianMelchior นี่คือวิธีแก้ปัญหาของฉันซึ่งแก้ไขปัญหาสองประการในโซลูชันก่อนหน้านี้:

  • โซลูชันที่เปลี่ยน values.xml ในไดเร็กทอรี build ทำให้เกิดการสร้างรีซอร์สใหม่ทั้งหมด (รวมถึง aapt ของ drawables ทั้งหมด)

  • ด้วยเหตุผลที่ไม่ทราบสาเหตุ IntelliJ (และอาจเป็น Android Studio) ไม่สามารถประมวลผลทรัพยากรได้อย่างน่าเชื่อถือทำให้โครงสร้างมี.res-autoผู้ให้บริการที่ไม่ได้ถูกแทนที่

โซลูชันใหม่นี้ทำสิ่งต่าง ๆ ได้มากกว่าวิธี Gradle โดยการสร้างงานใหม่และอนุญาตให้มีการสร้างส่วนเพิ่มโดยการกำหนดไฟล์อินพุตและเอาต์พุต

  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


อัปเดต: วิธีนี้ใช้ได้กับ Android 2.2.1 ขึ้นไปเท่านั้น สำหรับแพลตฟอร์มก่อนหน้านี้โปรดดูคำตอบนี้ซึ่งมีชุดปัญหาของตัวเองเนื่องจากการควบรวมรายการใหม่ยังคงหยาบมากรอบ ๆ ขอบ ...


คุณวางไดเร็กทอรีตัวแปรของคุณไว้ที่ใด ฉันมีโครงการ Android Studio ขนาดใหญ่หนึ่งโครงการที่ขึ้นอยู่กับโมดูล Android หลายตัว - แอปหลักของฉันและโมดูลห้องสมุด Android หลายตัว ฉันสามารถสร้างจากบรรทัดคำสั่ง แต่เมื่อฉันพยายามที่จะสร้างจากภายในสตูดิโอของ Android จะมองหาเมื่อเทียบกับvariants/res-auto-values.xml /Applications/Android Studio.app/bin/เช่นฉันไม่ได้รับ FileNotFoundException สำหรับ/Applications/Android Studio.app/bin/variants/res-auto-values.xml. ฉันใช้ Mac นี่เป็นวิธีแก้ปัญหาที่ยอดเยี่ยม แต่ฉันอยากให้มันทำงานใน IDE สำหรับสมาชิกคนอื่น ๆ ในทีม
user1978019

1
แก้ไขปัญหาของตัวเอง Gradle ดูเหมือนจะแก้ไขเส้นทางโดยใช้System.getProperty("user.dir")ซึ่งจะส่งคืนผลลัพธ์ที่แตกต่างกันเมื่อเรียกใช้โดย Android Studio build gradle.startParameter.getProjectDir()การแก้ปัญหาคือการใช้เส้นทางเทียบกับไดเรกทอรีโครงการซึ่งจะกลับมาพร้อมกับ ดูความคิดเห็นของฉันในส่วนสำคัญที่เชื่อมโยงของ Paour เช่นกัน
user1978019

ระวังเจ้าหน้าที่ที่ใช้ทรัพยากรจะทำงานบน Android 2.2.1 ขึ้นไปเท่านั้น: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour

3

ฉันได้เขียนบล็อกโพสต์ด้วยโครงการตัวอย่าง Github ที่จัดการกับปัญหานี้ (และปัญหาอื่น ๆ ที่คล้ายคลึงกัน) ด้วยวิธีที่แตกต่างจาก Cyril เล็กน้อย

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


2

น่าเสียดายที่ปลั๊กอิน android เวอร์ชันปัจจุบัน (0.4.1) ดูเหมือนจะไม่เป็นทางออกที่ดีสำหรับสิ่งนี้ ฉันไม่ได้มีเวลาที่จะลองนี้ยัง แต่วิธีแก้ปัญหาที่เป็นไปได้สำหรับปัญหานี้จะใช้ทรัพยากรสตริงและการใช้งานที่ประจักษ์ในนี้:@string/provider_authority android:authority="@string/provider_authority"จากนั้นคุณมีไฟล์res/values/provider.xmlโฟลเดอร์ res ของบิลด์แต่ละประเภทที่ควรลบล้างอำนาจในกรณีของคุณสิ่งนี้จะเป็นsrc/debug/res

ฉันได้ตรวจสอบการสร้างไฟล์ xml ทันที แต่ดูเหมือนว่าจะไม่มีตะขอที่ดีสำหรับมันในปลั๊กอินเวอร์ชันปัจจุบัน ฉันขอแนะนำให้ใส่คำขอคุณลักษณะ แต่ฉันสามารถจินตนาการได้ว่าจะมีผู้คนจำนวนมากพบปัญหาเดียวกันนี้


สวัสดีมาร์คัสขอบคุณสำหรับการตอบกลับของคุณ วิธีแก้ปัญหาที่คุณแนะนำเป็นวิธีเดียวที่ฉันคิดได้ในตอนนี้ แต่ปัญหาของฉันคือฉันไม่รู้ว่าจะทำอย่างไรให้สำเร็จด้วย Gradle
MantasV

2

คำตอบในโพสต์นี้ใช้ได้กับฉัน

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

ฉันใช้ 3 รสชาติที่แตกต่างกันดังนั้นฉันจึงสร้างรายการ 3 รายการกับผู้ให้บริการเนื้อหาในแต่ละรสชาติตามที่ kevinrschultz กล่าวว่า:

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

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

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

Manifest หลักของคุณไม่รวมผู้ให้บริการ:

<?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 หลายวิธี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.