Android如何像在iOS中一样将异步任务分组在一起

Posted

技术标签:

【中文标题】Android如何像在iOS中一样将异步任务分组在一起【英文标题】:Android how to group async tasks together like in iOS 【发布时间】:2016-04-24 14:51:06 【问题描述】:

我在 ios 应用中有一个函数,它使用dispatch_group 对多个休息请求进行分组:

static func fetchCommentsAndTheirReplies(articleId: String, failure: ((NSError)->Void)?, success: (comments: [[String: AnyObject]], replies: [[[String: AnyObject]]], userIds: Set<String>)->Void) 
    var retComments = [[String: AnyObject]]()
    var retReplies = [[[String: AnyObject]]]()
    var retUserIds = Set<String>()

    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
    Alamofire.request(.GET, API.baseUrl + API.article.listCreateComment, parameters: [API.article.articleId: articleId]).responseJSON 
        response in

        dispatch_async(queue) 

            guard let comments = response.result.value as? [[String: AnyObject]] else 
                failure?(Helper.error())
                return
            
            print(comments)
            retComments = comments

            let group = dispatch_group_create()

            for (commentIndex, comment) in comments.enumerate() 
                guard let id = comment["_id"] as? String else continue

                let relevantUserIds = helperParseRelaventUserIdsFromEntity(comment)
                for userId in relevantUserIds 
                    retUserIds.insert(userId)
                

                retReplies.append([[String: AnyObject]]())

                dispatch_group_enter(group)
                Alamofire.request(.GET, API.baseUrl + API.article.listCreateReply, parameters: [API.article.commentId: id]).responseJSON 
                    response in

                    dispatch_async(queue) 
                        if let replies = response.result.value as? [[String: AnyObject]] 
                            for (_, reply) in replies.enumerate() 

                                let relevantUserIds = helperParseRelaventUserIdsFromEntity(reply)
                                for userId in relevantUserIds 
                                    retUserIds.insert(userId)
                                
                            
                            retReplies[commentIndex] = replies
                        
                        dispatch_group_leave(group)
                    

                
            

            dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
            success(comments: retComments, replies: retReplies, userIds: retUserIds)

        

    

从我的代码中可以看出,我在同一个article 下获取所有comments,然后在每个comment 下获取对应的replies。完成所有请求后,我调用我的success 回调。这可以使用 GCD 的dispatch_group 来实现。

现在我正在将相同的功能迁移到 android

public static void fetchCommentsAndTheirReplies(Context context, String articleId, final StringBuffer outErrorMessage, final Runnable failure, final ArrayList<JSONObject> outComments, final ArrayList<ArrayList<JSONObject>> outReplies, final HashSet<String> outUserIds, final Runnable success) 
    final RequestQueue queue = Volley.newRequestQueue(context);
    HashMap<String, String> commentParams = new HashMap<>();
    commentParams.put(API.article.articleId, articleId);
    JsonArrayRequest commentRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateComment, new JSONObject(commentParams), new Response.Listener<JSONArray>() 
        @Override
        public void onResponse(JSONArray response) 
            try 
                for (int i = 0; i < response.length(); i++) 
                    JSONObject comment = response.getJSONObject(i);
                    outComments.add(comment);

                    outUserIds.addAll(helperParseRelaventUserIdsFromEntity(comment));
                    outReplies.add(new ArrayList<JSONObject>());

                    //TODO: DISPATCH_GROUP?
                    String id = comment.getString("_id");
                    HashMap<String, String> replyParams = new HashMap<>();
                    replyParams.put(API.article.commentId, id);
                    final int finalI = i;
                    JsonArrayRequest replyRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateReply, new JSONObject(replyParams), new Response.Listener<JSONArray>() 
                        @Override
                        public void onResponse(JSONArray response) 
                            try 
                                for (int j = 0; j < response.length(); j++) 
                                    JSONObject reply = response.getJSONObject(j);
                                    outUserIds.addAll(helperParseRelaventUserIdsFromEntity(reply));
                                    outReplies.get(finalI).add(reply);
                                
                             catch (JSONException ex) 
                        
                    , new Response.ErrorListener() 
                        @Override
                        public void onErrorResponse(VolleyError error) 
                    );
                    queue.add(replyRequest);
                
                success.run();

             catch (JSONException ex) 
        
    , new Response.ErrorListener() 
        @Override
        public void onErrorResponse(VolleyError error) 
            outErrorMessage.append(error.getMessage());
            failure.run();
        
    );
    queue.add(commentRequest);

请注意,我使用success 是在我获得所有comments 之后和获得所有replies 之前执行的。

那么我怎样才能对它们进行分组并延迟响应呢?

我正在研究毛茸茸的实现,比如

taskCount++;
if (taskCount == totalCount) 
    success.run();
 

在回复块中,但是看起来很乏味。

