📜 ⬆️ ⬇️

Tips for creating a modern Android application. Yandex lecture

We continue to publish the 2017 Mobile Development School. The next step is a big lecture by Android developer Dmitry Nikitin from the Yandex.Mail team. Dmitry tells how to approach the creation of a project from scratch, not to get lost among the multitude of libraries and what to look for when choosing this or that solution.


- All of you have been programming for Android for at least a couple of months. Probably, someone has been programming for a couple of years and has already read from cover to cover developer.android.com. And maybe not. But you all know for sure how many things can be done in at least one way. But it’s no secret that there can be many of these methods; each team can have its own, and often this or that method is chosen solely for historical reasons.

Today I want to make a small overview of what alternatives and development tools are generally available and what it is worth paying attention to when choosing a particular library.

What means will we talk about today? First of all, about those that will help to realize the immediate functions of the application: this is what the project manager usually requires from us.
')
Secondly, what will help us control the quality of the code, make it more flexible and expandable. And then we look at things that are also related to the development, but are not directly related to the previous two points.



How effective is this tool in terms of speed, as well as in terms of consumed RAM and disk memory.

How will we choose what is better and what is worse? First, we will look at usability, how easy it will be for us to solve a particular problem with the help of this tool.

As a rule, well-known libraries are already covered with tests, and bugs appear more often when it is integrated, therefore, the more concise and simpler our client code is, the less likely it is to make a mistake.

On the other hand, we, with our sometimes not the most direct business logic, may need so much something specific, and here it will be a huge plus if the tool chosen by us is flexible enough to allow it to be done without crutches.

The following criterion applies exclusively to external libraries - this is a license. Since we often write the commercial code, the license should not only allow using software, but should not force us to disclose our source code.

The most popular license is Apache 2.0 and MIT. As a rule, they, by and large, do not impose any restrictions on us. Other licenses should be used with caution.

In addition, it is desirable that all that is needed to connect, has already been collected and distributed, if not together with the Android SDK, then lay somewhere on the JCenter or Maven central.

If something is not clear to us, then we could refer to the documentation or ask a question to those who support this product.

And the last point is in order, but not in order of importance - this is how much the tool brings with it the number of methods.



Why is it important? If our application supports an API smaller than Lollypop (and this is the majority of applications), on these devices our code is executed on the Dalvik virtual machine, and when building the APK, Java files are compiled into bytecode and then packaged into Dalvik executable files. Each such file may contain no more than 65 thousand methods, and if we reach this limit, we will encounter such an error during assembly.




How to be? In an amicable way, you need to keep track of which classes occupy as much and try to avoid unnecessary. That is, you always need to evaluate what kind of positive contribution this or that dependence gives us in relation to the number of methods it introduces.

For example, if we use Guava solely in order to safely close cursors, then it is worth writing your own utility method and abandoning such a large dependency.

If something is used for the most part in tests, then we can add this dependency to the test compile so as not to include it directly in the project.

You also need to note how expensive different syntactic sugar costs us, for example, a lambda expression or a reference method. For more information on this topic, see the cool report on Exploring Hidden Java Costs by Jake Wharton.

In addition to lambda, it is important to look at the other generated code. For example, Butter Knife since one of the versions began to generate 40% fewer methods.



How can you track which classes take up how much in a project? Dexcount plugin is suitable for this. It generates such a beautiful chart, according to which we can see who introduced how many methods.

Before we add a dependency, we can estimate how expensive it will cost us, that of the site methodscount.com . We can also see there, how many methods bring its transitive dependencies.

If all else fails, we can still use several dex files, but this brings additional problems. In addition to the fact that such applications, as a rule, start longer and consume more memory, in some cases they can also fall. When building with MultiDex, an analysis of which classes are needed for the successful launch of the application in order to put them into the primary dex-file is performed.

If dependencies are complex, for example, a call is made from native code, then such classes can fall into other dex-files, and we will get NoClassDefFoundError when starting. To avoid this, we will be forced to specify such classes in a separate config.

In addition, this analysis significantly slows down the build, and since the Lollypop version, Android Runtime has come to replace Dalvik, which already supports MultiDex on its own, and the SDK 21 builds with it much faster.



