Używanie typów kompilacji w Gradle do uruchamiania tej samej aplikacji, która używa ContentProvider na jednym urządzeniu


124

Skonfigurowałem Gradle, aby dodać sufiks nazwy pakietu do mojej aplikacji do debugowania, dzięki czemu mogę mieć wersję używaną przeze mnie i wersję debugowania na jednym telefonie. Odnosiłem się do tego: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

Mój plik build.gradle wygląda następująco:

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

Wszystko działa dobrze, dopóki nie zacznę używać ContentProvider w mojej aplikacji. Dostaję:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

Rozumiem, że dzieje się tak, ponieważ dwie aplikacje (wydanie i debugowanie) rejestrują ten sam organ ContentProvider.

Widzę jedną możliwość rozwiązania tego problemu. Jeśli dobrze rozumiem, powinieneś być w stanie określić różne pliki do użycia podczas budowania. Następnie powinienem być w stanie umieścić różne uprawnienia w różnych plikach zasobów (i ustawić uprawnienia w pliku Manifest jako zasób ciągu) i powiedzieć Gradle, aby użył innego zasobu do kompilacji debugowania. Czy to jest możliwe? Jeśli tak, to wszelkie wskazówki, jak to osiągnąć, byłyby niesamowite!

A może można bezpośrednio zmodyfikować Manifest za pomocą Gradle? Każde inne rozwiązanie dotyczące uruchamiania tej samej aplikacji z ContentProvider na jednym urządzeniu jest zawsze mile widziane.


Dla tych, którzy są zainteresowani śledzeniem wsparcia ze strony nadawcy dla tego przypadku użycia: raport o błędzie AOSP . Obecne „oficjalne” stanowisko polega na zastosowaniu oczywistego rozwiązania nadrzędnego .
desseim

Odpowiedzi:


226

Żadna z istniejących odpowiedzi mnie nie satysfakcjonowała, jednak Liberty była blisko. Więc tak to robię. Przede wszystkim w tej chwili pracuję z:

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

Moim celem jest uruchomienie Debugwersji razem z Releasewersją na tym samym urządzeniu przy użyciu tego samego ContentProvider.


W build.gradle sufiksu zestawu aplikacji dla kompilacji debugowania:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

W pliku AndroidManifest.xml ustaw android:authoritieswłaściwość Twojego ContentProvider:

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

We właściwości zestawu kodu,AUTHORITY której można użyć w dowolnym miejscu w implementacji:

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

Wskazówka: wcześniejBuildConfig.PACKAGE_NAME

Otóż ​​to! Będzie działać jak urok. Czytaj dalej, jeśli używasz SyncAdapter!


Aktualizacja dla SyncAdapter (14.11.2014)

Jeszcze raz zacznę od mojej obecnej konfiguracji:

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

Zasadniczo, jeśli chcesz dostosować niektóre wartości dla różnych kompilacji, możesz to zrobić z pliku build.gradle:

  • użyj buildConfigField, aby uzyskać do niego dostęp z BuildConfig.javaklasy
  • użyj resValue, aby uzyskać do niego dostęp z zasobów, np. @ string / twoja_wartość

Alternatywą dla zasobów jest tworzenie osobnych katalogów buildType lub smaków i nadpisywanie zawartych w nich XML lub wartości. Jednak nie zamierzam tego używać w poniższym przykładzie.

Przykład


W pliku build.gradle dodaj:

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

Wyniki zobaczysz w klasie BuildConfig.java

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

oraz w 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>

W pliku uwierzytelniającym.xml użyj zasobu określonego w pliku 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"
/>

W swojej syncadapter.xml używać tego samego zasobu ponownie @ ŁAŃCUCH / władze zbyt

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

Wskazówka: autouzupełnianie (Ctrl + spacja) nie działa dla tych wygenerowanych zasobów, więc musisz wpisać je ręcznie


7
Najlepsza odpowiedź IMHO. Ładny, krótki i prosty przykład.
rekire

