I'm building a multiplatform library for Android and iOS. My gradle file looks like this:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.4.0'
}
repositories {
mavenCentral()
}
group 'com.example'
version '0.0.1'
apply plugin: 'maven-publish'
kotlin {
jvm()
// This is for iPhone simulator
// Switch here to iosArm64 (or iosArm32) to build library for iPhone device
ios {
binaries {
framework()
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
jvmTest {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
iosMain {
}
iosTest {
}
}
}
configurations {
compileClasspath
}
Im using a third party library and I'm using it like this:
fun test(value: String): Int {
return BigDecimal.parseString(value).toBigInteger().intValue()
}
The problem is when I build the .jar the bignum library isn't included, and when I use the lib in an Android project I get an exception ClassNotFoundException: Didn't find class "com.ionspin.kotlin.bignum.decimal.BigDecimal".
Is there a way to include third party libs in the .jar for Android and .framework for iOS?
JVM
So, the only way I've found to generate a Fat JAR that works like you expect is by adding two custom gradle tasks in project:build.gradle.kts of your KMP library after appling the java plugin.
plugins {
[...]
id("java")
}
[...]
kotlin {
jvm {
[...]
compilations {
val main = getByName("main")
tasks {
register<Copy>("unzip") {
group = "library"
val targetDir = File(buildDir, "3rd-libs")
project.delete(files(targetDir))
main.compileDependencyFiles.forEach {
println(it)
if (it.path.contains("com.")) {
from(zipTree(it))
into(targetDir)
}
}
}
register<Jar>("fatJar") {
group = "library"
manifest {
attributes["Implementation-Title"] = "Fat Jar"
attributes["Implementation-Version"] = archiveVersion
}
archiveBaseName.set("${project.name}-fat")
val thirdLibsDir = File(buildDir, "3rd-libs")
from(main.output.classesDirs, thirdLibsDir)
with(jar.get() as CopySpec)
}
}
tasks.getByName("fatJar").dependsOn("unzip")
}
}
[...]
}
You then must launch the fatJar gradle task that generate a .jar file with the 3rd libraries classes extracted from they corresponding jar archives.
You can customize the two custom gradle scripts even more in order to better fit your needs (here I only included com. package name starting deps).
Then in your Android app app:build.gradle file you can use it as you did or simply
implementation files('libs/KMLibraryTest001-fat-1.0-SNAPSHOT.jar')
iOS
As you ask also for the iOS part in your title (even if it's a second citizen in the main topic of your question) you need only to use api instead of implementation for your 3rd party library along with the export option of the framework.
ios() {
binaries {
framework() {
transitiveExport = true // all libraries
//export(project(":LibA")) // this library project in a trainsitive way
//export("your 3rd party lib") // this 3rd party lib in a transitive way
}
}
}
And you can find a full reference here.
If you see the Krypto library, it has
androidMain
jsMain
jvmMain
mingwX64Main
nativPosixMain
Which means 5 kind of binaries are generated to support 5 platforms
Convincingly, this explains that each platform expects its own binary
for example,
windows -- DLL file
linux -- so file
java -- JAR file
mac -- dylib file
A JAR gets loaded into JVM, but IOS does not use JVM
Separate your Utility functions which has a common logic and write gradle to target multiple platforms
If you want to start with pure multiplatform, you can try this Official Example
Or create a sub gradle module and create a library project which is common to IOS as well as Android
The possible targets are properly documented here
I have created a application which publishes the binary to local repository and re-uses in the MainActivity -- you can get the code here
modify the local.properties for android SDK location and use
gradlew assemble
to build the APK and test it yourself
open the mylib\build.gradle.kts folder and you can see the targets jvm and iosX64 , jvm is used for android
If I'm correct using api instead of implementation should fix your problem, though I didn't try it out yet on the Native part
See Api and implementation separation
Related
What I'm trying to achieve
I'm trying to generate my REST API client for Android using OpenAPI Generator from the build.gradle script. That way, I wouldn't have to run the generator command line every time the specs change. Ideally, this would be generated when I build/assemble my app, and the sources would end up in the java (generated) folder, where generated sources are then accessible from the code (this is what happens with the BuildConfig.java file for example).
What I've tried so far
Following this link from their official GitHub, here's the build.gradle file I ended up with:
apply plugin: 'com.android.application'
apply plugin: 'org.openapi.generator'
...
openApiValidate {
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
recommend = true
}
openApiGenerate {
generatorName = "java"
inputSpec = "$rootDir/app/src/main/openapi/my-api.yaml"
outputDir = "$buildDir/generated/openapi"
groupId = "$project.group"
id = "$project.name-openapi"
version = "$project.version"
apiPackage = "com.example.mypackage.api"
invokerPackage = "com.example.mypackage.invoker"
modelPackage = "com.example.mypackage.model"
configOptions = [
java8 : "true",
dateLibrary : "java8",
library : "retrofit2"
]
}
...
First, I've never managed to get the API generated with the build/assemble task, even when I tried adding:
compileJava.dependsOn tasks.openApiGenerate
or
assemble.dependsOn tasks.openApiGenerate
The only way I could generate the sources was by manually triggering the openApiGenerate task:
Then, when I do generate my sources this way, they end up in the build folder but aren't accessible from my code, and aren't visible in the java (generated) folder:
I then have to manually copy/paste the generated source files to my project sources in order to use the API.
Even though I'm able to work around these issues by adding manual procedures, it would be way more maintainable if the whole process was simply automatic. I was able to achieve a similar result with another tool, Protobuf. Indeed, my gradle task gets triggered every time I build the app, and the sources end up in the java (generated) folder, so I don't have to do any additional work. The task is much simpler though, so I assume the main work that I'm not able to replicate with OpenAPI Generator is handled by the Protobuf plugin itself.
You have to specify path to the generated sources as a custom source set for your Gradle module, which is app in this case, as described here – https://developer.android.com/studio/build/build-variants#configure-sourcesets. That way Gradle will treat your sources as accessible from your code.
Something like this:
android {
...
sourceSets {
main {
java.srcDirs = ['build/generated/openapi/src/main/java']
}
}
...
}
I solved the issue you described like this, I'm using gradle.kts however.
See my build.gradle.kts
plugins {
// Your other plugins
id("org.openapi.generator") version "5.3.0"
}
openApiGenerate {
generatorName.set("kotlin")
inputSpec.set("$rootDir/app/src/main/openapi/my-api.yaml")
outputDir.set("$buildDir/generated/api")
// Your other specification
}
application {
// Your other code
sourceSets {
main {
java {
// TODO: Set this path according to what was generated for you
srcDir("$buildDir/generated/api/src/main/kotlin")
}
}
}
}
tasks.compileKotlin {
dependsOn(tasks.openApiGenerate)
}
You need to build the application at least once for the IDE to detect the library (at least this is the case for me in Intellij)
Your build should automatically generate the open api classes , to refer the generated classes in your java project you should add the generated class path to your source directory like it was mentioned in the other answers
https://developer.android.com/studio/build/build-variants#configure-sourcesets
As far as the task dependency goes , in android tasks are generated after configuration thus for gradle to recognize the task , wrap it inside afterEvaluate block like
afterEvaluate {
tasks.compileDebugJavaWithJavac.dependsOn(tasks.openApiGenerate)
}
I had this issue, and this answer https://stackoverflow.com/a/55646891/14111809 led me to a more informative error:
error: incompatible types: Object cannot be converted to Annotation
#java.lang.Object()
Taking a look at the generated files that were causing this error, noticed:
import com.squareup.moshi.Json;
After including a Moshi in the app build.gradle, the build succeeded and the generated code was accessible.
implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
I have created a kotlin shared library project (using Android Studio on Windows), the android side of things work fine, but for some reason when writing iOS specific code in Kotlin, I can't seem to import the platform libraries. I was following the instructions from here to get started.
I did put include ':Shared' in my settings.gradle and also this implementation project(':Shared') in my app build.gradle
My end goal is to have the library just have business logic that will be shared between iOS and Android. I'm just trying to get an example project running, so that I know it works.
This is how my file structure is:
My build.gradle for the Shared module:
apply plugin: 'java-library'
apply plugin: 'kotlin-multiplatform'
/*We are doing three things in the codebase below:
1. Listing out the target for the shared code. For Android, JVM target.
For iOS, target depends on the device type, i.e. simulator or a real device.
2. We have defined iOS, Android and common source sets, which will allow different
configuration for each source set.
3. We have created a task for Xcode to generate framework and add it to our iOS project.
*/
kotlin{
targets{
// //Xcode sets SDK_NAME environment variable - based on whether the
// //target device is a simulator or a real device, the preset should vary
final def iOSTarget = System.getenv('SDK_NAME')?.startsWith("iphoneos") \
? presets.iosArm64 : presets.iosX64
//outputKinds - FRAMEWORK would mean that the shared code would be exported as a FRAMEWORK
// EXECUTABLE - produces a standalone executable that can be used to run as an app
fromPreset(iOSTarget, 'ios'){
binaries{
framework('Shared')
}
}
//create a target for Android from presets.jvm
fromPreset(presets.jvm, 'android')
}
//we have 3 different sourceSets for common, android and iOS.
//each sourceSet can have their own set of dependencies and configurations
sourceSets{
commonMain.dependencies{
api 'org.jetbrains.kotlin:kotlin-stdlib-common'
}
androidMain.dependencies{
api 'org.jetbrains.kotlin:kotlin-stdlib'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
iosMain{
}
}
}
configurations{
compileClasspath
}
// This task attaches native framework built from ios module to Xcode project
// Don't run this task directly,
// Xcode runs this task itself during its build process when we configure it.
// make sure all Gradle infrastructure exists (gradle.wrapper, gradlew)
//and gradlew is in executable mode (chmod +x gradlew)
task packForXCode(type: Sync) {
final File frameworkDir = new File(buildDir, "xcode-frameworks")
final String mode = project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG'
final def framework = kotlin.targets.ios.binaries.getFramework("Shared", mode)
inputs.property "mode", mode
dependsOn framework.linkTask
from { framework.outputFile.parentFile }
into frameworkDir
doLast {
new File(frameworkDir, 'gradlew').with {
text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$#\n"
setExecutable(true)
}
}
}
tasks.build.dependsOn packForXCode
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
commonMain code:
//This function will be the general function declaration that will be used as
//actual in our platform specific code.
expect fun getCurrentDate() : String
//This is the common function that will be called by Android and iOS app
fun getDate():String{
return "Current Date is ${getCurrentDate()}"
}
androidMain code:
import java.util.*
actual fun getCurrentDate(): String{
return Date().toString()
}
iosMain code (this is where the issue is):
//can't get this import to work.
//import platform.Foundation.NSDate
actual fun getCurrentDate(): String{
//return NSDate().toString()
return ""
}
My android MainActivity (this works) :
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import getDate// function from our Shared Module
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.dateLbl).text = getDate()
}
}
Any help or advice as to why the platform libraries won't work will be highly appreciated.
I just tried this on Windows, and the iOS platforms don't appear to be available, even just for importing. I don't think you'll be able to edit Apple targets on Windows. The available targets locally are:
android_arm32/
android_arm64/
android_x64/
android_x86/
linux_arm32_hfp/
linux_arm64/
linux_x64/
mingw_x64/
mingw_x86/
You can see local targets here: ~/.konan/kotlin-native-windows-1.3.72/klib/platform
My app has 3 flavors (free, paid, special) and there's a dependency LIB_1 need only for free flavor and other dependency LIB_2 needed for both paid and special flavors.
So, my question is how to define these dependencies in build.gradle file?
Currently, i define them like this:
dependencies {
freeImplementation 'LIB_1'
paidImplementation 'LIB_2'
specialImplementation 'LIB_2'
}
Is there a better way to define them instead of duplicating the same dependency for different flavors?
Yes, according to the documentation of android gradle dependency management, this is the only way of declaring flavor-specific dependencies.
If you are in a multi-modular project (and you don't want to repeat those lines in each submodule), you can also define these dependencies using the subproject block in the build.gradle of the root project:
subprojects {
//all subprojects` config
}
//or
configure(subprojects.findAll {it.name != 'sample'}) {
// subprojects that their name is not "sample"
}
Yes, it's possible to avoid duplicating the dependency across flavors, as I describe in my answer here: https://stackoverflow.com/a/75083956/6007104
Utility
Simply add these functions before your dependencies block:
/** Adds [dependency] as a dependency for the flavor [flavor] */
dependencies.ext.flavorImplementation = { flavor, dependency ->
def cmd = "${flavor}Implementation"
dependencies.add(cmd, dependency)
}
/** Adds [dependency] as a dependency for every flavor in [flavors] */
dependencies.ext.flavorsImplementation = { flavors, dependency ->
flavors.each { dependencies.flavorImplementation(it, dependency) }
}
Usage
You use the utility like this:
dependencies {
...
def myFlavors = ["flavor1", "flavor2", "flavor3"]
flavorsImplementation(myFlavors, "com.example.test:test:1.0.0")
flavorsImplementation(myFlavors, project(':local'))
...
}
How it works
The key to this utility is gradle's dependencies.add API, which takes 2 parameters:
The type of dependency, for example implementation, api, flavor1Implementation. This can be a string, which allows us to use string manipulation to dynamically create this value.
The dependency itself, for example "com.example.test:test:1.0.0", project(':local').
Using this API, you can add dependencies dynamically, with quite a lot of flexibility!
I'm trying to separate out integration tests in Android Studio 0.9.
I have added the following to the build file:
sourceSets {
integrationTest {
java.srcDir file('src/integrationTest/java')
}
}
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
I've run into a couple of issues:
The task will run but it doesn't have the rest of the project files available so I get errors about missing classes. There are some Java specific solutions I've found such as:
http://selimober.com/blog/2014/01/24/separate-unit-and-integration-tests-using-gradle/
https://blog.safaribooksonline.com/2013/08/22/gradle-test-organization/
But I haven't been able to figure out how to get this to work with Android Studio. Various combinations of main and main.output and playing around with dependencies don't seem to work, I get errors like:
Error:(33, 0) Could not find property 'main' on SourceSet container..
Which makes sense as the android plugin defines its own source sets, but these don't work either.
The IDE doesn't recognise the directory as a test source directory. For testing purposes I changed the source set name to androidTest and it correctly gets the green folder icon and the tests are run along with the existing unit tests that are already defined in androidTest.
#sm4's answer works indeed for a Java module (with apply plugin: 'java'), but unfortunately not for Android application (apply plugin: 'com.android.application') nor Android library modules (apply plugin: com.android.library).
But I have found a workaround:
Create the folders for your integration tests:
src/integrationTest/java
src/integrationTest/res
Add the sourceSets for your new folders:
sourceSets {
integrationTest {
java {
srcDir file('src/integrationTest/java')
}
res {
srcDir file('src/integrationTest/res')
}
}
}
In a pure Java module the java folder would now turn green and the res folder icon would change. In an Android application/library module it does not.
Now create a product flavor identically named as the folder configured in the sourceSet, and it works!
productFlavors {
integrationTest {
}
}
And to put a cherry on top:
configurations {
integrationTestCompile.extendsFrom testCompile
}
I've done exactly this kind of separation in Gradle, but for a pure Java project, not Android. You are not specifying the classpath in source sets, which I think is the issue. Here's the relevant part of the build.gradle:
sourceSets {
integration {
java {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/integration/java')
}
resources {
srcDir 'src/integration/resources'
}
}
}
configurations {
integrationCompile.extendsFrom testCompile
integrationRuntime.extendsFrom testRuntime
}
task integrationTest(group: "verification", type: Test) {
testClassesDir = sourceSets.integration.output.classesDir
classpath = sourceSets.integration.runtimeClasspath
}
integrationTest.dependsOn testClasses
IntelliJ idea picks up the folders under src/integration if they have the standard names (java, resources).
I have seen various versions of the dex erros before, but this one is new. clean/restart etc won't help. Library projects seems intact and dependency seems to be linked correctly.
Unable to execute dex: method ID not in [0, 0xffff]: 65536
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
or
Cannot merge new index 65950 into a non-jumbo instruction
or
java.util.concurrent.ExecutionException: com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
tl;dr: Official solution from Google is finally here!
http://developer.android.com/tools/building/multidex.html
Only one small tip, you will likely need to do this to prevent out of memory when doing dex-ing.
dexOptions {
javaMaxHeapSize "4g"
}
There's also a jumbo mode that can fix this in a less reliable way:
dexOptions {
jumboMode true
}
Update: If your app is fat and you have too many methods inside your main app, you may need to re-org your app as per
http://blog.osom.info/2014/12/too-many-methods-in-main-dex.html
Update 3 (11/3/2014)
Google finally released official description.
Update 2 (10/31/2014)
Gradle plugin v0.14.0 for Android adds support for multi-dex. To enable, you just have to declare it in build.gradle:
android {
defaultConfig {
...
multiDexEnabled true
}
}
If your application supports Android prior to 5.0 (that is, if your minSdkVersion is 20 or below) you also have to dynamically patch the application ClassLoader, so it will be able to load classes from secondary dexes. Fortunately, there's a library that does that for you. Add it to your app's dependencies:
dependencies {
...
compile 'com.android.support:multidex:1.0.0'
}
You need to call the ClassLoader patch code as soon as possible. MultiDexApplication class's documentation suggests three ways to do that (pick one of them, one that's most convenient for you):
1 - Declare MultiDexApplication class as the application in your AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.multidex.myapplication">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>
2 - Have your Application class extend MultiDexApplication class:
public class MyApplication extends MultiDexApplication { .. }
3 - Call MultiDex#install from your Application#attachBaseContext method:
public class MyApplication {
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
....
}
....
}
Update 1 (10/17/2014):
As anticipated, multidex support is shipped in revision 21 of Android Support Library. You can find the android-support-multidex.jar in /sdk/extras/android/support/multidex/library/libs folder.
Multi-dex support solves this problem. dx 1.8 already allows generating several dex files.
Android L will support multi-dex natively, and next revision of support library is going to cover older releases back to API 4.
It was stated in this Android Developers Backstage podcast episode by Anwar Ghuloum. I've posted a transcript (and general multi-dex explanation) of the relevant part.
As already stated, you have too many methods (more than 65k) in your project and libs.
Prevent the Problem: Reduce the number of methods with Play Services 6.5+ and support-v4 24.2+
Since often the Google Play services is one of the main suspects in "wasting" methods with its 20k+ methods. Google Play services version 6.5 or later, it is possible for you to include Google Play services in your application using a number of smaller client libraries. For example, if you only need GCM and maps you can choose to use these dependencies only:
dependencies {
compile 'com.google.android.gms:play-services-base:6.5.+'
compile 'com.google.android.gms:play-services-maps:6.5.+'
}
The full list of sub libraries and it's responsibilities can be found in the official google doc.
Update: Since Support Library v4 v24.2.0 it was split up into the following modules:
support-compat, support-core-utils, support-core-ui, support-media-compat and support-fragment
dependencies {
compile 'com.android.support:support-fragment:24.2.+'
}
Do note however, if you use support-fragment, it will have dependencies to all the other modules (ie. if you use android.support.v4.app.Fragment there is no benefit)
See here the official release notes for support-v4 lib
Enable MultiDexing
Since Lollipop (aka build tools 21+) it is very easy to handle. The approach is to work around the 65k methods per dex file problem to create multiple dex files for your app. Add the following to your gradle build file (this is taken from the official google doc on applications with more than 65k methods):
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
The second step is to either prepare your Application class or if you don't extend Application use the MultiDexApplication in your Android Manifest:
Either add this to your Application.java
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
or use the provided application from the mutlidex lib
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.myapplication">
<application
...
android:name="android.support.multidex.MultiDexApplication">
...
</application>
</manifest>
Prevent OutOfMemory with MultiDex
As further tip, if you run into OutOfMemory exceptions during the build phase you could enlarge the heap with
android {
...
dexOptions {
javaMaxHeapSize "4g"
}
}
which would set the heap to 4 gigabytes.
See this question for more detail on the dex heap memory issue.
Analyze the source of the Problem
To analyze the source of the methods the gradle plugin https://github.com/KeepSafe/dexcount-gradle-plugin can help in combination with the dependency tree provided by gradle with e.g.
.\gradlew app:dependencies
See this answer and question for more information on method count in android
Your project is too large. You have too many methods. There can only be 65536 methods per application. see here https://code.google.com/p/android/issues/detail?id=7147#c6
The below code helps, if you use Gradle. Allows you to easily remove unneeded Google services (presuming you're using them) to get back below the 65k threshold. All credit to this post: https://gist.github.com/dmarcato/d7c91b94214acd936e42
Edit 2014-10-22: There's been a lot of interesting discussion on the gist referenced above. TLDR? look at this one: https://gist.github.com/Takhion/10a37046b9e6d259bb31
Paste this code at the bottom of your build.gradle file and adjust the list of google services you do not need:
def toCamelCase(String string) {
String result = ""
string.findAll("[^\\W]+") { String word ->
result += word.capitalize()
}
return result
}
afterEvaluate { project ->
Configuration runtimeConfiguration = project.configurations.getByName('compile')
ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult
// Forces resolve of configuration
ModuleVersionIdentifier module = resolution.getAllComponents().find { it.moduleVersion.name.equals("play-services") }.moduleVersion
String prepareTaskName = "prepare${toCamelCase("${module.group} ${module.name} ${module.version}")}Library"
File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir
Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") {
inputs.files new File(playServiceRootFolder, "classes.jar")
outputs.dir playServiceRootFolder
description 'Strip useless packages from Google Play Services library to avoid reaching dex limit'
doLast {
copy {
from(file(new File(playServiceRootFolder, "classes.jar")))
into(file(playServiceRootFolder))
rename { fileName ->
fileName = "classes_orig.jar"
}
}
tasks.create(name: "stripPlayServices" + module.version, type: Jar) {
destinationDir = playServiceRootFolder
archiveName = "classes.jar"
from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) {
exclude "com/google/ads/**"
exclude "com/google/android/gms/analytics/**"
exclude "com/google/android/gms/games/**"
exclude "com/google/android/gms/plus/**"
exclude "com/google/android/gms/drive/**"
exclude "com/google/android/gms/ads/**"
}
}.execute()
delete file(new File(playServiceRootFolder, "classes_orig.jar"))
}
}
project.tasks.findAll { it.name.startsWith('prepare') && it.name.endsWith('Dependencies') }.each { Task task ->
task.dependsOn stripPlayServices
}
}
I've shared a sample project which solve this problem using custom_rules.xml build script and a few lines of code.
I used it on my own project and it is runs flawless on 1M+ devices (from android-8 to the latest android-19). Hope it helps.
https://github.com/mmin18/Dex65536
Faced the same problem and solved it by editing my build.gradle file on the dependencies section, removing:
compile 'com.google.android.gms:play-services:7.8.0'
And replacing it with:
compile 'com.google.android.gms:play-services-location:7.8.0'
compile 'com.google.android.gms:play-services-analytics:7.8.0'
Try adding below code in build.gradle, it worked for me -
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
multiDexEnabled true
}
The perfect solution for this would be to work with Proguard. as aleb mentioned in the comment.
It will decrease the size of the dex file by half.
You can analyse problem (dex file references) using Android Studio:
Build -> Analyse APK ..
On the result panel click on classes.dex file
And you'll see:
gradle + proguard solution:
afterEvaluate {
tasks.each {
if (it.name.startsWith('proguard')) {
it.getInJarFilters().each { filter ->
if (filter && filter['filter']) {
filter['filter'] = filter['filter'] +
',!.readme' +
',!META-INF/LICENSE' +
',!META-INF/LICENSE.txt' +
',!META-INF/NOTICE' +
',!META-INF/NOTICE.txt' +
',!com/google/android/gms/ads/**' +
',!com/google/android/gms/cast/**' +
',!com/google/android/gms/games/**' +
',!com/google/android/gms/drive/**' +
',!com/google/android/gms/wallet/**' +
',!com/google/android/gms/wearable/**' +
',!com/google/android/gms/plus/**' +
',!com/google/android/gms/topmanager/**'
}
}
}
}
}
Remove some jar file from Libs folder and copy to some other folder, And Go to _Project Properties > Select Java Build Path, Select Libraries, Select Add External Jar, Select the Removed jar to your project, Click save, this will be added under Referenced Library instead of Libs folder. Now clean and Run your project. You dont need to add Any code for MultDex. Its simply worked for me.
I was facing the same issue today what worked for is below down
For ANDROID STUDIO... Enable Instant Run
In File->Preferences->Build, Execution, Deployment->Instant Run-> Check Enable Instant run for hot swap...
Hope it helps