技术控

    今日:122| 主题:49179
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Become a Firebase Taskmaster! (Part 3: Wiring up your Tasks)

[复制链接]
bzor4bVn 发表于 2016-9-30 14:51:32
118 6

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

Become a Firebase Taskmaster! (Part 3: Wiring up your Tasks)-1 (everything,Android,listener,generated,effective)
              Doug Stevenson
    Developer Advocate        Alrighty! Thanks for joining us for part three of this blog series about the Play services Task API for Android. By now, you've seen the essentials of the API inpart one, and how to select the best style of listener inpart two. So, at this point, you probably have everything you need to know to make effective use of the Tasks generated by Firebase APIs. But, if you want to press into some advanced usage of Tasks, keep reading!
  Put Yourself to Task  We know that some of the Firebase features for Android will do work for you and notify a Task upon completion. But, what if you want to create your own Tasks to perform threaded work? The Task API gives you the tools for this. If you want to work with the Task API without having to integrate Firebase into your app, you can get the library with a dependency in your build.gradle:
  [code]compile 'com.google.android.gms:play-services-tasks:9.6.1'[/code]   But, if you are integrating Firebase, you'll get this library included for free, so no need to call it out specifically in that case.
  There is just one method (with two variants) you can use to kick off a new Task. You can use the static method named "call" on the Tasks utility class for this. The variants are as follows:
  [code]Task call(Callable callable)
    Task call(Executor executor, Callable callable)[/code]   Just like addOnSuccessListener(), you have a version of call() that executes the work on the main thread and another that submits the work to an Executor. You specify the work to perform inside the passed Callable . A Java Callable is similar to a Runnable, except it's parameterized by some result type, and that type becomes the returned object type of its call() method. This result type then becomes the type of the Task returned by call(). Here's a really simple Callable that just returns a String:
  [code]public class CarlyCallable implements Callable {
        @Override
        public String call() throws Exception {
            return "Call me maybe";
        }
    }[/code]  Notice that CarlyCallable is parameterized by String, which means its call() method must return a String. Now, you can create a Task out of it with a single line:
  [code]Task task = Tasks.call(new CarlyCallable());[/code]   After this line executes, you can be certain that the call() method on the CarlyCallable will be invoked on the main thread, and you can add a listener to the Task to find the result (even though that result is totally predictable). More interesting Callables might actually load some data from a database or a network endpoint, and you'd want to have those blocking Callables run on an Executor using the second form of call() that accepts the Executor as the first argument.
  Working on the Chain (Gang)   Let's say, for the sake of example, you want to process the String result of the CarlyCallable Task after it's been generated. Imagine that we're not so much interested in the text of the resulting String itself, and more interested in a List of individual words in the String. But, we don't necessarily want to modify CarlyCallable because it's doing exactly what it's supposed to, and it could be used in other places as it’s written now. Instead, we'd rather encapsulate the logic that splits words into its own class, and use that after the CarlyCallable returns its String. We can do this with a Continuation . An implementation of the Continuation interface takes the output of one Task, does some processing on it, and returns a result object, not necessarily of the same type. Here's a Continuation that splits a string of words into an List of Strings with each word:
  [code]public class SeparateWays implements Continuation> {
        @Override
        public List then(Task task) throws Exception {
            return Arrays.asList(task.getResult().split(" +"));
        }
    }[/code]   Notice that the Continuation interface being implemented here is parameterized by two types, an input type (String) and an output type (List       ). The input and output types are used in the signature of the lone method then() to define what it's supposed to do. Of particular note is the parameter passed to then(). It's a Task        , and the String there must match the input type of the Continuation interface. This is how the Continuation gets its input - it pulls the finished result out of the completed Task.      
  Here's another Continuation that randomizes a List of Strings:
  [code]public class AllShookUp implements Continuation, List> {
        @Override
        public List then(@NonNull Task> task) throws Exception {
            // Randomize a copy of the List, not the input List itself, since it could be immutable
            final ArrayList shookUp = new ArrayList<>(task.getResult());
            Collections.shuffle(shookUp);
            return shookUp;
        }
    }[/code]  And another one that joins a List of Strings into a single space-separated String:
  [code]private static class ComeTogether implements Continuation, String> {
        @Override
        public String then(@NonNull Task> task) throws Exception {
            StringBuilder sb = new StringBuilder();
            for (String word : task.getResult()) {
                if (sb.length() > 0) {
                    sb.append(' ');
                }
                sb.append(word);
            }
            return sb.toString();
        }
    }[/code]  Maybe you can see where I'm going with this! Let's tie them all together into a chain of operations that randomizes the word order of a String from a starting Task, and generates a new String with that result:
  [code]Task playlist = Tasks.call(new CarlyCallable())
            .continueWith(new SeparateWays())
            .continueWith(new AllShookUp())
            .continueWith(new ComeTogether());
    playlist.addOnSuccessListener(new OnSuccessListener() {
        @Override
        public void onSuccess(String message) {
            // The final String with all the words randomized is here
        }
    });[/code]   The continueWith() method on Task returns a new Task that represents the computation of the prior Task after it’s been processed by the given Continuation. So, what we’re doing here is chaining calls to continueWith() to form a pipeline of operations that culminates in a final Task that waits for each stage to complete before completing.
  This chain of operations could be problematic if these they have to deal with large Strings, so let's modify it to do all the processing on other threads so we don't block up the main thread:
  [code]Executor executor = ... // you decide!

    Task playlist = Tasks.call(executor, new CarlyCallable())
        .continueWith(executor, new SeparateWays())
        .continueWith(executor, new AllShookUp())
        .continueWith(executor, new ComeTogether());
    playlist.addOnSuccessListener(executor, new OnSuccessListener

  
   
() {
        @Override
        public void onSuccess(String message) {
            // Do something with the output of this playlist!
        }
    });


  [/code]   Now, the Callable, all of the Continuations, and the final Task listener will each run on some thread determined by the Executor, freeing up the main thread to deal with UI stuff while this happens. It should be totally jank-free.
  At first blush, it could seem a bit foolish to separate all these operations into all the different classes. You could just as easily write this as a few lines in a single method that do only what's required. So, keep in mind that this is a simplified example intended to highlight how Tasks can work for you. The benefit of chaining of Tasks and Continuations (even for relatively simple functions) becomes more evident when you consider the following:
  
       
  • How might you plug in new sources of input Strings? So, what if we also had a BlondieCallable? And a PaulSimonCallable?   
  • What about different kinds of processing for the input Strings, such as a YouSpinMeRound continuation that rotated the order of the Strings in a List one position to the right (like a record)?   
  • What if you wanted different components of the processing pipeline to be executed on different threads?  
  Practically speaking, you're more likely to use Task continuations to perform a series of modular chain of filter, map, and reduce functions on a set of data, and keep those units of work off the main thread, if the collections can be large. But, I had fun with music theme here!
  What if the Playlist Breaks?  One last thing to know about Continuations. If a runtime exception is thrown during processing at any stage along the way, that exception will normally propagate all the way down to the failure listeners on the final Task in the chain. You can check for this yourself in any Continuation by asking the input Task if it completed successfully with the isSuccessful() method. Or, you can just blindly call getResult() (as is the case in the above samples), and if there was previously a failure, it will get re-thrown and automatically end up in the next Continuation. The listeners on the final Task in the chain should always check for failure, though, if failure is an option.
  So, for example, if the CarlyCallable in the above chain returned null, that would cause the SeparateWays continuation to throw a NullPointerException, which would propagate to the end of the Task. And if we had an OnFailureListener registered, that would get invoked with the same exception instance.
  Pop Quiz!   What's the most efficient way, with the above chain, of finding out the number of words in the original string, without modifying any of the processing components? Take a moment to think about it before reading on!
  The answer is probably more simple than you'd imagine. The most obvious solution is to count the number of words in the final output string, since their order only got randomized. But there is one more trick. Each call to continueWith() returns a new Task instance, but those are all invisible here because we used a chaining syntax to assemble them into the final Task. So you can intercept any of those those tasks and add another listener to it, in addition to the next Continuation:
  [code]Task> split_task = Tasks.call(new CarlyCallable())
        .continueWith(executor, new SeparateWays());
    split_task =
        .continueWith(executor, new AllShookUp())
        .continueWith(executor, new ComeTogether());
    split_task.addOnCompleteListener(executor, new OnCompleteListener>() {
        @Override
        public void onComplete(@NonNull Task> task) {
            // Find the number of words just by checking the size of the List
            int size = task.getResult().size();
        }
    });
    playlist.addOnCompleteListener( /* as before... */ );[/code]   When a Task finishes, it will trigger both of the Continuations on it, as well as all of the added listeners. All we've done here is intercept the Task that captures the output of the SeparateWays continuation, and listen to the output of that directly, without affecting the chain of continuations. With this intercepted task, we only have to call size() on the List to get the count.
  Wrapping Up (part 3 of this series)  All joking aside, the Task API makes it relatively easy for you to express and execute a sequential pipeline of processing in a modular fashion, while giving you the ability to specify which Executor is used at each stage in the process. You can do this /with or without/ Firebase integrated into your app, using your own Tasks or those that come from Firebase APIs. For the next and final part to this series, we'll look at how Tasks can be used in parallel to kick off multiple units of work simultaneously.
   As usual, if you have any questions, consider using Twitter with the #AskFirebase hashtag or the firebase-talk Google Group . We also have a dedicated Firebase Slack channel. And you can follow me @CodingDoug on Twitter to get notified of the next post in this series.
   Lastly, if you're wondering about all the songs I referenced in this post, you can find them here:
友荐云推荐




上一篇:他趣已有平台引入直播的实战之路|架构师实践日
下一篇:【译】IntersectionObserver 正在来到我们的视野
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

叫兽光波 发表于 2016-9-30 15:53:01
是爷们的娘们的都帮顶!大力支持
回复 支持 反对

使用道具 举报

sackey_ma 发表于 2016-9-30 16:26:58
楼上的且行且珍惜!
回复 支持 反对

使用道具 举报

txnetworkz 发表于 2016-10-2 00:14:07
楼主,笑一个,萌萌哒!
回复 支持 反对

使用道具 举报

梁可 发表于 2016-10-2 02:39:23
虫星人来了,都叫兽呢?
回复 支持 反对

使用道具 举报

邓倩 发表于 2016-10-22 04:54:30
不怕别人笑,看贴顶贴就很好
回复 支持 反对

使用道具 举报

luoliran 发表于 2016-10-22 05:28:21
众里寻他千百度,蓦然回首在这里!
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表