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

Skonfigurowałem Gradle, aby dodać sufiks nazwy pakietu do mojej aplikacji do debugowania, więc mogę mieć wersję wydania, której używam 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

Moja budowa.plik gradle wygląda tak:
...
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 dlatego, że dwie aplikacje (release i debug) rejestrują tę samą treść.

Widzę jedną możliwość rozwiązania tego problemu. Jeśli dobrze rozumiem, powinieneś być w stanie określić różne pliki do wykorzystania podczas budowania. Następnie powinienem być w stanie umieścić różne władze w różnych plikach zasobów (i z manifestu set authority jako zasób Łańcuchowy) i powiedzieć Gradle, aby używał różnych zasobów do budowania debugowania. Czy to możliwe? Jeśli tak, to wszelkie wskazówki, jak to osiągnąć, byłoby niesamowite!

A może jest to możliwe aby bezpośrednio modyfikować 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.

Author: Jonik, 2013-05-27

14 answers

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

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

Moim celem jest uruchomienie wersji Debug wraz z wersją Release na tym samym urządzeniu przy użyciu tego samego ContentProvider.


In build.gradle Twojego zestawu aplikacji do debugowania Budowa:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

W AndroidManifest.XML zbiór plików android:authorities własność twojego ContentProvider:

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

W kodzie Ustaw AUTHORITY właściwość, która może być używana wszędzie tam, gdzie jest potrzebna w Twojej implementacji:

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

Wskazówka: zanim było BuildConfig.PACKAGE_NAME

To jest to! Będzie działać jak urok. Czytaj dalej, Jeśli używasz SyncAdapter!

Aktualizacja dla SyncAdapter (14.11.2014)

Po raz kolejny zacznę od mojego obecnego konfiguracja:

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

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

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

Jako alternatywę dla zasobów można utworzyć oddzielić katalogi buildType lub flavour i nadpisać XMLs lub wartości w nich zawarte. Jednak nie będę go używał w poniższym przykładzie.

Przykład


In build.gradle Dodaj plik:

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 BuildConfig.java class

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

I 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 Twoim authenticatorze.XML użyj zasobu określonego w buduj.gradle file
<?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 Twoim syncadapterze.xml użyj ponownie tego samego zasobu i @ string / string too

<?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: autocompletion (Ctrl+Spacja) nie działa dla tych wygenerowanych zasobów, więc musisz wpisać je ręcznie

 208
Author: Damian Petla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-19 12:24:54

Nowy Android build system tip: ContentProvider authority renaming

Myślę, że wszyscy słyszeliście o Nowym Systemie budowania opartym na Androidzie Gradle. Bądźmy szczerzy, ten nowy system budowania to ogromny krok naprzód w porównaniu do poprzedniego. Nie jest jeszcze ostateczna (w chwili pisania tego tekstu najnowsza wersja to 0.4.2), ale możesz już bezpiecznie korzystać z niej w większości swoich projektów.

Osobiście przełączyłem większość mojego projektu na ten nowy system budowania i miałem pewne problemy, ponieważ braku wsparcia w niektórych szczególnych sytuacjach. Jednym z nich jest obsługa zmiany nazwy organu 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 możesz teraz mieć dwie różne wersje 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"
       }
   }
}

Używając takiej konfiguracji Gradle, można złożyć dwa różne APKs:

* debugowanie APK Z com.cyrilmottier.android.app.Nazwa pakietu debugowania * APK wydania Z com.cyrilmottier.android.Nazwa pakietu aplikacji

Jedynym problemem jest to, że nie będziesz w stanie zainstalować dwóch plików APK w tym samym czasie, jeśli oba ujawnią ContentProvider z tymi samymi władzami. Dość logicznie musimy zmienić nazwę autorytetu w zależności od bieżącego typu budowania ... ale nie jest to obsługiwane przez system budowania Gradle (jeszcze? ... Na pewno tak będzie. być naprawione wkrótce). Więc oto sposób:

