πŸ“œ ⬆️ ⬇️

Android development best practices

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



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

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 structure
old-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:


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:


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.


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:

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> <!-- grayscale --> <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> <!-- basic colors --> <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> <!-- font sizes --> <dimen name="font_larger">22sp</dimen> <dimen name="font_large">18sp</dimen> <dimen name="font_normal">15sp</dimen> <dimen name="font_small">12sp</dimen> <!-- typical spacing between two views --> <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> <!-- typical sizes of views --> <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.xml

Use 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 ) . :


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)

Source: https://habr.com/ru/post/262501/


All Articles