Droidutils - a set of solutions that accelerate the development of applications for Android

When developing applications, I noticed that every time I had to deal with the solution of similar problems (implement work with http, json, multithreading, etc.), I had to do the same robot, and it took a lot of time. At first it was not critical, but in large projects it took too much time. To save my and your time, I decided to write a universal solution for these tasks, which I want to share with the community.

Let's start with JSON parsing


Droidutils provides a convenient class for working with JSON, which allows you to convert data into JSON and back into an object of a class that implements the structure of a specific JSON. Let's look at an example.

We have JSON:

  {
    "example":{
         "test":"Hello World"
    },
    "company_name":"Google",
    "staff":[
            {
             "Name":"David"
         },
            {
             "Name":"Mike"
         }
     ],
}

Now we need a class in which we write the data. Each field in which we want to write certain data should be annotated and indicate the key by which the data is stored in JSON.

 public class Company {
    // можно указывать конкретное поле из JSONObject
    @JsonKey("test")
    private String mTest;
    @JsonKey("company_name")
    private String mCompanyName;
    @JsonKey("staff")
    private LinkedList mStaff;
    public class Employee {
       @JsonKey("Name")
       private String mName;
    }
}

Everything is ready, now we can parse JSON.

    JsonConverter converter = new JsonConverter();
        try {
            // Получаем объект нашего класса уже с данными из JSON
            Company company = converter.readJson(exampleJson, Company.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

The opposite effect is also possible. To do this, create an instance of the class and fill it with data (the fields also need to be marked with annotations) and pass it to the parser, as a result we get the JSON string:

   String json = converter.convertToJsonString(new Company());

Everything is simple. But now you will say that there are a bunch of different and powerful frameworks that already know all this (for example jackson ). I agree with you, but in most cases we do not use all the power of these frameworks. In such cases, why do we need extra ballast if we can manage with one class?

Small retreat

When developing applications, try to avoid many dependencies. Do not rush to connect a bunch of libraries to the project just because you are too lazy to write with your own pens. Or because the developer of this library shouts with might and main that its development solves this problem. I'm not saying that dependencies are bad, just before you implement something in your project, it’s better to think a few times if you need it.

The main reasons why many dependencies are bad:

- the project becomes very cumbersome;
- performance is deteriorating;
- at the later stages of development, the project becomes very dependent on third-party libraries, which are difficult to cut out of the project if necessary;

This is said by a person who has already stepped on this rake and who later had to redo a lot of things. This, as we know, is the loss of precious time and money.

Work with Http


In order to work with Http in Android, we can use one of two standard solutions: ApacheHttpClient or HttpURLConnection. I chose HttpURLConnection, as the guys from Google use it themselves and recommend it to us.

Now about the
pros and cons: - HttpURLConnection a little faster, but less convenient (as for me, this is only at first glance);
- ApacheHttpClient is much more convenient in relation to the previous one, but slower, and it has a couple of bugs;

Let's imagine that we are developing an application that communicates closely with the server. We have a bunch of different requests that need to be sent to the server. Some of them ourselves periodically go to the server for updates, while others we ourselves send. And we also need to cache some data. Take news feed as an example. Imagine that we have a request, with the help of which we get new information, call it “update_news_request”.

Let's create a request

There is a convenient builder for building Url:

 String url = new Url.Builder("http://base_url?")
                            .addParameter("key1", "value1")
                            .addParameter("key2", "value2")
                            .build();
// На выходе получаем http://base_url?key1=value1&key2=value2

The request body can be created very simply:

// создаем объект класса, который реализует структуру тела запроса
// как в примере с JSON
Company сompany = new Company();
// передаем наш объект в конструктор HttpBody
HttpBody body = new HttpBody(сompany);

With headers everything is also simple:

HttpHeaders headers = new HttpHeaders();
headers.add("header1", "value1");
HttpHeader header = new HttpHeader("header2", "value2");
headers.add(header);

Now let's create an Http request, for this we have a convenient builder:

HttpRequest updateNewsRequest= new HttpRequest.Builder()
                            .setRequestKey("update_news_request") // указываем ключ, о нем чуть мы еще поговорим
                            .setHttpMethod(HttpMethod.GET) // указываем тип запроса(по умолчанию HttpMethod.GET)
                            .setUrl(url)
                            .setHttpBody(body)
                            .setHttpHeaders(header)
                            .setReadTimeout(10000) // устанавливает максимальное время ожидания входного потока для чтения
                                                                    // по умолчанию 30 сек.                                        
                            .setConnectTimeout(10000) // максимальное время ожидания подключения(по умолчанию 30 сек.)
                            .build();

So we created our request. To execute the queries, we need the HttpExecutor class:

HttpURLConnectionClient httpURLConnectionClient = new HttpURLConnectionClient();
httpURLConnectionClient.setRequestLimit("update_news_request", 30000);
httpExecutor = new HttpExecutor(httpURLConnectionClient);

Let's get it right. The HttpExecutor constructor requires an implementation of the HttpConnection interface. In our case, I use the implementation of HttpURLConnection (you can use another implementation). The second line sets the time limit for a particular request (the same key that was specified when creating the request is used here). That is, access to the server will occur no more than 30 seconds (in our case), all other attempts of this request will go to the cache or do nothing at all. This is convenient when you need to reduce the load on the server.

Now you can run the query:

RequestResponse response  = httpExecutor.execute(request, RequestResponse.class, new Cache() {
                        @Override
                        public RequestResponse syncCache(RequestResponse data, String requestKey) {
                            // пишем в кеш и возвращаем данные уже с кеша
                            // так мы избежим проблем синхронизации данных на сервере и в кеше
                            return data;
                        }
                        @Override
                        public RequestResponse readFromCache(String requestKey) {
                            RequestResponse response = new RequestResponse();
                            response.hello = "hello from cache";
                            return response;
                        }
                    });

The first parameter is the actual object of our request, the Second parameter is the class into which the result of the request will be written and the Third parameter is the implementation of the Cache interface, we will contact here if the request is made more often than indicated in the limit. Optionally, you can not use Cache. Everything is simple and convenient.

Work with streams


I decided to use java.util.concurrent to work with streams . This package provides us with a bunch of all sorts of convenient tools and thread-safe data structures for working with multithreading.

When communicating with the server, a number of problems arise that need to be solved.
The first problem that needs to be solved is to make sure that two threads do not simultaneously execute the same server request.

Here Semaphore comes to our aid. Let's look at the code:

public class CustomSemaphore {
    private Map mRunningTask;
    public CustomSemaphore(){
        mRunningTask = new HashMap();
    }
    public synchronized void acquire(String taskTag) throws InterruptedException {
        Semaphore semaphore = null;
        if (!mRunningTask.containsKey(taskTag)) {
            semaphore = new Semaphore(1);
        } else {
            semaphore = mRunningTask.get(taskTag);
        }
        semaphore.acquire();
        mRunningTask.put(taskTag, semaphore);
    }
    public void release(String taskTag) throws InterruptedException {
        if (mRunningTask.containsKey(taskTag)) {
            mRunningTask.remove(taskTag).release();
        }
    }
}

So how does this thing work?

When a thread makes a request to the server, we give this thread a lock and save our Semaphore in Map, where the key is our update_news_request request key. While the first thread executes the request, the second thread arrives with the same request and at that moment it checks to see if the Semaphore key is stored in Map. If there is one, then he tries to take a lock in this Semaphore, and since the first thread has already taken it, the second thread stops and waits for the first thread to release the lock. Thus, two threads will not be able to make the same request at the same time.

Sometimes it is necessary that all requests to the server are executed only in turn. Then we just do not need to specify a key in the request and the default key will be used, one for all.

The second important problem arises when you need to make several queries in a row.

For example, you need to log in to some social network, then get the user profile, then with the necessary data go through registration on our server. So we get three queries. In such cases, you do not need to make nested callbacks. For example, you start another thread from the UI thread, which makes a request to the server, and then pulls a callback in the UI thread, which in turn starts another thread that makes the next request - and so on. This approach creates multi-storey nesting in the code that is difficult to read and debase. A lot of unnecessary code is generated. But most importantly, from the point of view of multithreading, it is a bad practice to create a bunch of threads unnecessarily and constantly pull the UI stream. In such cases, it is better to make these three requests synchronous in the same thread and process all the information there,

There is also a convenient solution for doing something on a timer. For example, go to the server for updates every 30 seconds:

 ScheduledFuture scheduledFuture = ThreadExecutor.doTaskWithInterval(new Runnable() {
            @Override
            public void run() {
                // ходим на сервер
            }
        }, 0, 30, TimeUnit.SECONDS);

This method also returns to us the implementation of the ScheduledFuture interface , with which we can stop the operation of our timer, and also request the result using the get () method. Just remember that this method is blocking.

There are two convenient methods in the ThreadExecutor class:

doNetworkTaskAsync(final Callable task, final ExecutorListener listener)
doBackgroundTaskAsync(final Callable task, final ExecutorListener listener)

The difference is that each has its own pool of threads, which is quite convenient.

Conclusion


So we got to the finish line. Thank you all for your attention.
All source codes can be found here .
Sound criticism is welcome.

Also popular now: