Sử dụng các loại bản dựng trong Gradle để chạy cùng một ứng dụng sử dụng ContentProvider trên một thiết bị


124

Tôi đã thiết lập Gradle để thêm hậu tố tên gói vào ứng dụng gỡ lỗi của mình để tôi có thể có phiên bản phát hành mà tôi đang sử dụng và gỡ lỗi phiên bản trên một điện thoại. Tôi đã tham khảo điều này: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Tệp build.gradle của tôi trông như thế này:

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

Mọi thứ hoạt động tốt cho đến khi tôi bắt đầu sử dụng ContentProvider trong ứng dụng của mình. Tôi có:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Tôi hiểu rằng điều này xảy ra vì hai ứng dụng (phát hành và gỡ lỗi) đang đăng ký cùng thẩm quyền ContentProvider.

Tôi thấy một khả năng để giải quyết điều này. Nếu tôi hiểu chính xác, bạn sẽ có thể chỉ định các tệp khác nhau để sử dụng khi xây dựng. Sau đó, tôi sẽ có thể đặt các quyền hạn khác nhau trong các tệp tài nguyên khác nhau (và từ Manifest đặt quyền hạn làm tài nguyên chuỗi) và báo cho Gradle sử dụng các tài nguyên khác nhau để xây dựng gỡ lỗi. Điều đó có thể không? Nếu có thì bất kỳ gợi ý về cách để đạt được điều đó sẽ là tuyệt vời!

Hoặc có thể trực tiếp sửa đổi Manifest bằng Gradle? Bất kỳ giải pháp nào khác về cách chạy cùng một ứng dụng với ContentProvider trên một thiết bị luôn được chào đón.


Đối với những người quan tâm đến việc theo dõi hỗ trợ ngược dòng cho trường hợp sử dụng này: báo cáo lỗi AOSP . Lập trường hiện tại "chính thức" là sử dụng giải pháp ghi đè rõ ràng .
desseim

Câu trả lời:


226

Không có câu trả lời nào làm tôi hài lòng, tuy nhiên Liberty đã gần gũi. Vì vậy, đây là cách tôi đang làm điều đó. Trước hết tại thời điểm tôi đang làm việc với:

  • Android Studio Beta 0.8.2
  • Plugin cấp độ 0.12. +
  • Lớp 1.12

Mục tiêu của tôi là chạy Debugphiên bản cùng với Releasephiên bản trên cùng một thiết bị sử dụng cùng một thiết bị ContentProvider.


Trong build.gradle của ứng dụng của bạn đặt hậu tố cho bản dựng Debug:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Trong thuộc tính tập tin AndroidManifest.xmlandroid:authorities của bạn ContentProvider:

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

Trong thuộc tính bộ của bạn AUTHORITYcó thể được sử dụng bất cứ nơi nào cần thiết trong quá trình triển khai của bạn:

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

Mẹo: Trước đó làBuildConfig.PACKAGE_NAME

Đó là nó! Nó sẽ hoạt động như một lá bùa. Hãy tiếp tục đọc nếu bạn sử dụng SyncAd CHƯƠNG!


Cập nhật cho SyncAd CHƯƠNG (14.11.2014)

Một lần nữa tôi sẽ bắt đầu với thiết lập hiện tại của mình:

  • Android Studio Beta 0.9.2
  • Plugin cấp độ 0.14.1
  • Lớp 2.1

Về cơ bản, nếu bạn cần tùy chỉnh một số giá trị cho các bản dựng khác nhau, bạn có thể thực hiện nó từ tệp build.gradle:

  • sử dụng buildConfigField để truy cập nó từBuildConfig.java lớp
  • sử dụng resValue để truy cập nó từ các tài nguyên, ví dụ: @ string / your_value

Thay thế cho tài nguyên, bạn có thể tạo các thư mục buildType hoặc hương vị riêng biệt và ghi đè các giá trị hoặc XML trong chúng. Tuy nhiên, tôi sẽ không sử dụng nó trong ví dụ dưới đây.

