📜 ⬆️ ⬇️

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 tasks (implement working with http, json, multithreading, etc.), I had to do the same thing for the 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 his 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 parsing JSON


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 must be annotated and indicate the key for 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<Employee> 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(); } 

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

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

It's simple. But now you will say that there are a lot of different and powerful frameworks that already know how to do it all (for example, jackson ). I agree with you, but in most cases we do not use all the capacities of these frameworks. In such cases, why do we need extra ballast, if we can do with one class?

Small retreat

When developing applications, try to avoid multiple 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 is screaming with might and main that its development solves this problem. I'm not saying that dependencies are bad, just before you embed something in your project, you better think a few times if you need it.

The main reasons why many addictions are bad:

- the project becomes very bulky;
- performance deteriorates;
- in the later stages of development, the project becomes very dependent on third-party libraries, which, if necessary, are difficult to cut out from the project;

This says a person who has already stepped on this rake and who then 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 advantages and disadvantages:
- HttpURLConnection is slightly faster, but less convenient (as for me, it is only at first glance);
- ApacheHttpClient is much more convenient in relation to the previous one, but slower, and there are a couple of bugs in it;

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 themselves periodically go to the server for updates, while others we ourselves send. And we also need to cache some data. Take for example the news feed. Imagine that we have a request with which we receive new information, call it “update_news_request”.

Let's start creating a request

To build the Url there is a convenient builder:

  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<Company> body = new HttpBody<Company>(ompany); 

Everything is simple with headers too:

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

Now we will create an Http request, for this we have a handy 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 requests, we need the HttpExecutor class:

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

Let's figure it out. 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 specific request (the same key that was specified when creating the request is used here). That is, the server will not be accessed more than 30 seconds (in our case), all other attempts of this request will either go to the cache or do nothing at all. This is useful 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<RequestResponse>() { @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 object of our request itself, the second parameter is the class in which the result of the request is written, and the third parameter is the implementation of the Cache interface, we will apply here if the request is made more often than indicated in the limit. If you wish, you can not use Cache. Everything is simple and convenient.

Work with threads


I decided to use java.util.concurrent to work with streams. This package provides us with a bunch of handy 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 it so that two threads do not simultaneously execute the same request to the server.

This is where Semaphore comes to the rescue. Let's look at the code:

 public class CustomSemaphore { private Map<String, Semaphore> mRunningTask; public CustomSemaphore(){ mRunningTask = new HashMap<String, Semaphore>(); } 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 the thread executes the request to the server, we give this thread a lock and save our Semaphore in the Map, where the key is the key of our request “update_news_request”. While the first thread fulfills the request, the second stream arrives with the same request and at this moment it checks if the Semaphore key is stored in the 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 until the first thread releases the lock. Thus, two threads will not be able to simultaneously make the same request.

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

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

For example, you need to log in to a social network, then get a user profile, then register with our server with the necessary data. Thus, we get three requests. In such cases, you do not need to do nested callbacks. For example, you start another thread from a UI thread, which makes a request to the server, and then jerks a callback to the UI thread, which in turn starts another thread, which makes the next request - and so on. This approach creates multi-storey nesting in the code that is difficult to read and debug. A lot of unnecessary code is created. But most importantly, from the point of view of multithreading, it is a bad practice to create a lot of threads without need and constantly pull the UI thread. In such cases, it is better to make these three requests synchronous in one stream and process all the information in the same place, and send only the result to the UI stream.

There is a convenient solution to do 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 an implementation of the ScheduledFuture <?> Interface, with which we can stop the robot of our timer, and also request the result using the get () method. Just need to remember that this method is blocking.

Even in the ThreadExecutor class there are two convenient methods:

 doNetworkTaskAsync(final Callable<V> task, final ExecutorListener<V> listener) doBackgroundTaskAsync(final Callable<V> task, final ExecutorListener<V> listener) 

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

Conclusion


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

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


All Articles