Najpierw musimy przenieść deklarację dostawcy Android manifest ContentProvider 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>

Upewnij się, aby usunąć deklarację ContentProvider z AndroidManifest.xml w src/ main / bo Gradle nie wie jak scalić Contentprovidery posiadające to samo nazwisko, ale inny autorytet.

Wreszcie możemy potrzebować dostępu do organu w kodzie. Można to zrobić dość łatwo używając 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ł użyć BuildConfig.PROVIDER_AUTHORITY w ProviderContract i zainstalować dwie różne wersje aplikacji w tym samym czasie.


Oryginalnie na Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ

 38
Author: Cyril Mottier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-25 09:23:57

Podczas gdy 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 AndroidManifest.xml ' s.

Nasz projekt składa się z 3 różnych typów build i 6 smaków w sumie 18 wariantów build, więc zamiast tego dodaliśmy wsparcie dla ".res-auto " w ContentProvider, który rozszerza się do aktualnej nazwy pakietu i usuwa potrzebę utrzymania różnych 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

 23
Author: Christian Melchior,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-22 13:47:07

Od wersji wtyczki 0.8.3 (w rzeczywistości 0.8.1, ale nie dział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 dodatkowych folderów debugowania/wydania.

Buduj.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>
 20
Author: rciovati,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-14 10:37:49

Nie wiem, czy ktoś o tym wspomniał. Właściwie po Android Gradle plugin 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 następujący sposób:
<provider
    android:name=".provider.DatabasesProvider"
    android:authorities="${packageName}.databasesprovider"
    android:exported="true"
    android:multiprocess="true" />
I w Twojej budowie.gradle możesz mieć:
productFlavors {
    free {
        packageName "org.pkg1"
    }
    pro {
        packageName "org.pkg2"
    }
}

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

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

 13
Author: Liberty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-08 22:50:14

Użyj ${applicationId} symboli zastępczych w xml i BuildConfig.APPLICATION_ID w 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 bardzo szybko stanie się uciążliwa.

AndroidManifest.xml

Możesz użyć symbolu zastępczego applicationId po wyjęciu z pudełka w manifeście. Zadeklaruj swojego dostawcę jak to:

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

Zwróć uwagę na ${applicationId} bit. To jest zastępowane w czasie kompilacji rzeczywistym applicationId dla budowanego wariantu kompilacji.

W kodzie

Your ContentProvider needs to construct the authority string in code. 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_ID bit. Jest to wygenerowana klasa z rzeczywistym identyfikatorem applicationId dla budowanego wariantu build.

Pliki Res / xml/, np. syncadapter.xml, accountauthenticator.xml

Jeśli chcesz użyć adaptera synchronizacji, musisz podać metadane ContentProvider i AccountManager w plikach xml w katalogu res / xml/. Symbol zastępczy applicationId nie jest tutaj obsługiwany. Ale możesz sam rozszerzyć skrypt budowania, 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 buduj.gradle.

Buduj.gradle

Zastosuj dodatkowy skrypt build z modułu build.Gradle script. Dobre miejsce znajduje się pod Wtyczką Android gradle.

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

android {
    compileSdkVersion 21
    // etc.

Build-process applicationid.gradle

Poniżej znajduje się działające źródło dla skryptu 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)
    }
}

Struny.xml

W moim opinia nie ma potrzeby dodawania wsparcia zastępczego dla łańcuchów zasobów. W powyższym przypadku użycia przynajmniej nie jest to potrzebne. Można jednak łatwo zmienić skrypt, aby nie tylko zastąpić symbole zastępcze w katalogu res / xml/, ale także w katalogu res / values/.

 8
Author: Rob Meeuwisse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-13 19:51:42

[[4]] wolałbym mieszaninę Cyryla i rciovatiego. Myślę, że jest bardziej proste, masz tylko dwie modyfikacje.

build.gradle wygląda tak:

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

I 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>
 6