Thí dụ


Trong build.gradle tệp thêm vào như sau:

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

Bạn sẽ thấy kết quả trong BuildConfig.java lớp

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

và trong build / created / res / created / debug / value / created.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>

Trong tệp Authenticator.xml của bạn sử dụng tài nguyên được chỉ định trong tệp 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"
/>

Trong syncad CHƯƠNG.xml của bạn sử dụng lại cùng một tài nguyên và @ chuỗi / chính quyền cũng vậy

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

Mẹo: tự động hoàn thành (Ctrl + Space) không hoạt động đối với các tài nguyên được tạo này, do đó bạn phải nhập chúng theo cách thủ công


7
Câu trả lời tốt nhất IMHO. Đẹp ngắn và ví dụ đơn giản.
thiệu

Vâng, đó là cách giải quyết tốt nhất tôi từng thấy cho đến nay. Cảm ơn bạn rất nhiều vì đã chia sẻ! Vẫn còn một vấn đề khác không liên quan đến vấn đề này vì tôi cần cập nhật một Ý định rõ ràng trong một tệp ưu tiên để sử dụng tên gói mới. code.google.com/p/android/issues/detail?id=57460
Bernd S

@BerndS Tôi đã đăng một bình luận cho vấn đề của bạn với giải pháp. Bạn cần hiểu rằng việc thay đổi applicationId bằng cách thay thế nó hoặc đặt hậu tố không ảnh hưởng đến các gói java. Nó chỉ là một định danh của ứng dụng của bạn và nó được tách ra khỏi các gói java. Xem câu trả lời của tôi cho một câu hỏi khác stackoverflow.com/questions/24178007/ từ
Damian Petla

1
@JJD Các sửa đổi mà bạn liên kết đến sẽ hoạt động mà không có bất kỳ tập lệnh xây dựng tùy chỉnh nào. Nếu bạn muốn sử dụng trình giữ chỗ $ {applicationId} cho sync_ad CHƯƠNG.xml, tệp xác thực, bạn phải tùy chỉnh tập lệnh build.gradle của mình. Tôi thấy rằng bạn đã thực hiện nhiều trong kịch bản build.gradle của mình để bạn thoải mái với ý tưởng này. Bạn đã làm theo hướng dẫn trong câu trả lời của tôi và nó vẫn không hoạt động?
Rob Meeuwisse

1
Tôi đã cập nhật câu trả lời của mình với cách thực hiện cho syncad CHƯƠNG
Damian Petla

39

Mẹo xây dựng hệ thống Android mới: Đổi tên chính quyền ContentProvider

Tôi đoán tất cả các bạn đã nghe nói về hệ thống xây dựng dựa trên Android Gradle mới. Hãy thành thật mà nói, hệ thống xây dựng mới này là một bước tiến rất lớn so với hệ thống trước đó. Nó chưa phải là bản cuối cùng (kể từ bài viết này, phiên bản mới nhất là 0.4.2) nhưng bạn đã có thể sử dụng nó một cách an toàn trong hầu hết các dự án của mình.

Tôi đã chuyển phần lớn dự án của mình sang hệ thống xây dựng mới này và gặp một số vấn đề do thiếu hỗ trợ trong một số tình huống cụ thể. Một trong số đó là hỗ trợ đổi tên chính quyền ContentProvider

Hệ thống được xây dựng Android mới cho phép bạn xử lý các loại ứng dụng khác nhau bằng cách sửa đổi tên gói khi xây dựng. Một trong những lợi thế chính của cải tiến này là giờ đây bạn có thể cài đặt hai phiên bản ứng dụng khác nhau trên cùng một thiết bị. Ví dụ:

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

Sử dụng cấu hình Gradle như vậy, bạn có thể lắp ráp hai APK khác nhau:

• APK gỡ lỗi với tên gói com.cyrilmottier.android.app.debug • APK phát hành với tên gói com.cyrilmottier.android.app