Tak, to najlepsze obejście, jakie do tej pory widziałem. Bardzo dziękuję za udostępnienie! Nadal mam inny problem niezwiązany z tym, ponieważ muszę zaktualizować wyraźny zamiar w pliku preferencji.xml, aby używał nowej nazwy pakietu. code.google.com/p/android/issues/detail?id=57460
Bernd S

@BerndS Opublikowałem komentarz dotyczący Twojego problemu z rozwiązaniem. Musisz zrozumieć, że zmiana applicationId poprzez zastąpienie go lub ustawienie sufiksu nie ma wpływu na pakiety Java. Jest to tylko identyfikator Twojej aplikacji i jest oddzielony od pakietów Java. Zobacz moją odpowiedź na inne pytanie stackoverflow.com/questions/24178007/…
Damian Petla

1
@JJD Modyfikacje, do których prowadzi łącze, działałyby bez niestandardowego skryptu kompilacji. Jeśli chcesz używać symboli zastępczych $ {applicationId} dla sync_adapter.xml, authenticationator.xml, musisz dostosować skrypt build.gradle. Widzę, że wykonałeś już wiele w swoim skrypcie build.gradle, więc jesteś zadowolony z pomysłu. Czy postępowałeś zgodnie z instrukcjami zawartymi w mojej odpowiedzi i czy nadal nie działa?
Rob Meeuwisse

1
Zaktualizowałem moją odpowiedź o poradnik dotyczący synchronizacji
Damian Petla

39

Nowa wskazówka dotycząca systemu kompilacji Androida: zmiana nazwy organu ContentProvider

Myślę, że wszyscy słyszeliście o nowym systemie kompilacji opartym na Androidzie Gradle. Bądźmy szczerzy, ten nowy system kompilacji to ogromny krok naprzód w porównaniu z poprzednim. Nie jest jeszcze ostateczna (w chwili pisania tego najnowsza wersja to 0.4.2), ale możesz już bezpiecznie używać jej w większości swoich projektów.

Osobiście przerzuciłem większość swojego projektu na nowy system kompilacji i miałem pewne problemy z powodu braku wsparcia w niektórych sytuacjach. Jednym z nich jest obsługa zmiany nazwy uprawnień ContentProvider

Nowy system Android pozwala radzić sobie z różnymi typami aplikacji, po prostu modyfikując nazwę pakietu w czasie kompilacji. Jedną z głównych zalet tego ulepszenia jest to, że teraz możesz mieć dwie różne wersje swojej aplikacji zainstalowane na tym samym urządzeniu w tym samym czasie. Na przykład:

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

Korzystając z takiej konfiguracji Gradle, możesz zebrać dwa różne pliki APK:

• Plik APK do debugowania z nazwą pakietu com.cyrilmottier.android.app.debug • Wydany plik APK z nazwą pakietu com.cyrilmottier.android.app

Jedynym problemem jest to, że nie będzie można zainstalować dwóch pakietów APK w tym samym czasie, jeśli oba udostępniają dostawcę treści z tymi samymi uprawnieniami. Całkiem logicznie musimy zmienić nazwę uprawnienia w zależności od bieżącego typu kompilacji… ale nie jest to obsługiwane przez system kompilacji Gradle (jeszcze?… Jestem pewien, że wkrótce zostanie to naprawione). Oto sposób:

Najpierw musimy przenieść deklarację ContentProvider manifestu dostawcy dla systemu Android do odpowiedniego typu kompilacji. W tym celu będziemy mieli po prostu:

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>

Pamiętaj, aby usunąć deklarację ContentProvider z AndroidManifest.xml w src / main /, ponieważ Gradle nie wie, jak scalić ContentProvider o tej samej nazwie, ale z innym uprawnieniem.

Wreszcie może być konieczne uzyskanie dostępu do organu w kodzie. Można to zrobić dość łatwo za pomocą pliku BuildConfig i metody 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
       }
   }
}

Dzięki temu obejściu będziesz mógł korzystać z BuildConfig.PROVIDER_AUTHORITY w umowie ProviderContract i jednocześnie zainstalować dwie różne wersje swojej aplikacji.


Oryginał w Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ


1
Dla kogoś, kto nie może uruchomić gradle, z powodu sintaxy błąd. Oto odpowiedź: stackoverflow.com/questions/20678118/…
Renan Franca

