官术网_书友最值得收藏!

Making events quick

Android places very strict limits on the use of threads in applications: every application has a main thread, where all user-interface related code must run, but any long-running code will cause an error. Any attempt at networking on the main thread will result in a NetworkOnMainThreadException immediately, as networking by its very nature will block the main thread for too long, making the application unresponsive.

This means most tasks that you will want to perform should take place on a background worker thread. This will also provide you with a form of isolation from the user interface, as typically you will capture the user interface state on the main thread, pass the state to the background thread, process the event and then, send the result back to the main thread where you will update the user interface. How do we know that the state we capture will be consistent? The answer is that because user interface code can only run on the main thread, while you read the state of the widgets, any events that would change their state are blocked until you are finished (because they must also occur on the main thread). The message queue and threading rules avoid the need for locks and other thread protection mechanisms by ensuring that only one unit of code (in the form of a message) is processed at a time.

Android tasks that require larger amounts of background processing time are usually written using the AsyncTask class provided by the Android platform (or one of its child classes). AsyncTask has methods for running code on a background worker, and publishing status updates to the main thread (and receiving these update messages), along with several other utility structures. This makes it ideally suited to tasks such as downloading large files, where the user needs to be kept informed of the download's progress. However, most event handlers you will implement won't need anywhere near to this level of complexity.

Most event handlers are relatively lightweight, but that doesn't mean that it will perform quickly on all devices in all situations. You can't control what else the user is busy doing with their device, and a simple database query can end up taking much longer than expected. As such, it's better to push event processing to background threads wherever the event is not purely a user-interface update (that is, showing a dialog or similar). Even fairly small tasks should be moved to a background thread so that the main thread can continue consuming the user's input; this will keep your application responsive. Here's the pattern you should try and follow when implementing event handlers:

  • On the Main Thread: First, capture any required parameters
  • On a Background Worker: Process the user's event and data
  • On the Main Thread: End by updating the user interface with the new state

If you keep to this pattern, the application will always appear responsive to your users, since processing their data isn't stopping their events from being processed (events which may be them scrolling through a large list, for example). However, AsyncTask is not a great fit for these smaller events (such as attaching a file to a claim), so here's how to write a simple class (in a similar style to the command pattern) that will run first some code on the background and then pass the result of that code to another method on the main thread, perfect for carrying out smaller events:

  1. Right-click on your root package (that is, com.packtpub.claim) and choose New| Java Class.
  2. Name the class util.ActionCommand.
  3. Change the Modifiers to make the new class Abstract.
  4. Click OK to create the new package (util) and class.
  5. Change the class definition to include generic parameters for a "parameter" and a "returned" type:
public abstract class ActionCommand<P, R> {
  1. At the top of the new class, create a static constant that refers to the application main thread via an android.os.Handler object:
private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());

A Handler object is how you gain access to another thread's message-queue in Android. In this case, any message or Runnable object posted to this Handler will be run on the main thread as soon as possible. You can also post tasks to be run at specific times or after a specified delay. This is the preferred method of creating timers on Android.

  1. Create three method declarations for running code on the background worker, the main thread, and one for handling errors (with a default implementation):
public abstract R onBackground(final P value) throws Exception;
public abstract void onForeground(final R value);

public void onError(final Exception error) {
Log.e(
getClass().getSimpleName(),
"Error while processing data",
error
);
}
  1. Then, create two variations of an exec method that will be used to start the ActionCommand objects. The first one uses the standard Executor provided by AsyncTask that uses a single background thread to process tasks (this is the most common behavior you will want in an application):
public void exec(final P parameter) {
exec(parameter, AsyncTask.SERIAL_EXECUTOR);
}

public void exec(final P parameter, final Executor background) {
background.execute(new ActionCommandRunner(parameter, this));
}
  1. In the preceding method, we submit an ActionCommandRunner object to the background Executor object; this is a private inner class that will carry the state between the background and main thread, which keeps the ActionCommand classes reusable and stateless:
private static class ActionCommandRunner implements Runnable {
  1. ActionCommandRunner will be in one of the three possible states: background, foreground, or error. Declare three constants as names, and a field to keep track of which state the object is in:
private static final int STATE_BACKGROUND = 1;
private static final int STATE_FOREGROUND = 2;
private static final int STATE_ERROR = 3;
private int state = STATE_BACKGROUND;
  1. Then, you'll need fields for the ActionCommand being run, and the current value. The value field is a catch-all in this class holding either the input parameter, the output from the background code, or the Exception thrown from the background code:
private final ActionCommand command;
private Object value;

ActionCommandRunner(
final Object value,
final ActionCommand command) {

this.value = value;
this.command = command;
}
  1. Now, create methods to handle each of the ActionCommandRunner states:
void onBackground() {
try {
// our current "value" is the commands parameter
this.value = command.onBackground(value);
this.state = STATE_FOREGROUND;
} catch (final Exception error) {
this.value = error;
this.state = STATE_ERROR;
} finally {
MAIN_HANDLER.post(this);
}
}

void onForeground() {
try {
command.onForeground(value);
} catch (final Exception error) {
this.value = error;
this.state = STATE_ERROR;

// we go into an error state, and foreground to deliver it
MAIN_HANDLER.post(this);
}
}

void onError() {
command.onError((Exception) value);
}
  1. Finally, create the run method that will call the preceding onBackground, onForeground or onError method depending on the current execution state of ActionCommandRunner:
@Override
public void run() {
switch (state) {
case STATE_BACKGROUND:
onBackground();
break;
case STATE_FOREGROUND:
onForeground();
break;
case STATE_ERROR:
onError();
break;
}
}

This class makes it very easy to create and reuse small tasks, which can be extended, composed, mocked, and tested in isolation. It's a good idea whenever creating a new event handler to consider a command pattern or something similar so that the event isn't coupled to the widget or even the screen that you are busy with. This allows for better code reuse, and keeps code easier to test since you can test the event handler without the screen that it will be part of later. You can also make these classes even more modular by writing them as abstract classes with only their onBackground methods implemented, allowing the result to be processed in different ways by subclasses.

主站蜘蛛池模板: 东海县| 呼图壁县| 佛冈县| 嘉鱼县| 北碚区| 成武县| 玛曲县| 涿州市| 禹州市| 西乌珠穆沁旗| 钟山县| 凌云县| 太保市| 腾冲县| 苏尼特右旗| 南部县| 汝南县| 石渠县| 洛浦县| 达孜县| 罗源县| 嵩明县| 濮阳县| 错那县| 佛教| 清水县| 兴隆县| 鄂温| 洪湖市| 浑源县| 花莲县| 龙里县| 濮阳市| 格尔木市| 鄂托克前旗| 中牟县| 雷波县| 本溪| 鹤岗市| 中方县| 大渡口区|