We would like to share with you the experience that we, in
Futurice , have gained by developing Android applications. We hope these tips will save you from creating your own bikes. If you are interested in
iOS or
Windows Phone development, pay attention to the relevant documents on our website.
Briefly about the main thing
- Use the Gradle build system and standard project structure.
- Store passwords and other important data in the file gradle.properties
- Do not write your own HTTP client, better use the Volley or OkHttp libraries.
- Use the Jackson library to parse JSON data
- Avoid using Guava and generally save - try not to exceed the limit of 65k methods.
- Use snippets to display the user interface.
- Use activity only to manage fragments.
- XML markup is code, write it neatly
- Use styles to not duplicate attributes in XML markup.
- Better a few files with styles than a hefty one that will be hard to read.
- Try to avoid duplicates in colors.xml and make it shorter - mainly to store the color palette.
- The same applies to dimens.xml, define only basic constants there
- Do not make too deep a hierarchy of ViewGroup elements.
- Avoid running on the client side when using WebView, be careful of possible leaks
- Use Robolectric for unit testing, and Robotium for testing UI
- Use Genymotion as an emulator
- Always use ProGuard or DexGuard
Android SDK
Place your Android SDK in your home directory or other place not related to the application. Some IDEs are bundled with the SDK, and can be installed in their own directory. This may prevent you from updating (or reinstalling) the IDE, or when you switch to another IDE. Avoid installing the SDK in a different system directory, as this may require administrative privileges if your IDE is launched with user rights and not administrator privileges.
Assembly system
The default selection should be Gradle. Ant is much more modest in its capabilities, and besides its instructions are less compact. With Gradle, you can easily:
')
- Create various variations and builds of your application.
- Create simple tasks in the form of a script
- Manage dependencies and automatically download them.
- Customize keystore
- And many other useful things.
Also note that Gradle plugin for Android is actively being developed by Google as a new standard for build systems.
Project structure
There are two common options: the old Ant & Eclipse ADT project structure - or the new Gradle & Android Studio. It is better to choose the second option. If your project uses the old structure, we recommend porting it.
Old structureold-structure ββ assets ββ libs ββ res ββ src β ββ com/futurice/project ββ AndroidManifest.xml ββ build.gradle ββ project.properties ββ proguard-rules.pro
New structure new-structure ββ library-foobar ββ app β ββ libs β ββ src β β ββ androidTest β β β ββ java β β β ββ com/futurice/project β β ββ main β β ββ java β β β ββ com/futurice/project β β ββ res β β ββ AndroidManifest.xml β ββ build.gradle β ββ proguard-rules.pro ββ build.gradle ββ settings.gradle
The main difference is that the new structure explicitly shares the 'resource
androidTest
' (
main
,
androidTest
), this is one of the concepts of Gradle. You can, for example, add the 'paid' and 'free' folders to your 'src' folder, and they will contain the source code for the paid and free versions of your application.
Having an
app
folder at the top of the hierarchy helps separate it from the libraries (for example,
library-foobar
) that the application uses. The
settings.gradle
file in this case stores a list of these library projects that
app/build.gradle
can refer to.
Gradle configuration
General structure. Follow
the Google instructions for Gradle for Android .
Small assembly tasks. Unlike other scripting languages ββ(shell, Python, Perl, etc.), you can create build tasks in Gradle. See the
Gradle documentation for details.
Passwords In your
build.gradle
you need to define signing parameters (
signingConfigs
) for the release build. You should avoid this error:
Do not do this. This information will appear in the version control system.
signingConfigs { release { storeFile file("myapp.keystore") storePassword "password123" keyAlias "thekey" keyPassword "password789" } }
It would be more correct to create the file
gradle.properties
, which will not be added under the control of the version control system:
KEYSTORE_PASSWORD=password123 KEY_PASSWORD=password789
This data is automatically imported into gradle, and you can use it in
build.gradle
as follows:
signingConfigs { release { try { storeFile file("myapp.keystore") storePassword KEYSTORE_PASSWORD keyAlias "thekey" keyPassword KEY_PASSWORD } catch (ex) { throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.") } } }
Try to use Maven dependencies instead of importing jar files. If you include external jar files in your project, they will be mothballed in the version at which the import occurred, for example 2.1.1. Manual loading of jar-files and updating them is quite a time-consuming operation, and Maven can perfectly solve this problem for us by including the result in the assembly. For example:
dependencies { compile 'com.squareup.okhttp:okhttp:2.2.0' compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0' }
Avoid using dynamic Maven dependencies. Avoid specifying dynamically generated versions, for example
2.1.+
, As this can lead to instability of assemblies that depend on uncontrolled differences in the interaction of different versions of libraries. Using static version numbers, such as
2.1.1
, will help create more stable assemblies with predictable behavior.
IDE and text editor
Use any editor you like, but it should be well compatible with the project structure. The editor is your personal choice, and you should choose one that will be convenient to work with within your project structure and build system.
The most popular IDE at the moment is
Android Studio , since it is being developed by Google, integrated with Gradle, uses a new project structure by default, is in a stable build state and sharpened for Android development.
You can use Eclipse ADT if you like it, but you have to customize it, because it works by default with the old project structure and the Ant build system. You can even use text editors Vim, Sublime Text, or Emacs. In this case, you will have to use Gradle and adb from the command line. If you donβt manage to make Eclipse friends with Gradle, youβll also need to use the command line to build. Given that the ADT plugin has recently been declared obsolete, it is better to simply switch to Android Studio.
Whatever you use, keep in mind that Gradle and the new project structure are the officially recommended way to build applications, and do not add your editor-specific configuration files to the version control system. For example, do not add the Ant
build.xml
file. Also do not forget to update
build.gradle
when you change the build configuration in Ant. In general, do not force other developers to use unusual tools.
Libraries
Jackson is a Java library for converting objects to JSON and vice versa.
Gson is the most common way to solve this problem, but according to our observations, Jackson is more productive because it supports alternative ways to handle JSON:
streaming , the in-memory tree model, and the traditional connection of JSON-POJO formats. Keep in mind, however, that Jackson is larger than GSON in size, so you may prefer GSON in order to avoid the 65k limit. Other options:
Json-smart and
Boon JSON .
Networking, caching and images. There are a couple of proven solutions for productive requests to backend servers that you should consider before developing your own client. Use
Volley or
Retrofit . Volley also provides image loading and caching tools. If you choose Retrofit, take
Picasso to download or cache images, and
OkHttp for effective HTTP requests. All of these libraries - Retrofit, Picasso and OkHttp are developed by one company, so they complement each other perfectly.
OkHttp can also be used with Volley .
RxJava is a library for Reactive Programming, in other words, for handling asynchronous events. This is a powerful and promising concept, which can be confusing. We recommend that you think carefully before using this library as the foundation of the architecture of the entire application. There are projects created using RxJava, and you can ask for help from one of these people: Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen. We wrote several articles to our blog about this:
[1] ,
[2] ,
[3] ,
[4] .
If you haven't worked with Rx before, start by using the API. Or you can start by using it to handle simple user interface events, such as clicking or typing in a search field. If you are confident in your Rx skills and want to use it in the whole architecture, write Javadocs for the most difficult moments. Keep in mind that a programmer who does not have experience using RxJava,
curses you may have big problems with project support. Try to help him understand your code and Rx.
Retrolambda is a Java library for using lambda expressions on Android and other platforms with a JDK below version 8. It will help you make your code more compact and readable, especially if you use a functional style, for example, with RxJava. To use it, install JDK8, enter it in the path to the SDK in Android Studio in the project structure description dialog, and set the
JAVA8_HOME
and
JAVA7_HOME
environment variables, then write the following in the root
build.gradle
project:
dependencies { classpath 'me.tatarka:gradle-retrolambda:2.4.1' }
and in the file
build.gradle
each module add
apply plugin: 'retrolambda' android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } retrolambda { jdk System.getenv("JAVA8_HOME") oldJdk System.getenv("JAVA7_HOME") javaVersion JavaVersion.VERSION_1_7 }
Android Studio will begin to support lambda expression syntax. If you have not used them before, you can start by understanding the statements:
- Any interface with one method is compatible with lambda expressions and can be simplified in writing.
- If you donβt understand how to describe the parameters, write a regular inner class and let Android Studio translate it into a lambda expression.
Remember to limit the dex file to the number of methods, and avoid using a large number of libraries. Android applications, when packaged in a dex file, have a hard limit of 65,536 reference methods
[1] [2] [3] . If you exceed the limit, you will get a fatal compilation error. So we recommend using the minimum possible number of libraries, and pay attention to the
utility for calculating the number of methods in the dex-file . It will help determine which set of libraries can be used without exceeding the limit. Be especially careful when using the Guava library, which contains more than 13k methods.
Activity and fragments
In the community of Android developers (as in Futurice) there is no consensus on how best to build an Android application architecture in terms of using fragments and activity. Square even released a library for building architecture mainly using view, thus minimizing the need for fragments, but this method has not yet become generally accepted.
Based on the history of the development of the Android API, you can view the fragments as part of the user interface of the screen. In other words, fragments usually refer to UI. Activities are usually considered as controllers, they are especially important from the point of view of their life cycle and for state management. However, it may be different: activity can perform functions related to UI (state transition between screens), and fragments can only be used as controllers. We would advise to make an informed decision, bearing in mind that an architecture based on the use of fragments only, or just an activity, or only a view, can have a number of drawbacks. Here are a couple of tips that you should pay attention to, but be critical to them:
- Avoid intensive use of nested fragments, because of the possibility of βmatryoshkaβ type errors . Use nested fragments only if it makes sense (for example, fragments in a horizontally scrolling ViewPager inside a fragment of the screen) or if you are well aware of what you are doing.
- Do not put too much code in the activity. If possible, use them as lightweight containers that exist in your application mainly for lifecycle management and other important Android API functions. An activity with one fragment is better than just an activity β put code related to the user interface into a fragment. This will make it possible to reuse it in case you need to place it in the markup with tabs, or on a tablet screen with several fragments. Avoid creating an activity without associated fragments, except when you do it on purpose.
- You should not abuse the Android API level, for example, blindly relying on the Intent mechanism for the internal operation of the application. You can affect the Android operating system or other applications by causing errors or freezes. For example, it is known that if your application uses the Intent mechanism for internal communication between application packages, you can cause a hangup in a few seconds if the application was opened immediately after loading the operating system.
Java package architecture
Java architecture for Android applications resembles the
Model-View-Controller pattern. In Android,
fragments and activity represent Conroller classes . On the other hand, they are part of the user interface, so they are also part of the View.
Therefore, it is difficult to assign fragments (or activity) uniquely to the Controller or View. Better to put them in your own
fragments
package. Activity in this case can be left in the top-level package. If you have more than two or three activities, you can also put them in a separate package.
On the other hand, the architecture may look like a normal MVC, with the
models
package containing POJOs objects generated using the JSON parser from API responses, and the
views
package containing author View, alerts, View classes associated with the action bar, widgets, and so on. d. Adapters are the link between data and views. Given that they typically use View, exported via the
getView()
method, you can include adapters as a child package of
adapters
in
views
.
Some controller classes are used throughout the application and work directly with the Android operating system. They can be placed in the package
managers
. Various data processing classes, such as DateUtils, can be stored in the
utils
package. The classes responsible for interacting with the backend are in the
network
package.
All of the above packages, in order from the backend to the user interface:
com.futurice.project ββ network ββ models ββ managers ββ utils ββ fragments ββ views ββ adapters ββ actionbar ββ widgets ββ notifications
Resources
Naming Follow the convention on using an object type as a prefix for a file name, as in
type_foo_bar.xml
. Examples:
fragment_contact_details.xml
,
view_primary_button.xml
,
activity_main.xml
.
XML markup structure. If you are not sure how to format XML markup, the following tips may help.
- One attribute per line, indented with 4 spaces
android:id
always in first placeandroid:layout_****
attributes at the beginningstyle
attribute in last place- The closing tag
/>
is on its line to facilitate ordering and adding attributes. - Instead of writing
android:text
manually, you can use the Visual Attribute Editor for Android Studio.
Example <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="@string/name" style="@style/FancyText" /> <include layout="@layout/reusable_part" /> </LinearLayout>
As experience shows, the
android:layout_****
attribute must be defined in XML markup, and the remaining
android:****
attributes must be defined in XML styles. This rule has exceptions, but in general it works well. The point is to store only markup attributes (position, margins, size) and content attributes in the markup file, and the details of the visual components (colors, indents, fonts) should be in the style files.
Exceptions:
android:id
surely must be in the markup fileandroid:orientation
for a LinearLayout object usually makes sense in a markup fileandroid:text
must be in the markup file because it describes the content- Sometimes it makes sense to define in the general style
android:layout_width
and android:layout_height
, but by default these attributes should be in the markup file
Use styles. Virtually every project must use styles correctly, since there are usually duplicate display attributes for the view. At a minimum, you should have a general style for most of the textual content of the application, for example:
<style name="ContentText"> <item name="android:textSize">@dimen/font_normal</item> <item name="android:textColor">@color/basic_black</item> </style>
Applicable to TextView:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/price" style="@style/ContentText" />
You will probably have to do the same for the buttons, but donβt stop there. Develop this concept and bring all groups of duplicate
android:****
attributes related to certain types of visual components into styles.
Split a large file with styles into several smaller ones. There is no need to store all styles in a single
styles.xml
. The Android SDK supports various file names by default, so there is no problem with naming files with styles β they just need to contain the βstyleβ XML tag. So you can create
styles.xml
,
styles_home.xml
,
styles_item_details.xml
,
styles_forms.xml
. Unlike directory names, which are important for the build system, file names in
res/values
can be arbitrary.
colors.xml
is a color palette . Do not put anything in
colors.xml
, except the connection of the color name with its RGBA value. Do not use it to determine RGBA values ββfor different types of buttons.
So wrong <resources> <color name="button_foreground">#FFFFFF</color> <color name="button_background">#2A91BD</color> <color name="comment_background_inactive">#5F5F5F</color> <color name="comment_background_active">#939393</color> <color name="comment_foreground">#FFFFFF</color> <color name="comment_foreground_important">#FF9D2F</color> ... <color name="comment_shadow">#323232</color>
With this approach, it is very easy to create duplicate RGBA values, and colors are much more difficult to change. In addition, these colors refer to specific content, βbuttonβ or βcommentβ, and should be described in the style of the button, not in
colors.xml
.
And so - right <resources> <color name="white" >#FFFFFF</color> <color name="gray_light">#DBDBDB</color> <color name="gray" >#939393</color> <color name="gray_dark" >#5F5F5F</color> <color name="black" >#323232</color> <color name="green">#27D34D</color> <color name="blue">#2A91BD</color> <color name="orange">#FF9D2F</color> <color name="red">#FF432F</color> </resources>
The color palette is determined by the application designer. Colors do not have to be called "green", "blue", etc. Names like βbrand_primaryβ, βbrand_secondaryβ, βbrand_negativeβ are also quite acceptable. Such color formatting makes it easy to change or refactor, and also makes it easy to understand how many colors are used. To create a beautiful user interface, it is important, if possible, to reduce the number of colors used.
Design dimensionals.xml as colors.xml . For the same reason, it is also worth defining a βpaletteβ of typical sizes of objects and fonts.
An example of a good dimens.xml structure <resources> <dimen name="font_larger">22sp</dimen> <dimen name="font_large">18sp</dimen> <dimen name="font_normal">15sp</dimen> <dimen name="font_small">12sp</dimen> <dimen name="spacing_huge">40dp</dimen> <dimen name="spacing_large">24dp</dimen> <dimen name="spacing_normal">14dp</dimen> <dimen name="spacing_small">10dp</dimen> <dimen name="spacing_tiny">4dp</dimen> <dimen name="button_height_tall">60dp</dimen> <dimen name="button_height_normal">40dp</dimen> <dimen name="button_height_short">32dp</dimen> </resources>
We recommend not to write numeric values ββin duplicate markup attributes (fields and indents), but to use constants of the form
spacing_****
(approximately the way you usually do for localization of string values).
This will make the markup clearer and make it easier to change.
strings.xmlUse keys in string naming, as in package naming β this will allow you to solve the problem with the same constant names and better understand the context for their use.
Bad example <string name="network_error">Network error</string> <string name="call_failed">Call failed</string> <string name="map_failed">Map loading failed</string>
Good example <string name="error.message.network">Network error</string> <string name="error.message.call">Call failed</string> <string name="error.message.map">Map loading failed</string>
Do not write string values ββin lower case. You can use regular text conversions (including converting the first letter to uppercase). If you need to write the entire line in lower case letters - use the
textAllCaps attribute of the
TextView object.
Bad example <string name="error.message.call">CALL FAILED</string>
Good example <string name="error.message.call">Call failed</string>
Avoid deep view hierarchy . Sometimes you will be tempted to add another LinearLayout to solve your view description task.
As a result, you may get something like <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout ... > <LinearLayout ... > <LinearLayout ... > <LinearLayout ... > </LinearLayout> </LinearLayout> </LinearLayout> </RelativeLayout> </LinearLayout>
, ( Java) view views.
. , . , β
StackOverflowError .
view :
RelativeLayout ,
.
WebView. web-, , HTML, backend- «» HTML. WebView Activity, ApplicationContext. WebView , TextView Button.
Android SDK ( Espresso 2.1 ?! β . .), . Android Gradle
connectedAndroidTest , JUnit,
JUnit Android . , .
[1] [2] .
Robolectric unit-, UI. , , unit- view. , Robolectric . , , , .., Β« Β» ( ).
Robotium . UI- Robotium, view . :
solo.sendKey(Solo.MENU); solo.clickOnText("More"); // searches for the first occurence of "More" and clicks on it solo.clickOnText("Preferences"); solo.clickOnText("Edit File Extensions"); Assert.assertTrue(solo.searchText("rtf"));
Emulators
If you are engaged in Android development professionally, buy a license for the Genymotion emulator. It works faster than a regular AVD emulator. Emulator Genymotion allows you to record a video showing the operation of your application, emulates various quality of network connections, GPS signals and much more. It is ideal for running tests. You will have access to many (although not all) images of devices with Android OS, so the cost of a Genymotion license is much cheaper than buying a lot of devices.Pitfalls: Genymotion does not allow Google services such as the Google Play Store or Maps to be used in an application. And if you need to test the functions of the Samsung API, you have to buy a real device.Proguard Configuration
ProGuard is commonly used in Android projects for compressing and obfuscating code.Terms of use for ProGuard depend on your project settings. Usually you configure gradle to use ProGuard to build the release version. buildTypes { debug { minifyEnabled false } release { signingConfig signingConfigs.release minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
, , . , , , , .. , Android framework,
SDK_HOME/tools/proguard/proguard-android.txt
. ProGuard,
my-project/app/proguard-rules.pro
, .
, ProGuard β
ClassNotFoundException
NoSuchFieldException
, (,
assembleRelease
) . :
- ProGuard , enum, , , .
- ProGuard , enum , , Java reflection.
app/build/outputs/proguard/release/usage.txt
. ,
app/build/outputs/proguard/release/mapping.txt
.
,
ProGuard',
keep
:
-keep class com.futurice.project.MyClass { *; }
keepnames
:
-keepnames class com.futurice.project.MyClass { *; }
ProGuard
. Proguard
.
, , , ProGuard . , . Β«1.0Β» , .
. mapping.txt . mapping.txt , , .
DexGuard. , , DexGuard, ProGuard. Dex- 65k .
Thanks
Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori MΓ€ntylΓ€, Mark Voit, Andre Medeiros, Paul Houghton Futurice , Android.
License
Futurice Oy Creative Commons Attribution 4.0 International (CC BY 4.0)