Vấn đề duy nhất là bạn sẽ không thể cài đặt hai APK cùng một lúc nếu cả hai đều phơi bày ContentProvider với cùng các cơ quan. Về mặt logic, chúng ta cần đổi tên cơ quan tùy thuộc vào kiểu xây dựng hiện tại nhưng điều này không được hệ thống xây dựng Gradle hỗ trợ (chưa? ... Tôi chắc chắn rằng nó sẽ được khắc phục sớm). Vì vậy, đây là một cách để đi:

Trước tiên, chúng ta cần chuyển tuyên bố ContentProvider của nhà cung cấp Android sang loại bản dựng phù hợp. Để làm điều đó, chúng tôi sẽ chỉ cần:

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 / phát hành / 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>

Đảm bảo xóa khai báo ContentProvider khỏi AndroidManifest.xml trong src / main / vì Gradle không biết cách hợp nhất ContentProviders có cùng tên nhưng có thẩm quyền khác.

Cuối cùng, chúng ta có thể cần truy cập vào cơ quan có thẩm quyền trong mã. Điều này có thể được thực hiện khá dễ dàng bằng cách sử dụng tệp BuildConfig và phương thức 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
       }
   }
}

Nhờ cách giải quyết này, bạn sẽ có thể sử dụng BuildConfig.PROVIDER_AUTHORITY trong Nhà cung cấp của bạn Liên kết và cài đặt hai phiên bản khác nhau của ứng dụng của bạn cùng một lúc.


Bản gốc trên Google+: https://plus.google.com/u/0/11841777715 3109946393/posts/EATUmhntaCQ


1
Đối với ai đó không thể chạy gradle, lỗi sint Wax. Đây là câu trả lời: stackoverflow.com/questions/20678118/ từ
Renan Franca

23

Mặc dù ví dụ của Cyril hoạt động rất tốt nếu bạn chỉ có một vài loại bản dựng, nhưng nó sẽ nhanh chóng trở nên phức tạp nếu bạn có nhiều loại bản dựng và / hoặc hương vị sản phẩm vì bạn cần duy trì nhiều loại AndroidManifest.xml khác nhau.

Dự án của chúng tôi bao gồm 3 loại bản dựng khác nhau và 6 loại hương vị với tổng số 18 biến thể xây dựng, vì vậy thay vào đó chúng tôi đã thêm hỗ trợ cho ".res-auto" trong các cơ quan ContentProvider, mở rộng sang packagename hiện tại và loại bỏ nhu cầu duy trì AndroidManifest.xml khác nhau

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

Mã ví dụ có thể được tìm thấy ở đây: https://gist.github.com/cmelchior/6988275


Tôi đã chuyển sang sử dụng một cái gì đó rất giống với dự án của mình, bởi vì tôi có cùng một vấn đề với hương vị xây dựng. Cách tiếp cận này hoạt động rất tốt cho bây giờ.
MantasV

