Android - 异步网络调用 - 响应相互依赖
Android - Asynchronous Network Calls - Response dependent on each other
我今天在开发 Android 应用程序时遇到了这种情况,我需要根据来自 2 个不同 API 的响应呈现图形。我正在使用 Volley,我所做的是我进行了顺序网络调用,即我发出了第一个请求,并在该请求的 onResponse
方法中发出了第二个请求。然后我在第二个请求的 onResponse
方法中呈现视图(图形)。
现在我想优化一下这个情况。我想知道一种可以异步进行这两个网络调用的方法,我只在收到来自两个 API 的响应后才呈现视图。
所以,假设我有 3 种模块化方法,即 -
- getDataFromServer1(从一台服务器获取数据的网络调用)
- getDataFromServer2(从另一台服务器获取数据的网络调用)
loadView
(根据从 2 个网络调用接收到的数据渲染图表)
我该怎么做?有人可以解释一下吗?
如果两个请求的数据格式相同,并且存放在同一个容器中。
为什么不简单地检查容器的大小。
public class MainActivity extends AppCompatActivity {
private List<Data> myList = new ArrayList<>();
private boolean error; // if one request got error, another one need to konw,
//use to handle response size 0 or other error
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GsonRequest request1 = new GsonRequest(params... , this.mClazz, null, new Response.Listener() {
@Override
public void onResponse(Object response) {
boolean updateUI = false;
if(myList.size()>0){ //or > the init size
updateUI = true;
}
myList.addAll(response);
if(updateUI){
notifydatasetchange();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
GsonRequest request2 = new GsonRequest(params... , this.mClazz, null, new Response.Listener() {
@Override
public void onResponse(Object response) {
boolean updateUI = false;
if(myList.size()>0){ //or > the init size
updateUI = true;
}
myList.addAll(response);
if(updateUI){
notifydatasetchange();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
}
}
版本 1 - 带有外部库
这是一个完美的示例,其中 RxAndroid 派上用场(或更一般的 - 任何支持事件驱动编程的框架)。
假设我们有以下域 classes 允许我们从网络服务中获取一些数据:
存储库class:
public class Repository {
protected String name;
public Repository(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
服务接口:
public interface GitService {
List<Repository> fetchRepositories();
}
第一个服务实现:
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;
}
}
第二次服务实施:
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;
}
}
有了上面的内容,我们可以轻松地创建一个可观察对象(一个查看是否发生了某些事情的对象)来检查我们是否已成功从两个服务下载数据。这个职责有以下方法:
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;
}
);
}
唯一要做的就是在某处执行任务(onCreate
方法中的示例):
@Override
protected void onCreate(Bundle savedInstanceState) {
(...)
AndroidObservable
.bindActivity(this, fetchRepositories())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(repos -> showRepositories(repos), error -> showErrors());
}
上面的代码是一个神奇的地方。这里是:
- 定义了任务的上下文,
- 定义了创建异步线程的必要性,
- 定义当hard job结束时,结果应该在UI线程中处理,
- 定义调用哪些方法来处理结果和错误。
以上,在 subscribe
中,我们传递 lambda 调用以向用户显示 results/errors:
private void showRepositories(List<Repository> repositories) {
// Show repositories in your fragment.
}
private void showErrors() {
// Pops up some contextual information / help.
}
由于 Android 目前使用 SDK 1.7,因此需要使用允许我们在 1.7 兼容代码中使用 lambda 的库。就个人而言,我在这种情况下使用 retrolambda。
如果您不喜欢 lambda - 您总是可以在必要时实施匿名 classes。
这样我们就可以避免写很多Android的样板代码。
版本 2 - 没有外部库
如果您不想使用外部库,您可以使用 AsyncTasks accompanied by Executor 实现类似的效果。
我们将重用上述域 classes:Repository
、GitService
、GithubService
和 BitbucketService
。
因为我们想轮询已经完成了多少任务,让我们在 activity 中引入某种计数器:
private AtomicInteger counter = new AtomicInteger(2);
我们将在我们的异步任务中共享这个对象。
接下来,我们要自己实现一个任务,例如这样:
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);
}
}
}
这是发生了什么:
- 在构造函数中,我们注入了用于获取数据的共享计数器和服务,
- 在
doInBackground
方法中,我们已将控制权委托给我们的专用服务,
- 我们在
onPostExecute
方法中测试了所有预期任务是否已完成,
- 完成所有艰巨的工作 - 已向 activity 发送广播。
接下来我们必须接收通知我们工作已经完成的潜在广播。对于那种情况,我们实现了广播接收器:
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.
}
}
您必须更新您的视图,而不是记录消息。
提到的常量 TASKS_FINISHED_ACTION
为您的过滤器命名:
private static final String TASKS_FINISHED_ACTION = "some.intent.filter.TASKS_FINISHED";
请记住在您的 activity 和清单中初始化和注册接收器和过滤器。
Activity:
private BroadcastReceiver receiver = new FetchBroadcastReceiver();
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(TASKS_FINISHED_ACTION);
registerReceiver(receiver, filter);
}
清单(在应用程序标签内):
<receiver android:name=".TestActivity$FetchBroadcastReceiver"/>
我把接收器 class 作为 public
在 TestActivity
中,所以它看起来很奇怪。
在清单中,您还必须注册您的操作(在 activity 意图过滤器中):
<action android:name="some.intent.filter.TASKS_FINISHED"/>
记得在 onPause()
方法中取消注册您的接收器。
准备好 activity 你可以在某个地方执行你的任务(例如在第一个例子中的 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();
}
如您所见,并行任务 运行 仅在 Honeycomb 及更高版本上。
在此版本之前 Android 线程池最多可以容纳 1 个任务。
至少我们使用了一些依赖注入和策略模式。 :)
@tommus 解决方案是最好的方法。
如果您想采用简单或更少代码的方法,您可以使用布尔标志来确保两者都被执行并根据条件继续前进。
声明一个将用作标志的易失性布尔变量。
private volatile boolean flag = false;
标志在开始时为假。现在,调用两个 web 服务。执行的任何服务都会将此标志变为真。
getDataFromServer1();
function void onCompleteServer1() {
if(flag) {
loadViews();
} else {
flag = true;
}
}
getDataFromServer2();
onCompleteServer2Request() {
if(flag) {
loadViews();
} else {
flag = true;
}
}
不完美但我希望这个逻辑对你有用
onDatafromServer1Fetched{
flag1 = true;
}
onDataFromServer2Fetched{
flag2=true;
}
main(){
boolean flag1 = false;
boolean flag2 =false;
getDataFromSerVer1();
getDataFromServer2();
while(!flag1 && !flag2){/**no code required here*/}
loadView();
}
我今天在开发 Android 应用程序时遇到了这种情况,我需要根据来自 2 个不同 API 的响应呈现图形。我正在使用 Volley,我所做的是我进行了顺序网络调用,即我发出了第一个请求,并在该请求的 onResponse
方法中发出了第二个请求。然后我在第二个请求的 onResponse
方法中呈现视图(图形)。
现在我想优化一下这个情况。我想知道一种可以异步进行这两个网络调用的方法,我只在收到来自两个 API 的响应后才呈现视图。 所以,假设我有 3 种模块化方法,即 -
- getDataFromServer1(从一台服务器获取数据的网络调用)
- getDataFromServer2(从另一台服务器获取数据的网络调用)
loadView
(根据从 2 个网络调用接收到的数据渲染图表)
我该怎么做?有人可以解释一下吗?
如果两个请求的数据格式相同,并且存放在同一个容器中。 为什么不简单地检查容器的大小。
public class MainActivity extends AppCompatActivity {
private List<Data> myList = new ArrayList<>();
private boolean error; // if one request got error, another one need to konw,
//use to handle response size 0 or other error
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GsonRequest request1 = new GsonRequest(params... , this.mClazz, null, new Response.Listener() {
@Override
public void onResponse(Object response) {
boolean updateUI = false;
if(myList.size()>0){ //or > the init size
updateUI = true;
}
myList.addAll(response);
if(updateUI){
notifydatasetchange();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
GsonRequest request2 = new GsonRequest(params... , this.mClazz, null, new Response.Listener() {
@Override
public void onResponse(Object response) {
boolean updateUI = false;
if(myList.size()>0){ //or > the init size
updateUI = true;
}
myList.addAll(response);
if(updateUI){
notifydatasetchange();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
}
}
版本 1 - 带有外部库
这是一个完美的示例,其中 RxAndroid 派上用场(或更一般的 - 任何支持事件驱动编程的框架)。
假设我们有以下域 classes 允许我们从网络服务中获取一些数据:
存储库class:
public class Repository {
protected String name;
public Repository(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
服务接口:
public interface GitService {
List<Repository> fetchRepositories();
}
第一个服务实现:
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;
}
}
第二次服务实施:
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;
}
}
有了上面的内容,我们可以轻松地创建一个可观察对象(一个查看是否发生了某些事情的对象)来检查我们是否已成功从两个服务下载数据。这个职责有以下方法:
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;
}
);
}
唯一要做的就是在某处执行任务(onCreate
方法中的示例):
@Override
protected void onCreate(Bundle savedInstanceState) {
(...)
AndroidObservable
.bindActivity(this, fetchRepositories())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(repos -> showRepositories(repos), error -> showErrors());
}
上面的代码是一个神奇的地方。这里是:
- 定义了任务的上下文,
- 定义了创建异步线程的必要性,
- 定义当hard job结束时,结果应该在UI线程中处理,
- 定义调用哪些方法来处理结果和错误。
以上,在 subscribe
中,我们传递 lambda 调用以向用户显示 results/errors:
private void showRepositories(List<Repository> repositories) {
// Show repositories in your fragment.
}
private void showErrors() {
// Pops up some contextual information / help.
}
由于 Android 目前使用 SDK 1.7,因此需要使用允许我们在 1.7 兼容代码中使用 lambda 的库。就个人而言,我在这种情况下使用 retrolambda。
如果您不喜欢 lambda - 您总是可以在必要时实施匿名 classes。
这样我们就可以避免写很多Android的样板代码。
版本 2 - 没有外部库
如果您不想使用外部库,您可以使用 AsyncTasks accompanied by Executor 实现类似的效果。
我们将重用上述域 classes:Repository
、GitService
、GithubService
和 BitbucketService
。
因为我们想轮询已经完成了多少任务,让我们在 activity 中引入某种计数器:
private AtomicInteger counter = new AtomicInteger(2);
我们将在我们的异步任务中共享这个对象。
接下来,我们要自己实现一个任务,例如这样:
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);
}
}
}
这是发生了什么:
- 在构造函数中,我们注入了用于获取数据的共享计数器和服务,
- 在
doInBackground
方法中,我们已将控制权委托给我们的专用服务, - 我们在
onPostExecute
方法中测试了所有预期任务是否已完成, - 完成所有艰巨的工作 - 已向 activity 发送广播。
接下来我们必须接收通知我们工作已经完成的潜在广播。对于那种情况,我们实现了广播接收器:
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.
}
}
您必须更新您的视图,而不是记录消息。
提到的常量 TASKS_FINISHED_ACTION
为您的过滤器命名:
private static final String TASKS_FINISHED_ACTION = "some.intent.filter.TASKS_FINISHED";
请记住在您的 activity 和清单中初始化和注册接收器和过滤器。
Activity:
private BroadcastReceiver receiver = new FetchBroadcastReceiver();
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(TASKS_FINISHED_ACTION);
registerReceiver(receiver, filter);
}
清单(在应用程序标签内):
<receiver android:name=".TestActivity$FetchBroadcastReceiver"/>
我把接收器 class 作为 public
在 TestActivity
中,所以它看起来很奇怪。
在清单中,您还必须注册您的操作(在 activity 意图过滤器中):
<action android:name="some.intent.filter.TASKS_FINISHED"/>
记得在 onPause()
方法中取消注册您的接收器。
准备好 activity 你可以在某个地方执行你的任务(例如在第一个例子中的 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();
}
如您所见,并行任务 运行 仅在 Honeycomb 及更高版本上。 在此版本之前 Android 线程池最多可以容纳 1 个任务。
至少我们使用了一些依赖注入和策略模式。 :)
@tommus 解决方案是最好的方法。
如果您想采用简单或更少代码的方法,您可以使用布尔标志来确保两者都被执行并根据条件继续前进。
声明一个将用作标志的易失性布尔变量。
private volatile boolean flag = false;
标志在开始时为假。现在,调用两个 web 服务。执行的任何服务都会将此标志变为真。
getDataFromServer1();
function void onCompleteServer1() {
if(flag) {
loadViews();
} else {
flag = true;
}
}
getDataFromServer2();
onCompleteServer2Request() {
if(flag) {
loadViews();
} else {
flag = true;
}
}
不完美但我希望这个逻辑对你有用
onDatafromServer1Fetched{
flag1 = true;
}
onDataFromServer2Fetched{
flag2=true;
}
main(){
boolean flag1 = false;
boolean flag2 =false;
getDataFromSerVer1();
getDataFromServer2();
while(!flag1 && !flag2){/**no code required here*/}
loadView();
}