23

Chociaż przykład Cyrila działa świetnie, jeśli masz tylko kilka typów kompilacji, szybko się komplikuje, jeśli masz wiele typów kompilacji i / lub smaków produktów, ponieważ musisz utrzymywać wiele różnych plików AndroidManifest.xml.

Nasz projekt składa się z 3 różnych typów kompilacji i 6 smaków, w sumie 18 wariantów kompilacji, więc zamiast tego dodaliśmy obsługę „.res-auto” w organach ContentProvider, które rozszerzają się do bieżącej nazwy pakietu i eliminują potrzebę utrzymywania różnych plików 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)
    }
}

Przykładowy kod można znaleźć tutaj: https://gist.github.com/cmelchior/6988275


Przerzuciłem się na użycie czegoś bardzo podobnego w moim projekcie, ponieważ miałem ten sam problem z kompilacją smaków. Na razie to podejście działa bardzo dobrze.
MantasV

2
FileWriter robi problemy z plikami utf-8, przynajmniej na moim Mac OS. Zmieniłem powiązaną linię na: def writer = new OutputStreamWriter (new FileOutputStream (pathToFile), "UTF-8")
Reza Mohammadi

To jest naprawdę świetne, dziękuję! Wprowadziłem małą zmianę, aby zapobiec uszkodzeniu sformatowanych ciągów. gist.github.com/paour/8475929
Pierre-Luc Paour

Było to bardzo pomocne, ale napotkałem problem polegający na tym, że po wyczyszczeniu nie można było kompilować, ponieważ w folderze kompilacji na etapie processManifest nie było pliku values.xml. To nie istnieje aż do etapu processResources, w którym to momencie jest już za późno na modyfikację manifestu, więc aby zamienić .res-auto zarówno w pliku manifestu, jak i pliku wartości, myślę, że potrzebujesz 2 funkcji, jednej wywoływanej przez wariant. processManifest.doLast, drugi o nazwie variant.processResources.doLast.
Niall,

20

Od wersji wtyczki 0.8.3 (właściwie 0.8.1, ale nie działała poprawnie) możesz zdefiniować zasoby w pliku kompilacji, więc może to być czystsze rozwiązanie, ponieważ nie musisz tworzyć plików ciągów ani dodatkowego debugowania / wydania lornetka składana.

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
Uważaj, organy oparte na zasobach działają tylko na Androidzie 2.2.1 i nowszych: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour

Dziękuję za wyjaśnienie.
rciovati

1
jest to również bardzo przydatne w pliku searchable.xml dla Androida: searchSuggestAuthority, ponieważ nie możesz użyć $ {applicationId}
user114676.

13

Nie wiem, czy ktoś o tym wspomniał. Właściwie po wtyczce Android Gradle w wersji 0.10+, manifest fuzji zapewni oficjalne wsparcie dla tej funkcji: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger

W AndroidManifest.xml możesz użyć $ {packageName} w ten sposób:

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

W pliku build.gradle możesz mieć:

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

Zobacz pełny przykład tutaj: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152

i tutaj: https://code.google.com/p/anymemo/source/browse/build.gradle#41


To świetna wiadomość, ale nie wydaje się, aby było to kompletne rozwiązanie w przypadku elementów <searchable>, które muszą odwoływać się do autorytetu, ponieważ nie są one częścią manifestu (ale istniejące strategie scalania działają dla tych plików, w przeciwieństwie do Manifestu).
Pierre-Luc Paour

1
Nie musisz do tego używać smaków, działa to również z typami kompilacji. Byłoby również miło wspomnieć, że możesz użyć BuildConfig.PACKAGE_NAME, aby uzyskać statyczne odniesienie do swojego pakietu. Jest to przydatne dla dostawców treści, w przypadku których w czasie wykonywania należy znać uprawnienie, aby wysłać zapytanie do dostawcy treści.
Matt Wolfe,

1
Powinien również zostać zaktualizowany, aby używał $ {applicationId} zamiast $ {packageName} dla Androida: Authority
Bernd S

8