2
FileWriter gây rắc rối cho các tệp utf-8, ít nhất là trên hệ điều hành Mac của tôi. Tôi đã thay đổi dòng liên quan thành: def wr = new OutputStreamWriter (new FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi

Điều này thực sự tuyệt vời, cảm ơn bạn! Tôi đã thực hiện một thay đổi nhỏ để tránh bị đứt với các chuỗi được định dạng. gist.github.com/paour/8475929
Pierre-Luc Paour

Điều này rất hữu ích, nhưng tôi đã gặp phải một vấn đề trong đó nó sẽ không được xây dựng sau khi sạch vì không có tệp value.xml trong thư mục xây dựng ở giai đoạn processManifest. Điều đó không tồn tại cho đến giai đoạn processResource, tại thời điểm đó là quá muộn để sửa đổi tệp kê khai, vì vậy để thay thế .res-auto trong cả tệp tệp kê khai và giá trị, tôi nghĩ rằng bạn cần 2 hàm, một hàm được gọi bởi biến thể. processManifest.doLast, cái khác được gọi bởi biến thể. processResource.doLast.
Niall

20

Vì phiên bản plugin 0.8.3 (thực tế là 0.8.1 nhưng nó không hoạt động chính xác), bạn có thể xác định tài nguyên trong tệp xây dựng để đây có thể là một giải pháp sạch hơn vì bạn không cần tạo tệp chuỗi cũng như gỡ lỗi / phát hành bổ sung thư mục.

xây dựng. nâng cấp

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
Coi chừng, các nhà chức trách dựa trên tài nguyên chỉ hoạt động trên Android 2.2.1 trở lên: github.com/android/pl
platform_frameworks_base/commit/ Kẻ

Cảm ơn bạn đã làm rõ.
rciovati

1
điều này cũng rất hữu ích trong tệp tìm kiếm cho android: searchSuggestAuthority, vì ở đó bạn không thể sử dụng $ {applicationId}
user114676

13

Tôi không biết nếu có ai đề cập đến nó. Trên thực tế sau plugin Android gradle 0.10+, trình hợp nhất tệp kê khai sẽ cung cấp hỗ trợ chính thức cho chức năng này: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

Trong AndroidManifest.xml, bạn có thể sử dụng $ {packName} như thế này:

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

Và trong build.gradle của bạn, bạn có thể có:

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

Xem ví dụ đầy đủ ở đây: https://code.google.com.vn/p/anymemo/source/browse/AndroidManifest.xml#152

và tại đây: https://code.google.com.vn/p/anymemo/source/browse/build.gradle#41


Đây là một tin tuyệt vời, nhưng có vẻ như đó không phải là một giải pháp hoàn chỉnh trong trường hợp các yếu tố <có thể tìm kiếm> cần tham chiếu đến tính tự kỷ, vì đây không phải là một phần của bảng kê khai (nhưng các chiến lược hợp nhất hiện có hoạt động cho các tệp này, không giống như bản kê khai).
Pierre-Luc Paour

1
Bạn không phải sử dụng hương vị cho việc này, nó cũng hoạt động với các kiểu xây dựng. Ngoài ra, thật tuyệt khi đề cập rằng bạn có thể sử dụng BuildConfig.PACKAGE_NAME để có được một tham chiếu tĩnh cho gói của bạn. Điều này hữu ích cho các nhà cung cấp nội dung nơi quyền hạn cần được biết đến trong thời gian chạy để truy vấn nhà cung cấp nội dung.
Matt Wolfe

1
Cũng nên được cập nhật để sử dụng $ {applicationId} thay vì $ {packName} cho android: chính quyền
Bernd S

8

Sử dụng ${applicationId}trình giữ chỗ trong xml vàBuildConfig.APPLICATION_ID trong mã.

Bạn sẽ cần mở rộng tập lệnh xây dựng để cho phép giữ chỗ trong các tệp xml khác với tệp kê khai. Bạn có thể sử dụng một thư mục nguồn trên mỗi biến thể xây dựng để cung cấp các phiên bản khác nhau của các tệp xml nhưng việc bảo trì sẽ trở nên cồng kềnh rất nhanh.

AndroidManifest.xml

Bạn có thể sử dụng trình giữ chỗ applicationId trong hộp trong bảng kê khai. Khai báo nhà cung cấp của bạn như thế này:

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

Lưu ý ${applicationId}bit. Điều này được thay thế tại thời điểm xây dựng với ứng dụng thực tế cho biến thể xây dựng đang được xây dựng.

Trong mã

ContentProvider của bạn cần xây dựng chuỗi quyền trong mã. Nó có thể sử dụng lớp BuildConfig.

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

Lưu ý BuildConfig.APPLICATION_IDbit. Nó là một lớp được tạo với applicationId thực tế cho biến thể xây dựng đang được xây dựng.

res / xml / files, ví dụ: syncad CHƯƠNG.xml, accountauthenticator.xml

Nếu bạn muốn sử dụng Bộ điều hợp đồng bộ hóa, bạn sẽ cần cung cấp dữ liệu meta cho ContentProvider và AccountManager trong các tệp xml trong thư mục res / xml /. Trình giữ chỗ applicationId không được hỗ trợ ở đây. Nhưng bạn có thể tự mở rộng tập lệnh xây dựng để hack nó.

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

Một lần nữa, lưu ý ${applicationId}. Điều này chỉ hoạt động nếu bạn thêm tập lệnh phân lớp dưới đây vào thư mục gốc của mô-đun và áp dụng nó từ build.gradle.

xây dựng. nâng cấp

Áp dụng tập lệnh xây dựng bổ sung từ tập lệnh build.gradle mô-đun. Một nơi tốt là bên dưới plugin Android gradle.

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

android {
    compileSdkVersion 21
    // etc.

xây dựng-processApplicationId.gradle

Dưới đây là nguồn làm việc cho tập lệnh xây dựng res / xml / giữ chỗ. Một phiên bản tài liệu tốt hơn có sẵn trên github . Cải tiến và mở rộng được chào đón.

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

Chuỗi tệp

Theo tôi, không cần thêm hỗ trợ giữ chỗ cho các chuỗi tài nguyên. Đối với trường hợp sử dụng ở trên ít nhất là không cần thiết. Tuy nhiên, bạn có thể dễ dàng thay đổi tập lệnh để không chỉ thay thế chỗ dành sẵn trong thư mục res / xml / mà còn trong thư mục res / value /.


6

Tôi thà thích một hỗn hợp giữa Cyril và rciovati. Tôi nghĩ đơn giản hơn, bạn chỉ có hai sửa đổi.

Hình build.gradlenhư:

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

xác thực

<?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_ad CHƯƠNG.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>

Mã số:

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

4

Dựa trên mẫu của @ChristianMelchior, đây là giải pháp của tôi, khắc phục hai vấn đề trong các giải pháp trước:

  • các giải pháp thay đổi giá trị tệp XML trong thư mục xây dựng gây ra việc xây dựng lại toàn bộ tài nguyên (bao gồm cả tất cả các phần có thể rút được)

  • vì một lý do không xác định, IntelliJ (và có lẽ là Android Studio) không xử lý tài nguyên một cách đáng tin cậy, khiến bản dựng chứa các .res-autocơ quan cung cấp không được thay thế

Giải pháp mới này thực hiện mọi thứ theo cách Gradle bằng cách tạo một tác vụ mới và cho phép xây dựng gia tăng bằng cách xác định các tệp đầu vào và đầu ra.

  1. tạo một tệp (trong ví dụ tôi đặt nó trong một variantsthư mục), được định dạng như một tệp xml tài nguyên, chứa các tài nguyên chuỗi. Chúng sẽ được hợp nhất vào tài nguyên của ứng dụng và bất kỳ sự xuất hiện nào .res-autotrong các giá trị sẽ được thay thế bằng tên gói của biến thể, ví dụ:<string name="search_provider">.res-auto.MySearchProvider</string>

  2. thêm build_extras.gradletệp từ ý chính này vào dự án của bạn và tham chiếu nó từ chính build.gradlebằng cách thêm apply from: './build_extras.gradle'một nơi nào đó phía trên androidkhối

  3. đảm bảo bạn đặt tên gói mặc định bằng cách thêm nó vào android.defaultConfigkhốibuild.gradle

  4. trong AndroidManifest.xmlvà các tệp cấu hình khác (chẳng hạn như xml/searchable.xmlđối với nhà cung cấp dịch vụ tìm kiếm hoàn thành tự động), hãy tham khảo nhà cung cấp (ví dụ @string/search_provider)

  5. nếu bạn cần lấy cùng tên, bạn có thể sử dụng BuildConfig.PACKAGE_NAMEbiến đó, ví dụBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Cập nhật: phương pháp này chỉ hoạt động trên Android 2.2.1 trở lên. Đối với các nền tảng trước đó, hãy xem câu trả lời này , có một số vấn đề riêng, vì việc sáp nhập tệp kê khai mới vẫn còn rất khó khăn xung quanh các cạnh.


Bạn đang đặt thư mục biến thể của bạn ở đâu? Tôi có một dự án Android Studio lớn phụ thuộc vào một số mô-đun Android - ứng dụng chính của tôi và một số mô-đun Thư viện Android. Tôi có thể xây dựng từ dòng lệnh, nhưng khi tôi cố gắng xây dựng từ bên trong Android Studio thì nó có vẻ variants/res-auto-values.xmltương đối /Applications/Android Studio.app/bin/. tức là tôi không nhận được FileNotFoundException cho /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Tôi đang chạy trên máy mac. Đây là một giải pháp tuyệt vời, nhưng tôi rất muốn làm cho nó hoạt động trong IDE cho các thành viên khác trong nhóm.
dùng1978019

1
Đã khắc phục sự cố của riêng tôi. Gradle xuất hiện để giải quyết các đường dẫn bằng cách sử dụng System.getProperty("user.dir"), trả về một kết quả khác khi được xây dựng bởi Android Studio. Giải pháp là sử dụng đường dẫn liên quan đến thư mục dự án, được trả về gradle.startParameter.getProjectDir(). Xem bình luận của tôi trong ý chính liên kết của Paour là tốt.
dùng1978019

Coi chừng, các nhà chức trách dựa trên tài nguyên chỉ hoạt động trên Android 2.2.1 trở lên: github.com/android/pl
platform_frameworks_base/commit/ Kẻ


2

Thật không may, phiên bản hiện tại (0.4.1) của plugin Android dường như không cung cấp giải pháp tốt cho việc này. Tôi chưa có thời gian để thử điều này, nhưng một cách giải quyết có thể cho vấn đề này là sử dụng tài nguyên chuỗi @string/provider_authorityvà sử dụng tài nguyên đó trong tệp kê khai : android:authority="@string/provider_authority". Sau đó, bạn có một res/values/provider.xmlthư mục res của từng loại bản dựng sẽ ghi đè lên quyền hạn, trong trường hợp của bạn, đây sẽ làsrc/debug/res

Tôi đã xem xét việc tạo tệp xml một cách nhanh chóng, nhưng một lần nữa, dường như không có bất kỳ móc nối tốt nào cho nó trong phiên bản hiện tại của plugin. Tôi khuyên bạn nên đưa vào một yêu cầu tính năng mặc dù, tôi có thể tưởng tượng nhiều người sẽ gặp phải vấn đề tương tự.


Xin chào Marcus, cảm ơn bạn đã trả lời. Giải pháp đề xuất của bạn là người duy nhất tôi có thể nghĩ về bản thân mình bây giờ. Nhưng vấn đề của tôi là, tôi không biết làm thế nào để đạt được điều đó với Gradle.
Thần chú

2

Câu trả lời trong bài viết này làm việc cho tôi.

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

Tôi sử dụng 3 hương vị khác nhau để tôi tạo 3 bảng kê khai với nhà cung cấp nội dung trong mỗi hương vị như kevinrschultz nói:

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

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

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

Bản kê khai chính của bạn không bao gồm các nhà cung cấp:

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

Và biểu hiện của bạn trong mỗi hương vị của bạn bao gồm cả nhà cung cấp.

Miễn phí:

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

Đã thanh toán:

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

Khác:

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

Giải pháp của tôi là sử dụng thay thế giữ chỗ trong AndroidManifest.xml. Nó cũng xử lý các packageNameSuffixthuộc tính để bạn có thể có debugreleasecũng như mọi bản dựng tùy chỉnh khác trên cùng một thiết bị.

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

Tôi có nó lên gist nếu bạn muốn xem nếu nó phát triển sau này.

Tôi thấy đó là một cách tiếp cận thanh lịch hơn so với các cách tiếp cận phân tích cú pháp XML và nhiều tài nguyên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.