Therefore, as a hack, we can use the fact that, despite the fact that the actual minimum version of the SDK is much smaller, we specify 21 versions for the local build.



We take this value from the command line parameter in the studio, and this will enable us to locally assemble faster. At the same time, tape checks of the API level inside the studio will not break.

Summarize. What do we need to do with the number of methods? The way a healthy person is to count and reduce them naturally. You can also use proguard to remove what we plugged in, but not use. In the release collection, we already include proguard, so it may be normal practice for us if we only need to enable MultiDex in debug. But if it didn't work out, then all that remains is to turn on MultiDex, slightly brightening up your life with a fast local build.

What tasks we usually need to do in the application? Often we receive data from the server, process it in a certain way, show it on the screen, and if, for example, we need to work offline, then we must save it in some definite way. For example, in a database, shared preference or file.

Let's look at all these stages using the example of an application that shows user data from GitHub.

The user data itself is on the server, and we need to pick it up from there. How can we do this? For example, there is an HttpUrlConnection.



We go to StackOverflow, take the example of a get request, substitute our URL. We open Connection. We receive InputStream. We read the data we need here as a string. And do not forget to close the connection. With this query, everything is simple. Difficulties begin if we need to do something less trivial, for example, upload a file from ReadMe to the server. You will need to write your own wrapper.



And already of this size, although it would seem that the task is not so rare.

We also need to write tests for this, which we don’t want to do for, in fact, someone else’s code.

There are quite a few libraries that run on top of the HttpUrlConnection, in which the sending of files is simplified in this case, other features are also added, for example, an asynchronous call, etc.

For example, there is a HttpClient library from Kevin Savinsky. But there is an alternative that, out of the box, is devoid of all these shortcomings - this is OkHttp.



This is how the same file sending with OkHttp looks like. We just specify the URL. Create RequestBody. And execute the request.

Starting with Kitkat HttpUrlConnection at the network level, it itself uses OkHttp. Thus, if we choose this option, we will protect ourselves from the fact that on different versions of Android the library will behave differently.

But imagine if we want to add a useragent for a part of the requests, or change the behavior in the case of redirection. We can also do this pretty quickly with the OkHttp interceptors.



Override the intercept method. Get the original request. We can somehow change it and pass it further along the chain. And then just add this Interceptor to the client. In this case, we can call not addInterceptor, but addNetworkInterceptor, if we want to re-intercept requests in the case of a redirect.

In addition, OkHttp can perform requests asynchronously, work with web socket and do many other good things.



But there is one drawback. Nevertheless, this is a third-party library, and it pulls along 2.5 thousand methods. Is there any embedded solution that will save us from this?

At the dawn of Android was ApacheHttpClient, it was more stable in earlier versions, but the Google team decided that its API was too big for further development, and declared it deprecated.



We can still use it in our projects by explicitly specifying useLibrary “org.apache.http.legacy”, but then it will already be packaged in an APK, and we will not achieve our goal.



In addition to the options considered, there is also a Google-supported Voley library. It is higher level than previous alternatives. Just as OkHttp can execute requests asynchronously. She has good internal caching, and she even knows how to upload images, only for this our imageview must inherit the network imageview, which is a pretty strong limitation.

Voley, unfortunately, is not laid out on JCenter and Maven Central, it is imported into the project by source code, which, if we are not lucky, this source code will also be changed in the same commit, which is very bad.

Voley cannot boast of good documentation, unlike OkHttp.



So what should we take? If we have a strict limit on the number of methods, or working with the network is not so complicated that any non-trivial things are required, then we can take HttpUrlConnection or one of its wrappers. Otherwise, OkHttp looks like the best option. (For questions and answers, see video. - Ed.)

We ended up downloading the data. This is now a JSON string. And how can we get a convenient view from a Java point of view?



If there is little data, then we may well take the value we need using the built-in JSONObject. We consistently go around the whole structure until we meet the fields of interest. We read value, and we go further.

If there is a lot of data, then it becomes quite difficult to maintain this routine work, and it makes sense to use a specific library.



For example, there is Moshi from Square, which was originally created for Android, and due to this, it is more economical to consume memory. But it is rather slow.