Użyj ${applicationId}symboli zastępczych w XML i BuildConfig.APPLICATION_IDw kodzie.

Będziesz musiał rozszerzyć skrypt kompilacji, aby włączyć symbole zastępcze w plikach xml innych niż manifest. Możesz użyć katalogu źródłowego dla każdego wariantu kompilacji, aby zapewnić różne wersje plików xml, ale konserwacja stanie się bardzo szybko uciążliwa.

AndroidManifest.xml

Możesz użyć symbolu zastępczego applicationId po wyjęciu z pola w manifeście. Zadeklaruj swojego dostawcę w ten sposób:

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

Zwróć uwagę na ${applicationId}trochę. Jest on zastępowany w czasie kompilacji przez rzeczywisty identyfikator aplikacji dla budowanego wariantu kompilacji.

W kodzie

Twój dostawca treści musi skonstruować ciąg autoryzacji w kodzie. Może używać klasy BuildConfig.

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

Zwróć uwagę na BuildConfig.APPLICATION_IDtrochę. Jest to wygenerowana klasa z rzeczywistym identyfikatorem applicationId dla budowanego wariantu kompilacji.

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

Jeśli chcesz użyć adaptera synchronizacji, musisz podać metadane dla ContentProvider i AccountManager w plikach xml w katalogu res / xml /. Symbol zastępczy applicationId nie jest tutaj obsługiwany. Ale możesz samodzielnie rozszerzyć skrypt kompilacji, aby go zhakować.

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

Ponownie zwróć uwagę na ${applicationId}. Działa to tylko wtedy, gdy dodasz poniższy skrypt gradle do katalogu głównego modułu i zastosujesz go z pliku build.gradle.

build.gradle

Zastosuj dodatkowy skrypt kompilacji ze skryptu build.gradle modułu. Dobre miejsce znajduje się pod wtyczką Android Gradle.

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

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

Poniżej znajduje się działające źródło skryptu kompilacji res / xml / placeholder. Lepiej udokumentowana wersja jest dostępna na github . Ulepszenia i rozszerzenia są mile widziane.

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

Moim zdaniem nie ma potrzeby dodawania zastępczej obsługi ciągów zasobów. Przynajmniej w powyższym przypadku nie jest to potrzebne. Jednak można łatwo zmienić skrypt, aby nie tylko zastępował symbole zastępcze w katalogu res / xml /, ale także w katalogu res / values ​​/.


6

Wolałbym raczej mieszankę Cyrila i rciovati. Myślę, że jest prostszy, masz tylko dwie modyfikacje.

Do build.gradlewygląda następująco:

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

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

autentykator.xml

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

sync_adapter.xml

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

AndroidManifest.xml

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

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

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

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

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

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

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

Kod:

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

4

Na podstawie próbki autorstwa @ChristianMelchior, oto moje rozwiązanie, które rozwiązuje dwa problemy z poprzednich rozwiązań:

  • rozwiązania, które zmieniają values.xml w katalogu kompilacji, powodują pełną przebudowę zasobów (w tym aapt wszystkich drawables)

  • z nieznanego powodu IntelliJ (i prawdopodobnie Android Studio) nie przetwarza niezawodnie zasobów, przez co kompilacja zawiera niezmienione .res-autouprawnienia dostawcy

To nowe rozwiązanie działa bardziej w sposób Gradle, tworząc nowe zadanie i pozwala na przyrostowe kompilacje poprzez definiowanie plików wejściowych i wyjściowych.

  1. utwórz plik (w przykładzie umieściłem go w variantskatalogu), sformatowany jak plik zasobów xml, który zawiera zasoby ciągów. Zostaną one scalone z zasobami aplikacji, a każde wystąpienie .res-autow wartościach zostanie zastąpione na przykład nazwą pakietu wariantu<string name="search_provider">.res-auto.MySearchProvider</string>

  2. dodaj build_extras.gradleplik z tej treści do swojego projektu i odwołaj się do niego z głównego build.gradle, dodając apply from: './build_extras.gradle'gdzieś nad androidblokiem

  3. upewnij się, że ustawiłeś domyślną nazwę pakietu, dodając ją do android.defaultConfigblokubuild.gradle

  4. in AndroidManifest.xmli inne pliki konfiguracyjne (na przykład xml/searchable.xmldla dostawców wyszukiwania automatycznego uzupełniania), należy odwołać się do dostawcy (na przykład @string/search_provider)

  5. jeśli potrzebujesz uzyskać taką samą nazwę, możesz BuildConfig.PACKAGE_NAMEna przykład użyć zmiennejBuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Aktualizacja: ta metoda działa tylko w systemie Android 2.2.1 i nowszych. W przypadku wcześniejszych platform zobacz tę odpowiedź , która ma swój własny zestaw problemów, ponieważ nowe manifestacyjne połączenie jest nadal bardzo szorstkie na krawędziach…


