Share
[AppdevCon '19] Demystifying Android Libraries
Building an Android library in the current times is way different than what it used to be earlier. Things have changed considerably and keeping up to date with them is now a necessity rather than just some acquirable knowledge.
In this session, you will dive deeper into best practices and ways of architecting Android libraries. You will get to learn about the common pitfalls and how to overcome them by using the right approach such as leveraging architecture components and making your Android libraries lifecycle-aware. You will also understand how one can leverage Kotlin language when developing Android libraries as well as information around API design and exploring the path to becoming a better Android Library Developer.
By the end of this session, you will be all set to build Android libraries that scale and have API which contributes to the developer's happiness.
Event Link: https://web.archive.org/web/20190315154423/https://appdevcon.nl/session/demystifying-android-libraries/
Nishant Srivastava
Transcript
143 slides
-
How does Gradle process Android Libraries repositories { jCenter() }
dependencies { implementation 'com.github.nisrulz:awesomelib:1.0' ... } 4 @nisrulz -
How does Gradle process Android Libraries repositories { jCenter() }
dependencies { implementation 'com.github.nisrulz:awesomelib:1.0' ... } 5 @nisrulz // Dependency Notation -
How does Gradle process Android Libraries repositories { jCenter() }
dependencies { implementation 'com/github/nisrulz/awesomelib/1.0' ... } 6 @nisrulz -
Sensey’s build.gradle dependencies { ... // Other testing dependencies //
Transitive dependency: Support Compat library implementation "com.android.support:support-compat:27.0.2" } 18 @nisrulz -
Using an AAR as dependency repositories{ flatDir{ dirs 'libs' }
} dependencies { implementation(name:'nameOfYourAARFileWithoutExtension', ext:'aar') } @nisrulz 20 -
Using an AAR as dependency repositories{ flatDir{ dirs 'libs' }
} dependencies { implementation(name:'nameOfYourAARFileWithoutExtension', ext:'aar') } // No transitive dependencies of the library are downloaded 21 @nisrulz -
POM • Project Object Model • XML file • Configuration
details used by Maven to build the project 28 @nisrulz -
POM POM of Sensey Android Library <?xml version="1.0" encoding="UTF-8"?> <project
xsi:schemaLocation="..." xmlns="..."xmlns:xsi="..."> <modelVersion>4.0.0</modelVersion> <groupId>com.github.nisrulz</groupId><artifactId>sensey</artifactId><version>1.8.0</version> <packaging>aar</packaging><name>sensey</name> <description>Android library which makes playing with sensor events & detecting gestures a breeze.</description> <url>https://github.com/nisrulz/sensey</url> <licenses> <license> <name>The Apache Software License, Version 2.0</name><url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> </license> </licenses> <developers> <developer><id>nisrulz</id><name>Nishant Srivastava</name><email>[email protected]</email></developer> </developers> <scm> <connection>https://github.com/nisrulz/sensey.git</connection> <developerConnection>https://github.com/nisrulz/sensey.git</developerConnection> <url>https://github.com/nisrulz/sensey</url> </scm> <dependencies> <dependency> <groupId>com.android.support</groupId> <artifactId>support-compat</artifactId><version>27.0.2</version> <scope>runtime</scope> </dependency> <dependency> ... </dependency> </dependencies> </project> 29 @nisrulz -
POM POM of Sensey Android Library <?xml version="1.0" encoding="UTF-8"?> <project
xsi:schemaLocation="..." xmlns="..."xmlns:xsi="..."> <modelVersion>4.0.0</modelVersion> <groupId>com.github.nisrulz</groupId><artifactId>sensey</artifactId><version>1.8.0</version> <packaging>aar</packaging><name>sensey</name> <description>Android library which makes playing with sensor events & detecting gestures a breeze.</description> <url>https://github.com/nisrulz/sensey</url> <licenses> <license> <name>The Apache Software License, Version 2.0</name><url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> </license> </licenses> <developers> <developer><id>nisrulz</id><name>Nishant Srivastava</name><email>[email protected]</email></developer> </developers> <scm> <connection>https://github.com/nisrulz/sensey.git</connection> <developerConnection>https://github.com/nisrulz/sensey.git</developerConnection> <url>https://github.com/nisrulz/sensey</url> </scm> <dependencies> <dependency> <groupId>com.android.support</groupId> <artifactId>support-compat</artifactId><version>27.0.2</version> <scope>runtime</scope> </dependency> <dependency> ... </dependency> </dependencies> </project> 30 @nisrulz -
POM POM of Sensey Android Library <?xml version="1.0" encoding="UTF-8"?> <project
xsi:schemaLocation="..." xmlns="..."xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.android.support</groupId> <artifactId>support-compat</artifactId><version>27.0.2</version> <scope>runtime</scope> </dependency> <dependency> ... </dependency> </dependencies> </project> 31 @nisrulz -
Bundled Proguard Configs android { release { minifyEnabled true //
Rules to be used during the AAR generation proguardFiles 'proguard-rules-for-building-library.pro' } ... } 33 @nisrulz -
Bundled Proguard Configs android { release { minifyEnabled true //
Rules to be used during the AAR generation proguardFiles 'proguard-rules-for-building-library.pro' // Rules appended to the integrating app consumerProguardFiles 'proguard-rules-for-using-library.pro' } ... } 34 @nisrulz -
Bundled Proguard Configs # Fuel’s bundled Proguard file # Without
specifically keeping this class, # callbacks on android don't function properly. -keep class com.github.kittinunf.fuel.android.util.AndroidEnvironment 37 @nisrulz -
Bundled Proguard Configs # DON'T DO THIS # Do not
obfuscate the input class files -dontobfuscate # Optimizes variable allocation on the local variable frame. -optimizations !code/allocation/variable # Preserved as entry points -keep public class * { public protected *; } 38 @nisrulz -
Bundled Proguard Configs # DON'T DO THIS # Adding the
below in library proguard rules disables # the optimizations in the Android app -dontoptimize 39 @nisrulz -
Bundled Proguard Configs # To check the merged configuration #
Add the below to your app’s current proguard rules -printconfiguration proguard-merged-config.txt 40 @nisrulz -
Modularization dependencies { def libVer = {latest_version} // Base +
Ads Bundled Library implementation "com.github.nisrulz:easydeviceinfo:$libVer" // Base Library implementation "com.github.nisrulz:easydeviceinfo-base:$libVer" // Ads Library implementation "com.github.nisrulz:easydeviceinfo-ads:$libVer" } 45 @nisrulz -
Modularization <!-- POM file for com.github.nisrulz:easydeviceinfo --> <project xsi:schemaLocation="..." xmlns="..."
xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-ads</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> ... </dependencies> </project> 46 @nisrulz -
Modularization <!-- POM file for com.github.nisrulz:easydeviceinfo--> <project xsi:schemaLocation="..." xmlns="..." xmlns:xsi="...">
... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-ads</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> ... </dependencies> </project> 47 @nisrulz -
Modularization <!-- POM file for com.github.nisrulz:easydeviceinfo--> <project xsi:schemaLocation="..." xmlns="..." xmlns:xsi="...">
... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-ads</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> ... </dependencies> </project> 48 @nisrulz -
Modularization <!-- POM file for com.github.nisrulz:easydeviceinfo-ads --> <project xsi:schemaLocation="..." xmlns="..."
xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-common</artifactId><version>2.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.android.gms</groupId> <artifactId>play-services-ads-identifier</artifactId><version>16.0.0</version> <scope>runtime</scope> </dependency> ... </dependencies> </project> 49 @nisrulz -
Modularization <!-- POM file for com.github.nisrulz:easydeviceinfo-ads --> <project xsi:schemaLocation="..." xmlns="..."
xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-common</artifactId><version>2.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.android.gms</groupId> <artifactId>play-services-ads-identifier</artifactId><version>16.0.0</version> <scope>runtime</scope> </dependency> ... </dependencies> </project> 50 @nisrulz -
Conflict occurs between a library & app resource > Project
will not compile What happens when... 52 @nisrulz -
Conflict occurs between 2 libraries integrated in the app >
Resources from library defined first in build.gradle gets included What happens when... 53 @nisrulz -
Add a prefix to all your resources. How? • Enforce
this in Android Studio Solution? 57 @nisrulz -
Add a prefix to all your resources. How? • Enforce
this in Android Studio • Declare in build.gradle of library android { resourcePrefix 'YOUR_PREFIX_' // i.e 'sensey_' } Solution? 58 @nisrulz -
Solution? Resource named ‘app_name’ does not start with the project’s
resource prefix ‘sensey_’; Rename to `sensey_app_name`? 59 @nisrulz -
Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller than
version 21 declared in library [:sensey] Suggestion: + Use a compatible library with a minSdk of at most 14 + Increase this project's minSdk version to at least 21 + Use tools:overrideLibrary="com.github.nisrulz.sensey" to force usage (may lead to runtime failures) minSdk Restriction 63 @nisrulz -
Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller than
version 21 declared in library [:sensey] Suggestion: + Use a compatible library with a minSdk of at most 14 + Increase this project's minSdk version to at least 21 + Use tools:overrideLibrary="com.github.nisrulz.sensey" to force usage (may lead to runtime failures) minSdk Restriction 64 @nisrulz -
Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller than
version 21 declared in library [:sensey] Suggestion: + Use a compatible library with a minSdk of at most 14 + Increase this project's minSdk version to at least 21 + Use tools:overrideLibrary="com.github.nisrulz.sensey" to force usage (may lead to runtime failures) minSdk Restriction 65 @nisrulz -
Visibility vs Organization • Code organized in individual packages; everything
is public 70 @nisrulz -
Visibility vs Organization • Code organized inside one package; Classes
and methods are package private and only public on demand 71 @nisrulz -
Visibility vs Organization • Code organized inside the module i.e
individual packages Classes and methods are internal and only public on demand (in Kotlin land) 72 @nisrulz -
Visibility vs Organization • internal and only public on demand
(in Kotlin land) 73 @nisrulz -
Visibility vs Organization • Code organized inside the module i.e
individual packages Classes and methods are internal and only public on demand (in Kotlin land) • Drawback ◦ Need dependency on kotlin std library 74 @nisrulz -
Lifecycle Components Classes designed to help deal with Android lifecycle
• Lifecycle • LifecycleOwner • LifecycleObserver 76 @nisrulz -
Lifecycle Owner + Lifecycle class MainActivity extends AppCompatActivity() {...} public
class AppCompatActivity extends FragmentActivity{...} 81 @nisrulz -
Lifecycle Owner + Lifecycle class MainActivity extends AppCompatActivity() {...} public
class AppCompatActivity extends FragmentActivity{...} public class FragmentActivity extends SupportActivity {...} 82 @nisrulz -
Lifecycle Owner + Lifecycle class MainActivity extends AppCompatActivity() {...} public
class AppCompatActivity extends FragmentActivity{...} public class FragmentActivity extends SupportActivity {...} public class SupportActivity extends Activity implements LifecycleOwner, Component { ... public Lifecycle getLifecycle() {return this.mLifecycleRegistry();} ... } 83 @nisrulz -
Lifecycle Owner + Lifecycle class MainActivity extends AppCompatActivity() {...} public
class AppCompatActivity extends FragmentActivity{...} public class FragmentActivity extends SupportActivity {...} public class SupportActivity extends Activity implements LifecycleOwner, Component { ... public Lifecycle getLifecycle() {return this.mLifecycleRegistry();} ... } 84 @nisrulz -
LifecycleObserver dependencies { def lifecycleVer = "2.0.0" // Runtime implementation
"androidx.lifecycle:lifecycle-runtime:$lifecycleVer" // Annotation Support annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycleVer" ... } 87 @nisrulz -
LifecycleObserver public class AwesomeLib implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) public void
init() { ... } @OnLifecycleEvent(Lifecycle.Event.ON_START) public void libOnStart() { ... } ... @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void cleanup() { ... } } 88 @nisrulz -
LifecycleOwner class MainActivity : AppCompatActivity() { val awesomeLib = AwesomeLib()
override fun onStart() { ... // Add lifecycle observer lifecycle.addObserver(awesomeLib) } override fun onStop() { ... // Remove lifecycle observer lifecycle.removeObserver(awesomeLib) } } 89 @nisrulz -
ProcessLifecycleOwner Does something weird… Adding lifecycle-extensions artifact > automatically adds
<provider> element to the merged manifest 94 @nisrulz -
ProcessLifecycleOwner // Internal class to initialize Lifecycles. public class ProcessLifecycleOwnerInitializer
extends ContentProvider { @Override public boolean onCreate() { ... ProcessLifecycleOwner.init(getContext()); return true; } ... } 103 @nisrulz -
ProcessLifecycleOwner Why? To invoke ProcessLifecycleOwner as soon as process starts
Drawback? Initializes ProcessLifecycleOwner even if your app does not use it! 105 @nisrulz -
ProcessLifecycleOwner How to get rid of it in app? Use
Merge rule marker in app’s AndroidManifest.xml: // Remove marked element from the merged manifest tools:node="remove" 107 @nisrulz -
AutoInit Android Library Android Libraries need Android context to handle
simple tasks such as • Hook into Android Runtime • Access app resources • Use System Services • Register BroadcastReceiver 113 @nisrulz -
AutoInit Android Library public class MyApplication extends Application { @Override
public void onCreate() { super.onCreate(); // Init android library AwesomeLib.getInstance().init(this); } } 114 @nisrulz -
AutoInit Android Library public class MyApplication extends Application { @Override
public void onCreate() { super.onCreate(); // Init android library AwesomeLib.getInstance().init(this); } } 115 @nisrulz -
AutoInit Android Library public class MyApplication extends Application { @Override
public void onCreate() { super.onCreate(); // Init android library AwesomeLib.getInstance().init(this); } } 116 @nisrulz -
AutoInit Android Library ContentProvider can be used to simplify the
process. 119 @nisrulz -
AutoInit Android Library ContentProvider can be used to simplify the
process. Simply because ContentProvider • Is created and initialized (on the main thread) before all other components • Participate in manifest merging at build time. 120 @nisrulz -
AutoInit Android Library public class AwesomeLibInitProvider extends ContentProvider { ...
@Override public boolean onCreate() { // get the context (Application context) Context context = getContext(); // initialize AwesomeLib here AwesomeLib.getInstance().init(context); return false; } ... } 121 @nisrulz -
AutoInit Android Library public class AwesomeLibInitProvider extends ContentProvider { ...
@Override public boolean onCreate() { // get the context (Application context) Context context = getContext(); // initialize AwesomeLib here AwesomeLib.getInstance().init(context); return false; } ... } 122 @nisrulz -
AutoInit Android Library <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider" android:authorities="${applicationId}.awesomelibinitprovider"
android:enabled="true" android:exported="false"/> </application> </manifest> 123 @nisrulz -
AutoInit Android Library <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider" android:authorities="${applicationId}.awesomelibinitprovider"
android:enabled="true" android:exported="false"/> </application> </manifest> 124 @nisrulz -
AutoInit Android Library <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider" android:authorities="${applicationId}.awesomelibinitprovider"
android:enabled="true" android:exported="false"/> </application> </manifest> 125 @nisrulz -
AutoInit Android Library <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider" android:authorities="${applicationId}.awesomelibinitprovider"
android:enabled="true" android:exported="false"/> </application> </manifest> 126 @nisrulz -
AutoInit Android Library ContentProvider can be used to simplify the
process. 127 @nisrulz -
AutoInit Android Library ContentProvider can be used to simplify the
process. Challenges: • There can be only one Content Provider with a given “authority” string • Only run on main thread 128 @nisrulz -
AutoInit Android Library ContentProvider can be used to simplify the
process. 129 @nisrulz -
AutoInit Android Library ContentProvider can be used to simplify the
process. Why this is a bad idea: • Increases startup time • Bloats applications even when not used • Abusing functionality of ContentProvider 130 @nisrulz -
AutoInit Android Library ContentProvider can be used to simplify the
process. Why this is a bad idea: • Increases startup time (Solution: async initialize) • Bloats applications even when not used • Abusing functionality of ContentProvider 131 @nisrulz -
AutoInit Android Library ProcessLifecycleOwner [androidx.lifecycle:lifecycle-extensions:2.0.0] <application> <provider android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer" android:authorities="${applicationId}.lifecycle-process" android:exported="false"
android:multiprocess="true" /> </application> 133 @nisrulz -
AutoInit Android Library Firebase [com.google.firebase:firebase-common:16.0.5] <application> <provider android:name="com.google.firebase.provider.FirebaseInitProvider" android:authorities="${applicationId}.firebaseinitprovider" android:exported="false"
android:initOrder="100" /> </application> 134 @nisrulz -
AutoInit Android Library Facebook-Marketing [com.facebook.android:facebook-marketing:4.34.0] <application> <provider android:name="com.facebook.marketing.internal.MarketingInitProvider" android:authorities="${applicationId}.MarketingInitProvider" android:exported="false"
/> </application> 136 @nisrulz -
AutoInit Android Library Crashlytics [com.crashlytics.sdk.android:crashlytics:2.9.5] <application> <provider android:name="com.crashlytics.android.CrashlyticsInitProvider" android:authorities="${applicationId}.crashlyticsinitprovider" android:exported="false"
android:initOrder="90" /> </application> 137 @nisrulz -
AutoInit Android Library Picasso // Pre - v2.71828 Picasso.with(this).load("...<url>...").into(imageView); //
After - v2.71828 Picasso.get().load("...<url>...").into(imageView); 138 @nisrulz -
AutoInit Android Library Picasso // Pre - v2.71828 Picasso.with(this).load("...<url>...").into(imageView); //
After - v2.71828 Picasso.get().load("...<url>...").into(imageView); 139 @nisrulz -
Links/References Android Libraries I have built: https://github.com/nisrulz/nisrulz.github.io#open-source-co ntributions Auto initialize
android library example: https://github.com/nisrulz/android-examples/tree/develop/Au toInitLibrary Lifecycle Aware android library example: https://github.com/nisrulz/android-examples/tree/develop/Li feCycleCompForLib 141 @nisrulz