There is also Gson from Google, it is faster, but consumes more RAM, although by the number of methods it is somewhat more attractive.

And there is Jackson, which is the fastest of the reflection-analogs. It has a separate streaming part and Databing for direct mapping. But at the same time he is the fattest.

In addition, there is LoganSquare ( link to GitHub ), which generates adapters for parsing when compiling. It uses the Jackson streaming API, and due to the compile time generation it significantly outperforms competitors in speed.



Comparison can be viewed on the benchmarks of its creators. It is seen that LoganSquare of the options presented is the fastest.



But compile time generation imposes certain restrictions on the structure. The entity itself cannot inherit anything. There is also a problem with generics.

There is another parser without reflection from Instagram - IgJsonParcer. It obliges us to annotate all fields directly, which we must process. This library does not yet have a stable version, and there are problems with the number of methods, as it depends on both Guava and Apache Commons.

Thus, from the presented solutions, the fastest is LoganSquare, and if speed is extremely important to us, then we can use it. Otherwise, choose from the previous options, based on the fact that we need more: speed, memory consumption, or the number of methods.



We have chosen the means. Do not forget to test it, and the parsing is most appropriate for test-driven development. We copy JSON from the documentation, write a test for it, and then write the parsing itself until the test turns green.

Often, additional fields may be added to the API.

To prevent our parsing from falling, you can add unknown fields to the tests in advance. Thus, we are guaranteed to be ready for such a change.



If, on the contrary, we want to send data to the server or save it as a string, we will need a reverse process - serialization, and tests will also help us to verify its correctness.

For example, the JsonUnit library is used here. It is used so that we do not compare the bare lines, because such a comparison is unstable to order, to indents, etc. And so, we compare directly the values ​​obtained by us.

Thus, we chose how to go to the network, chose what to parse, how we can now combine this with the help of the Retrofit library ( link to GitHub ).



So we will have a single access point, where we set the base URL. In which case we can quickly change it here, for example, for the QA environment. We can choose a converter between Moshi, Gson, Jackson or Protobuf. And the way we will make the calls. We can do it in sync.



Or asynchronously.



Or using Rx. To do this, we just need to specify that our API method returns single completable or observable. And choose the appropriate CallFactory.



We stopped at the fact that deserialized user data. How do we present them in Java now?



We can do it like this. But how bad can it be? Suppose that in our future the login field will be calculated directly at the call, or we will log all these calls.

Therefore, we change their visibility to privatev, and add getters and setters.





If we want to add it to HashMap, we need equals and hashCode.





Add here another beautiful String. And if we decide to transfer it in the intent to another activity, or save it to the bundle when the screen is flipped, then we will need this object to be Parcelable.

And when we learn that a new field is being added to the class, we will need to repeat these circles and not forget anything. Fortunately, there are libraries that can solve this problem.



For example, AutoValue ( link to GitHub ). We write the AutoValue annotation and abstract field access methods. Directly the fields themselves during assembly will generate a library. It will also generate all of these methods that we listed before.

The killer feature of AutoValue is that it is easy enough to use extensions. For example, there are Gson and Jackson extensions that allow you to generate compile time adapters, and due to this they can catch up with the same LoganSquare speed. Thus, we kill two birds with one stone: solve the problems of both parsing and internal representation of models.

There is an extension of AutoValue Parcel. If we connect it, then we can simply write implements Parcelable, and methods to support parselization will be generated for us. In this case, if one of the class fields is not serializable, we get an error when compiling.



There is another AutoValue extension - this is Auto-Parcel, which also does parselization, but it, unlike AutoValue Parcel, checks that the object is serialized exclusively in Runtime, and it also has a less popular EPL 1.0 license.

There is an alternative to AutoValue - this is Immutables. He also has adapters for Moshi and Gson, but there is no extension for Parcelable.

And there is Lombok, which, unlike the others, does not generate a successor, but replaces the original class.

To support all this magic, we need an IDE plugin and patience, because all this can be pretty buggy.

Thus, of the options considered, AutoValue and the Auto-Parcel extension for it looks the most beneficial. (For questions and answers, see video. - Ed.)

Imagine that, according to the user's age, we need to show him various types of banners. It seems to be all simple. We take the current date, calculate the date of birth and get the age.