Gdzie umieszczasz katalog wariantów? Mam jeden duży projekt Android Studio, który zależy od kilku modułów systemu Android - mojej głównej aplikacji i kilku modułów biblioteki systemu Android. Mogę budować z wiersza poleceń, ale kiedy próbuję budować z poziomu Android Studio, szuka on w variants/res-auto-values.xmlodniesieniu do /Applications/Android Studio.app/bin/. tj. nie otrzymuję wyjątku FileNotFoundException dla /Applications/Android Studio.app/bin/variants/res-auto-values.xml. Biegam na Macu. To świetne rozwiązanie, ale chciałbym, aby działało w IDE dla innych członków zespołu.
user1978019

1
Naprawiłem własny problem. Wydaje się, że Gradle rozwiązuje ścieżki przy użyciu System.getProperty("user.dir"), co zwraca inny wynik po wywołaniu przez kompilację Android Studio. Rozwiązaniem jest użycie ścieżki względnej do katalogu projektu, który jest zwracany z gradle.startParameter.getProjectDir(). Zobacz także mój komentarz w powiązanej treści Paoura.
user1978019

Uważaj, organy oparte na zasobach działają tylko na Androidzie 2.2.1 i nowszych: github.com/android/platform_frameworks_base/commit/…
Pierre-Luc Paour


2

Niestety, obecna wersja (0.4.1) wtyczki Androida nie wydaje się być dobrym rozwiązaniem. Nie miałem czasu, aby spróbować tego jeszcze, ale możliwe obejście tego problemu byłoby wykorzystanie zasobu ciąg @string/provider_authorityi użyć jej w manifeście: android:authority="@string/provider_authority". W res/values/provider.xmlfolderze res każdego typu kompilacji masz wtedy plik, który powinien nadpisywać uprawnienia, w twoim przypadku tak będziesrc/debug/res

Rozejrzałem się nad generowaniem pliku xml w locie, ale znowu wydaje się, że w obecnej wersji wtyczki nie ma dla niego żadnych dobrych punktów zaczepienia. Poleciłbym jednak zgłosić prośbę o funkcję, wyobrażam sobie, że więcej osób napotka ten sam problem.


Cześć Marcus, dzięki za twoją odpowiedź. Twoje sugerowane rozwiązanie jest jedynym, jakie mogę sobie na razie pomyśleć. Ale mój problem polega na tym, że nie wiem, jak to osiągnąć z Gradle.
MantasV

2

Odpowiedź w tym poście mi odpowiada.

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

Używam 3 różnych smaków, więc tworzę 3 manifesty z dostawcami treści dla każdego smaku, jak powiedział Kevinrschultz:

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

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

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

Twój główny manifest nie obejmuje dostawców:

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

I twój manifest w każdym twoim smaku, w tym dostawcy.

Wolny:

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

Płatny:

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

Inny:

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

Moje rozwiązanie polega na użyciu zastępowania symboli zastępczych w programie AndroidManifest.xml. Obsługuje również packageNameSuffixatrybuty, dzięki czemu możesz mieć, debuga releasetakże inne niestandardowe kompilacje na tym samym urządzeniu.

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

Mam to na gist też, jeśli chcesz zobaczyć, czy rozwinie się później.

Okazało się, że jest to bardziej eleganckie podejście niż podejście do wielu zasobów i analizowania XML.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.