Android - 异步网络呼叫 - 响应相互依赖

Shu*_*ral 6 java android asynchronous sequential android-volley

我今天在开发Android应用程序时遇到了这种情况,我需要根据来自2个不同API的响应来呈现图形.我正在使用Volley而我所做的是我进行了顺序网络呼叫,即我发出了第一个请求,并且在该onResponse请求的方法中我发出了第二个请求.然后我在onResponse第二个请求的方法中渲染视图(图形).

现在我想优化这种情况.我想知道一种方法,我可以异步地进行这两个网络调用,我只在收到来自两个API的响应后呈现视图.所以,我说有3种模块化方法,即 -

  1. getDataFromServer1(从一个服务器获取数据的网络调用)
  2. getDataFromServer2(从另一台服务器获取数据的网络调用)
  3. loadView (根据从2个网络呼叫收到的数据渲染图表)

我该怎么办呢?有人可以对它嗤之以鼻吗?

Tom*_*iak 6

版本1 - 使用外部库

这是一个完美的例子,其中RxAndroid方便(或更一般 - 任何支持事件驱动编程的框架).


假设我们有以下域类允许我们从Web服务中获取一些数据:

存储库类:

public class Repository {
    protected String name;

    public Repository(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
Run Code Online (Sandbox Code Playgroud)

服务界面:

public interface GitService {
    List<Repository> fetchRepositories();
}
Run Code Online (Sandbox Code Playgroud)

第一项服务实施:

public class BitbucketService implements GitService {
    @Override
    public List<Repository> fetchRepositories() {
        // Time consuming / IO consuming task.
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // Swallow exception.
        }

        List<Repository> result = new ArrayList<>();
        result.add(new Repository("http://some-fancy-repository.com/"));
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

第二次服务实施:

public class GithubService implements GitService {
    @Override
    public List<Repository> fetchRepositories() {
        // Time consuming / IO consuming task.
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // Swallow exception.
        }

        List<Repository> result = new ArrayList<>();
        result.add(new Repository("http://some-fancier-repository.com/"));
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

有了上面的内容,我们可以轻松地创建一个可观察的(一个看起来已经发生过的事情的对象)来检查我们是否已成功从两个服务下载数据.此责任有以下方法:

public Observable<List<Repository>> fetchRepositories() {
    // This is not the best place to instantiate services.
    GitService github = new GithubService();
    GitService bitbucket = new BitbucketService();

    return Observable.zip(
        Observable.create(subscriber -> {
            subscriber.onNext(github.fetchRepositories());
        }),
        Observable.create(subscriber -> {
            subscriber.onNext(bitbucket.fetchRepositories());
        }),
        (List<Repository> response1, List<Repository> response2) -> {
            List<Repository> result = new ArrayList<>();
            result.addAll(response1);
            result.addAll(response2);
            return result;
        }
    );
}
Run Code Online (Sandbox Code Playgroud)

唯一要做的就是在某处执行任务(示例在onCreate方法中):

@Override
protected void onCreate(Bundle savedInstanceState) {
    (...)

    AndroidObservable
        .bindActivity(this, fetchRepositories())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(repos -> showRepositories(repos), error -> showErrors());
}
Run Code Online (Sandbox Code Playgroud)

上面的代码是魔术发生的地方.这是:

  • 定义了任务的上下文,
  • 定义了创建异步线程的必要性
  • 定义在硬件作业结束时应该在UI线程中处理结果,
  • 定义了调用哪些方法来处理结果和错误.

在上面,subscribe我们正在传递lambda调用以向用户显示结果/错误:

private void showRepositories(List<Repository> repositories) {
    // Show repositories in your fragment.
}

private void showErrors() {
    // Pops up some contextual information / help.
}
Run Code Online (Sandbox Code Playgroud)

由于Android目前使用SDK 1.7,因此需要使用允许我们在符合1.7的代码中使用lambdas的库.就个人而言,我在这个案例中使用了retrolambda.

如果你不喜欢lambdas - 你总是有可能在必要时实现匿名类.

这样我们就可以避免编写很多Android的样板代码.


版本2 - 没有外部库

如果您不想使用外部库,则可以使用Executor附带的AsyncTasks实现类似的功能.


我们将重用域类上面描述:Repository,GitService,GithubServiceBitbucketService.

由于我们想要查看完成了多少任务,让我们在我们的活动中引入一些计数器:

private AtomicInteger counter = new AtomicInteger(2);
Run Code Online (Sandbox Code Playgroud)

我们将在异步任务中共享此对象.

接下来,我们必须自己实现一个任务,例如:

public class FetchRepositories extends AsyncTask<Void, Void, List<Repository>> {
    private AtomicInteger counter;
    private GitService service;

    public FetchRepositories(AtomicInteger counter, GitService service) {
        this.counter = counter;
        this.service = service;
    }

    @Override
    protected List<Repository> doInBackground(Void... params) {
        return service.fetchRepositories();
    }

    @Override
    protected void onPostExecute(List<Repository> repositories) {
        super.onPostExecute(repositories);

        int tasksLeft = this.counter.decrementAndGet();
        if(tasksLeft <= 0) {
            Intent intent = new Intent();
            intent.setAction(TASKS_FINISHED_ACTION);
            sendBroadcast(intent);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是发生了什么:

  • 在构造函数中,我们注入了用于获取数据的共享计数器和服务,
  • doInBackground方法中,我们已将控制委托给我们的专用服务,
  • onPostExecute方法中我们测试了所有预期的任务是否已经完成,
  • 经过所有艰苦的工作 - 已经发送了一个广播活动.

接下来,我们必须接收潜在的广播,通知我们已完成工作.为此,我们实施了广播接收器:

public class FetchBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("onReceive", "All tasks has been finished.");
        // No need to test 
        // if intent.getAction().equals(TASKS_FINISHED_ACTION) {}
        // as we used filter.
    }
}
Run Code Online (Sandbox Code Playgroud)

而不是记录消息 - 您必须更新您的视图.

提到TASKS_FINISHED_ACTION过滤器的常量名称:

 private static final String TASKS_FINISHED_ACTION = "some.intent.filter.TASKS_FINISHED";
Run Code Online (Sandbox Code Playgroud)

请记住初始化并注册接收器和过滤器 - 您的活动和清单.

活动:

private BroadcastReceiver receiver = new FetchBroadcastReceiver();

@Override
protected void onResume() {
    super.onResume();

    IntentFilter filter = new IntentFilter();
    filter.addAction(TASKS_FINISHED_ACTION);

    registerReceiver(receiver, filter);
}
Run Code Online (Sandbox Code Playgroud)

清单(内部应用程序标记):

<receiver android:name=".TestActivity$FetchBroadcastReceiver"/>
Run Code Online (Sandbox Code Playgroud)

我把接收器类public放在里面TestActivity所以看起来很奇怪.

在清单中,您还必须注册您的操作(内部活动意图过滤器):

<action android:name="some.intent.filter.TASKS_FINISHED"/>
Run Code Online (Sandbox Code Playgroud)

请记住在onPause()方法中取消注册您的接收器.


准备好活动后,您可以在某处执行任务(例如,onCreate在第一个示例中的方法中):

if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
    new FetchRepositories(counter, new GithubService())
        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    new FetchRepositories(counter, new BitbucketService())
        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
    // Below Honeycomb there was no parallel tasks.
    new FetchRepositories(counter, new GithubService()).execute();
    new FetchRepositories(counter, new BitbucketService()).execute();
}
Run Code Online (Sandbox Code Playgroud)

您可以注意到,并行任务仅在Honeycomb及更高版本上运行.在此版本的Android线程池之前最多可以容纳1个任务.


至少我们使用了一些依赖注入和策略模式.:)


Ahm*_*aza 4

@tommus 解决方案是最好的方法。


如果您想采用简单或更少的代码方法,您可以使用布尔标志来确保两者都被执行并根据条件前进。

声明一个将用作标志的易失性布尔变量。

private volatile boolean flag = false;
Run Code Online (Sandbox Code Playgroud)

标志在开始时将为假。现在,调用这两个 Web 服务。任何执行的服务都会将该标志变为 TRUE。

getDataFromServer1();
function void onCompleteServer1() {
    if(flag) {
       loadViews();
    } else {
       flag = true;
    }
}


getDataFromServer2();
onCompleteServer2Request() {
    if(flag) {
       loadViews();
    } else {
       flag = true;
    }
}
Run Code Online (Sandbox Code Playgroud)