【问题讨论】:

你看过RxJava吗?这是它的预期用例。异步操作以及对结果和中间体的操作。 为什么不使用异步任务? developer.android.com/reference/android/os/AsyncTask.html 抱歉,您能否解释一下,您的 iOS 代码中发生了什么?为什么你首先要使用dispatch_group?无论您是发送还是接收某些东西,只要只涉及单个服务器,不按顺序执行操作就没有多大帮助。而且移动连接肯定不会有任何好处,这通常会将并发网络请求的数量限制为 1。您最好在阻塞模式下,在单个后台请求中执行上述所有操作。 【参考方案1】:

您可以简单地使用我为模仿 iOS 行为而制作的此类。调用 enter() 和 leave() 的方式与在 iOS 中使用 dispatch_group_enter 和 dispatch_group_leave 的方式相同,并在要分组的请求之后调用 notify(),就像 dispatch_group_notify 一样。它也像 iOS 使用块一样使用 runnable:

public class DispatchGroup 

    private int count = 0;
    private Runnable runnable;

    public DispatchGroup()
    
        super();
        count = 0;
    

    public synchronized void enter()
        count++;
    

    public synchronized void leave()
        count--;
        notifyGroup();
    

    public void notify(Runnable r) 
        runnable = r;
        notifyGroup();
    

    private void notifyGroup()
        if (count <=0 && runnable!=null) 
             runnable.run();
        
    

希望对你有帮助;)

【讨论】:

我建议在 notify 方法中跳过 notifyGroup。如果您有例如嵌套的 Api 调用,您在另一个回调上调用更多 Api 调用并且您需要等待它们完成,这将导致问题。 @Placeable 跳过不是一个好主意。如果没有元素进入组怎么办?您的执行块将永远不会被调用。 iOS 的行为与上面的代码类似。如果没有调用 enter(),则立即调用执行块。【参考方案2】:

这是 Damien Praca 答案的 Kotlin 版本。这将允许您像这样使用 Kotlin lambda。

val dispatchGroup = DispatchGroup()
dispatchGroup.enter()
// Some long running task
dispatchGroup.leave()

dispatchGroup.notify 
// Some code to run after all dispatch groups complete


class DispatchGroup 
    private var count = 0
    private var runnable: (() -> Unit)? = null

    init 
        count = 0
    

    @Synchronized
    fun enter() 
        count++
    

    @Synchronized
    fun leave() 
        count--
        notifyGroup()
    

    fun notify(r: () -> Unit) 
        runnable = r
        notifyGroup()
    

    private fun notifyGroup() 
        if (count <= 0 && runnable != null) 
            runnable!!()
        
    

【讨论】:

【参考方案3】:

恕我直言,您的“毛茸茸”实现根本不是“毛茸茸”的。

public void onResponse(JSONArray response) 
                try 
                    final int[] taskFinished = 0;
                    final int taskTotal = response.length();
                    for (int i = 0; i < response.length(); i++) 
                        JSONObject comment = response.getJSONObject(i);
                        outComments.add(comment);

                        outUserIds.addAll(helperParseRelaventUserIdsFromEntity(comment));
                        outReplies.add(new ArrayList<JSONObject>());

                        //TODO: DISPATCH_GROUP?
                        String id = comment.getString("_id");
                        HashMap<String, String> replyParams = new HashMap<>();
                        replyParams.put(API.article.commentId, id);
                        final int finalI = i;
                        JsonArrayRequest replyRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateReply, new JSONObject(replyParams), new Response.Listener<JSONArray>() 
                            @Override
                            public void onResponse(JSONArray response) 
                                taskFinished[0]++;
                                try 
                                    for (int j = 0; j < response.length(); j++) 
                                        JSONObject reply = response.getJSONObject(j);
                                        outUserIds.addAll(helperParseRelaventUserIdsFromEntity(reply));
                                        outReplies.get(finalI).add(reply);
                                    
                                 catch (JSONException ex) 
                                if (taskFinished[0] == taskTotal) 
                                    success.run();
                                
                            
                        , new Response.ErrorListener() 
                            @Override
                            public void onErrorResponse(VolleyError error) 
                                taskFinished[0]++;
                                if (taskFinished[0] == taskTotal) 
                                    success.run();
                                
                            
                        );
                        queue.add(replyRequest);
                    


                 catch (JSONException ex) 
            

【讨论】:

是的,我知道如何实现这一点,但它仍然不如 iOS 的 dispatch_group【参考方案4】:

在纯 Java 或 Android 中没有 dispatch_group 的直接类似物。如果您准备在其中投入一些额外的时间,我可以推荐一些相当复杂的技术来产生一个非常干净和优雅的解决方案。不幸的是,它不会是一两行代码。

    Use RxJava with parallelization。 RxJava 提供了一种干净的方式来分派多个任务,但默认情况下它是按顺序工作的。请参阅这篇文章以使其同时执行任务。

    虽然这不完全是预期的用例,但您可以尝试使用 ForkJoinPool 来执行您的任务组并在之后收到一个结果。