Author: icastell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-25 10:33:01

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

  • Rozwiązania, które zmieniają wartości.xml w katalogu build powoduje pełną przebudowę zasobów (w tym apt wszystkich drawables)

  • Z nieznanego powodu IntelliJ (i prawdopodobnie Android Studio) nie przetwarzają w sposób niezawodny zasobów, powodując, że kompilator zawiera nie-wymienione władze dostawcy .res-auto {16]}

Ten nowy rozwiązanie robi rzeczy bardziej Gradle sposób tworząc nowe zadanie i pozwala na przyrostowe buduje poprzez definiowanie plików wejściowych i wyjściowych.

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

  2. Dodaj plik build_extras.gradle z ten gist do twojego projektu i odwołaj się do niego z głównego build.gradle dodając apply from: './build_extras.gradle' gdzieś nad android blok

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

  4. W AndroidManifest.xml i innych plikach konfiguracyjnych (takich jak {[11] } dla dostawców wyszukiwania automatycznego uzupełniania), odwołaj się do dostawcy (na przykład @string/search_provider)

  5. Jeśli chcesz uzyskać tę samą nazwę, możesz użyć zmiennej BuildConfig.PACKAGE_NAME, na przykład BuildConfig.PACKAGE_NAME + ".MySearchProvider"

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


Aktualizacja: ta metoda działa tylko na Androidzie 2.2.1 i nowszych. Jeśli chodzi o wcześniejsze platformy, zobacz ta odpowiedź , która ma swój własny zestaw problemów, ponieważ nowa fuzja manifestu jest nadal bardzo szorstka wokół krawędzi…
 4
Author: Pierre-Luc Paour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:11

Gradle.build

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

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

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

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

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

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

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

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

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

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

        //replace field ${valueContentAuthority} in AndroidManifest.xml
        mergedFlavor.manifestPlaceholders = [ valueContentAuthority: valueContentAuthority ]
    }
}
Authenticator.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:smallIcon="@drawable/ic_launcher" />

Sync_adapter.xml

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

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

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

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

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

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

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

Kod:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
 4
Author: maros136,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-06 17:16:35

Napisałem blogpost z Github sample project, który rozwiązuje ten problem (i inne podobne problemy) w nieco inny sposób niż Cyril ' s.

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

 3
Author: icecreamman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 18:57:03

Niestety, obecna wersja (0.4.1) wtyczki android nie wydaje się być dobrym rozwiązaniem dla tego. Nie miałem jeszcze czasu, aby tego spróbować, ale możliwym obejściem tego problemu byłoby użycie zasobu łańcuchowego @string/provider_authority i użycie go w manifeście: android:authority="@string/provider_authority". Następnie masz res/values/provider.xml w folderze res każdego typu budowania, który powinien zastąpić uprawnienia, w Twoim przypadku byłoby to src/debug/res

Zajrzałem do generowania pliku xml w locie, ale znowu, nie ma wydają się być jakieś dobre Hooki dla niego w bieżącej wersji wtyczki. Polecam jednak umieszczenie prośby o funkcję, mogę sobie wyobrazić, że więcej osób natknie się na ten sam problem.

 2
Author: Marcus Forsell Stahre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-28 12:42:01

Odpowiedź w tym poście działa dla mnie.

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 manifest z dostawcą treści w każdym 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 twoja manifestacja w każdym Twoim smaku, w tym dostawca.

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łatne:

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

Inne:

<?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>
 2
Author: jcmore2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-05 11:56:18

Dlaczego po prostu tego nie dodać?

Typ.packageNameSuffix=". $type.name"

 0
Author: guydemossyrock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-14 12:40:27

Moim rozwiązaniem jest użycie zastępstwa zastępczego w AndroidManifest.xml. Obsługuje również atrybuty packageNameSuffix, dzięki czemu możesz mieć debug i release, jak również 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 go na gist też, jeśli chcesz zobaczyć, czy ewoluuje później.

Uznałem, że jest to bardziej eleganckie podejście niż wiele zasobów i metod parsowania XML.

 0
Author: Saad Farooq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-04 02:53:25