But if we use both the java date and the standard calendar, we need to expect surprises. For example, what do we get if we call getYear? The correct answer is 117, because it is not just a year.

Because it is not just a year.



And as can be seen from Java.doc, this is a year minus 1900. Partly, we are to blame for not having read Java.doc in advance, and this deprecated method, but in general the number of deprecated methods of this API is too large.

Or, for example, not the most obvious point that the most famous method of the calendar GetInstance, in fact, each time creates a new calendar.



One of the most popular Java libraries is JodaTime ( link to GitHub ). With its use, we can so easily solve our problem with the user. We can also, for example, count the number of days before the New Year.

In addition, JodaTime includes the current version of the Tzdata file. This file contains a history of how the time zones of different regions changed over time, and how to get local time, based on UTC.

This file encapsulates in itself solutions for the whole mountain maybe. You can learn more about them in the Tom Scott video. The Tzdata file is in the system itself, but if we add it to the APK, we will protect ourselves from the problem, if the vendor does not release an update for their old devices. Instead, for example, vendors can make a beautiful three-dimensional model of choosing the time zone for their new customers, as HTC often does.



But the inclusion of the Tzdata file in the APK is not in vain. , , — 5 . . , Java, , ClassLoader.getResourceAsStream , .

AssetManager, , Joda Time Android.



JDK , Java - , , Joda Time JSR 310. API Android , back-port ThreeTenBP, , ThreeTenABP, « » Tzdata.



Summarize. ? APK, API. ThreeTenABP , Joda Time - Android , , .

, .



UI- , AsyncTask. ? , , - .



Picasso. , placeholders, , .

, , , , URL, . Picasso .



Glide, Picasso , , .



Fetcher, URL, .



, Glide . StreamModelLoader, .



Loader Module, Module Manifest, , . , , . proguard.



, Glide, .

, activity , Glide , , .



, Glide , , ImageView . Picasso, , .

Glide .

Universal ImageLoader, , , deprecated.

Fresco, ashmem, , .

gif. , Glide.



, ? , , , , Picasso. - gif, Glide. Picasso Glide , API. ( . — . .)

? , dependency injection, MVP, MVVM . .



dependency injection, reflection-based RoboGuice, , , , . . Java , , , , reflection , Android , , .

RoboGuice deprecated. - Dagger1, , reflection , , . Dagger2. , .

, , . , - scope, . Toothpick, , , , . reflection free, scope. , , .



Dagger, . ( GitHub .)



, RoboGuice .



, , . , , , API, TootPick . — , .

MVP MVVM , , , , , , , , . , . , - .



. , , , . , GreenDAO, ORMLite NoSQL Realm.

, — .

, , . , , - , .

, SQLDelight, Java- SQL-. , , , , Rx, , StorIO SQLBrite.

Room, , , , , .

, ? , , -, . , — .



— checkstyle ( SourceForge ). , , . Override , , , , . . .



checkstyle, . , , .



, , , , , , .



. Java-.



Lint, Android- , , , .



. , nullability, , , , , , .



. baseline. Lint . , , .



Lint. .



XML.



— PMD ( SourceForge ). Java. .



.



.



, PMD, Java-, -. FindBugs ( SourceForge ). , , .



, , . , hashCode, , . ?

FindBugs , hashCode int, - abs , IndexOutOfBounds.



, , FindBugs .



FindBugs. , XML, HTML-, , , .



- , Findbugs:annotations.

, FindBugs , SpotBugs, , , Android, .



, , , , , dashboard. dashboard .



.



, -.



Summarize. ? — . , , , . ( GitHub .)



, , , ? JitPack, , - fork, .



— Kotlin. Google , , . NullPointerException ClassCastException. data-, , , AutoValue, , . — , Parcelable.

pattern matching, . , , Kotlin , , proguard .

, final everywhere, final, . Mockito — , . — — Mockito 2.

any. , matcher, null Mockito. Kotlin- null, . Mockito Kotlin. , Spek — JetBrains. , , Roboelectric. — Detekt SonarQube. I have it all. Thanks for attention.

Contacts of the author: mail , Telegram

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


All Articles