【讨论】:

【参考方案5】:

您可以使用Threads 和Thread.join()Handlers 作为选项。

引用自:https://docs.oracle.com/javase/tutorial/essential/concurrency/join.html

join方法允许一个线程等待完成 其他。如果 t 是其线程当前正在执行的 Thread 对象,

t.join();导致当前线程暂停执行直到 t's 线程终止。连接的重载允许程序员指定一个 等待期。但是,与 sleep 一样,join 取决于操作系统 时间,所以你不应该假设加入将完全等待 只要你指定。

像 sleep 一样,join 通过退出来响应中断 中断异常。

编辑: 您还应该查看我的event dispatcher 要点。你可能会喜欢。

【讨论】:

【参考方案6】:

尝试优先作业队列:https://github.com/yigit/android-priority-jobqueue

Priority Job Queue 是 Job Queue 的具体实现 为 Android 编写,用于轻松安排在 背景,改善用户体验和应用程序稳定性。

(...)

如有必要,您可以对作业进行分组以确保其连续执行。为了 例如,假设您有一个消息传递客户端并且您的用户发送了一堆 当他们的手机没有网络覆盖时的消息。创建时 这些 SendMessageToNetwork 作业,您可以按对话对它们进行分组 ID。通过这种方法,同一对话中的消息将发送 按它们入队的顺序,而不同的消息之间的消息 对话仍然是并行发送的。这让你毫不费力 最大化网络利用率并确保数据完整性。

【讨论】:

【参考方案7】:

我使用 java.util.concurrent.CountDownLatch 来实现目标。 首先我为每个任务做了一个界面。

interface GroupTask 
    void onProcessing(final CountDownLatch latch);

然后我创建一个类来处理分组任务。

interface MyDisptchGroupObserver 
    void onAllGroupTaskFinish();

class MyDisptchGroup 
    private static final int MSG_ALLTASKCOMPLETED = 300;
    private CountDownLatch latch;
    private MyDisptchGroupObserver observer;

    private MsgHandler msgHandler;
    private class MsgHandler extends Handler 
        MsgHandler(Looper looper) 
            super(looper);
        
        @Override
        public void handleMessage(Message msg) 
            switch(msg.what) 
                case MSG_ALLTASKCOMPLETED:
                    observer.onAllGroupTaskFinish();
                    break;
                default:
                    break;
            
        
    

    MyDisptchGroup(List<GroupTask> tasks, MyDisptchGroupObserver obj) 
        latch = new CountDownLatch(tasks.size());
        observer = obj;
        msgHandler = new MsgHandler(getActivity().getMainLooper())

        new Thread( new Runnable() 
            @Override
            public void run() 
                try 
                    latch.await();
                    Log.d(TAG, "========= All Tasks Completed =========");
                    msgHandler.sendEmptyMessage(MSG_ALLTASKCOMPLETED);
                 catch() 
                    e.printStackTrace();
                
            
        ).start();

        for( GroupTask task : tasks ) 
            task.onProcessing(latch);
        
    

当然,我有不止一个任务实现,如下所示。 任务1

class Task1 implements GroupTask 
    @Override
    public void onProcessing(final CountDownLatch latch) 
        new Thread( new Runnable() 
            @Override
            public void run() 
                // Just implement my task1 stuff here


                // The end of the Task1 remember to countDown
                latch.countDown();
            
        ).start();
    

任务2

class Task2 implements GroupTask 
    @Override
    public void onProcessing(final CountDownLatch latch) 
        new Thread( new Runnable() 
            @Override
            public void run() 
                // Just implement my task2 stuff here


                // The end of the Task2 remember to countDown
                latch.countDown();
            
        ).start();
    

现在一切准备就绪。

ArrayList<GroupTask> allTasks = new ArrayList<GroupTask>();
allTasks.add(new Task1());
allTasks.add(new Task2());
new MyDisptchGroup(allTasks, this);

【讨论】:

以上是关于Android如何像在iOS中一样将异步任务分组在一起的主要内容,如果未能解决你的问题,请参考以下文章

Swift3 如何像在 android 中一样在 iOS 中实现底部工作表

如何像在 iOS 中为 iMessage 一样为 Android 创建贴纸包?

Android:像在 iOS 中一样逐步获取 HTTP 响应

如何使回车键将光标移动到 Android Studio 中的行尾,就像在 Eclipse 中一样

像在 GOOGLE NEWS 中一样将相似的新闻内容分组在一起

我们可以像在 android asyncTask 中一样显示加载状态,直到我们在 swift 中使用 HTTP Post 请求得到响应