Android 宁静的API服务
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3197335/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Restful API service
提问by Martyn
I'm looking to make a service which I can use to make calls to a web-based REST API.
我正在寻找一项服务,我可以用它来调用基于 Web 的 REST API。
Basically I want to start a service on app init then I want to be able to ask that service to request a url and return the results. In the meantime I want to be able to display a progress window or something similar.
基本上我想在 app init 上启动一个服务,然后我希望能够要求该服务请求一个 url 并返回结果。与此同时,我希望能够显示一个进度窗口或类似的东西。
I've created a service currently which uses IDL, I've read somewhere that you only really need this for cross app communication, so think these needs stripping out but unsure how to do callbacks without it. Also when I hit the post(Config.getURL("login"), values)
the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)
我目前已经创建了一个使用 IDL 的服务,我在某处读到过你只需要它来进行跨应用程序通信,所以认为这些需要剥离但不确定如何在没有它的情况下进行回调。此外,当我点击post(Config.getURL("login"), values)
应用程序时,它似乎暂停了一段时间(似乎很奇怪 - 认为服务背后的想法是它在不同的线程上运行!)
Currently I have a service with post and get http methods inside, a couple of AIDL files (for two way communication), a ServiceManager which deals with starting, stopping, binding etc to the service and I'm dynamically creating a Handler with specific code for the callbacks as needed.
目前我有一个带有 post 和 get http 方法的服务,几个 AIDL 文件(用于双向通信),一个 ServiceManager 处理服务的启动、停止、绑定等,我正在动态创建一个带有特定代码的处理程序根据需要进行回调。
I don't want anyone to give me a complete code base to work on, but some pointers would be greatly appreciated.
我不希望任何人给我一个完整的代码库来处理,但一些指针将不胜感激。
Code in (mostly) full:
代码(大部分)完整:
public class RestfulAPIService extends Service {
final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
return binder;
}
public void onCreate() {
super.onCreate();
}
public void onDestroy() {
super.onDestroy();
mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
public void doLogin(String username, String password) {
Message msg = new Message();
Bundle data = new Bundle();
HashMap<String, String> values = new HashMap<String, String>();
values.put("username", username);
values.put("password", password);
String result = post(Config.getURL("login"), values);
data.putString("response", result);
msg.setData(data);
msg.what = Config.ACTION_LOGIN;
mHandler.sendMessage(msg);
}
public void registerCallback(IRemoteServiceCallback cb) {
if (cb != null)
mCallbacks.register(cb);
}
};
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
// Broadcast to all clients the new value.
final int N = mCallbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
switch (msg.what) {
case Config.ACTION_LOGIN:
mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
break;
default:
super.handleMessage(msg);
return;
}
} catch (RemoteException e) {
}
}
mCallbacks.finishBroadcast();
}
public String post(String url, HashMap<String, String> namePairs) {...}
public String get(String url) {...}
};
A couple of AIDL files:
几个 AIDL 文件:
package com.something.android
oneway interface IRemoteServiceCallback {
void userLogIn(String result);
}
and
和
package com.something.android
import com.something.android.IRemoteServiceCallback;
interface IRestfulService {
void doLogin(in String username, in String password);
void registerCallback(IRemoteServiceCallback cb);
}
and the service manager:
和服务经理:
public class ServiceManager {
final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
public IRestfulService restfulService;
private RestfulServiceConnection conn;
private boolean started = false;
private Context context;
public ServiceManager(Context context) {
this.context = context;
}
public void startService() {
if (started) {
Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
} else {
Intent i = new Intent();
i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
context.startService(i);
started = true;
}
}
public void stopService() {
if (!started) {
Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
} else {
Intent i = new Intent();
i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
context.stopService(i);
started = false;
}
}
public void bindService() {
if (conn == null) {
conn = new RestfulServiceConnection();
Intent i = new Intent();
i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
context.bindService(i, conn, Context.BIND_AUTO_CREATE);
} else {
Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
}
}
protected void destroy() {
releaseService();
}
private void releaseService() {
if (conn != null) {
context.unbindService(conn);
conn = null;
Log.d(LOG_TAG, "unbindService()");
} else {
Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
}
}
class RestfulServiceConnection implements ServiceConnection {
public void onServiceConnected(ComponentName className, IBinder boundService) {
restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
try {
restfulService.registerCallback(mCallback);
} catch (RemoteException e) {}
}
public void onServiceDisconnected(ComponentName className) {
restfulService = null;
}
};
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
public void userLogIn(String result) throws RemoteException {
mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));
}
};
private Handler mHandler;
public void setHandler(Handler handler) {
mHandler = handler;
}
}
Service init and bind:
服务初始化和绑定:
// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);
service function call:
服务函数调用:
// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();
application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);
try {
servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
e.printStackTrace();
}
...later in the same file...
Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case Config.ACTION_LOGIN:
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
try {
...process login results...
}
} catch (JSONException e) {
Log.e("JSON", "There was an error parsing the JSON", e);
}
break;
default:
super.handleMessage(msg);
}
}
};
回答by Robby Pond
If your service is going to be part of you application then you are making it way more complex than it needs to be. Since you have a simple use case of getting some data from a RESTful Web Service, you should look into ResultReceiverand IntentService.
如果您的服务将成为您应用程序的一部分,那么您将使它变得比它需要的更加复杂。由于您有一个从 RESTful Web 服务获取一些数据的简单用例,因此您应该查看ResultReceiver和IntentService。
This Service + ResultReceiver pattern works by starting or binding to the service with startService()when you want to do some action. You can specify the operation to perform and pass in your ResultReceiver (the activity) through the extras in the Intent.
当您想要执行某些操作时,此 Service + ResultReceiver 模式通过使用startService()启动或绑定到服务来工作。您可以指定要执行的操作,并通过 Intent 中的附加项传入您的 ResultReceiver(活动)。
In the service you implement onHandleIntentto do the operation that is specified in the Intent. When the operation is completed you use the passed in ResultReceiver to senda message back to the Activity at which point onReceiveResultwill be called.
在服务中,您实现onHandleIntent以执行 Intent 中指定的操作。操作完成后,您使用传入的 ResultReceiver将消息发送回 Activity,此时onReceiveResult将被调用。
So for example, you want to pull some data from your Web Service.
例如,您想从您的 Web 服务中提取一些数据。
- You create the intent and call startService.
- The operation in the service starts and it sends the activity a message saying it started
- The activity processes the message and shows a progress.
- The service finishes the operation and sends some data back to your activity.
- Your activity processes the data and puts in in a list view
- The service sends you a message saying that it is done, and it kills itself.
- The activity gets the finish message and hides the progress dialog.
- 您创建意图并调用 startService。
- 服务中的操作开始并向活动发送一条消息说它已开始
- 活动处理消息并显示进度。
- 该服务完成操作并将一些数据发送回您的活动。
- 您的活动处理数据并放入列表视图中
- 该服务会向您发送一条消息,说它已完成,然后它会自行终止。
- 活动获取完成消息并隐藏进度对话框。
I know you mentioned you didn't want a code base but the open source Google I/O 2010app uses a service in this way I am describing.
我知道您提到您不想要代码库,但开源Google I/O 2010应用程序使用我描述的这种方式的服务。
Updated to add sample code:
更新以添加示例代码:
The activity.
活动。
public class HomeActivity extends Activity implements MyResultReceiver.Receiver {
public MyResultReceiver mReceiver;
public void onCreate(Bundle savedInstanceState) {
mReceiver = new MyResultReceiver(new Handler());
mReceiver.setReceiver(this);
...
final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
intent.putExtra("receiver", mReceiver);
intent.putExtra("command", "query");
startService(intent);
}
public void onPause() {
mReceiver.setReceiver(null); // clear receiver so no leaks.
}
public void onReceiveResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case RUNNING:
//show progress
break;
case FINISHED:
List results = resultData.getParcelableList("results");
// do something interesting
// hide progress
break;
case ERROR:
// handle the error;
break;
}
}
The Service:
服务:
public class QueryService extends IntentService {
protected void onHandleIntent(Intent intent) {
final ResultReceiver receiver = intent.getParcelableExtra("receiver");
String command = intent.getStringExtra("command");
Bundle b = new Bundle();
if(command.equals("query") {
receiver.send(STATUS_RUNNING, Bundle.EMPTY);
try {
// get some data or something
b.putParcelableArrayList("results", results);
receiver.send(STATUS_FINISHED, b)
} catch(Exception e) {
b.putString(Intent.EXTRA_TEXT, e.toString());
receiver.send(STATUS_ERROR, b);
}
}
}
}
ResultReceiver extension - edited about to implement MyResultReceiver.Receiver
ResultReceiver 扩展 - 编辑以实现 MyResultReceiver.Receiver
public class MyResultReceiver implements ResultReceiver {
private Receiver mReceiver;
public MyResultReceiver(Handler handler) {
super(handler);
}
public void setReceiver(Receiver receiver) {
mReceiver = receiver;
}
public interface Receiver {
public void onReceiveResult(int resultCode, Bundle resultData);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (mReceiver != null) {
mReceiver.onReceiveResult(resultCode, resultData);
}
}
}
回答by Terrance
Developing Android REST client applicationshas been an awesome resource for me. The speaker does not show any code, he just goes over design considerations and techniques in putting together a rock solid Rest Api in android. If your a podcast kinda person or not, I'd recommend giving this one at least one listen but, personally I've listened to it like 4 or five times thus far and I'm probably going to listen to it again.
开发 Android REST 客户端应用程序对我来说是一个很棒的资源。演讲者没有展示任何代码,他只是介绍了在 android 中组装坚如磐石的 Rest Api 的设计考虑和技术。如果你是一个播客类型的人,我建议你至少听一次,但就个人而言,到目前为止我已经听了四到五次,我可能会再听一次。
Developing Android REST client applications
Author: Virgil Dobjanschi
Description:
开发 Android REST 客户端应用程序
作者:Virgil Dobjanschi
描述:
This session will present architectural considerations for developing RESTful applications on the Android platform. It focuses on design patterns, platform integration and performance issues specific to the Android platform.
本次会议将介绍在 Android 平台上开发 RESTful 应用程序的架构注意事项。它侧重于特定于 Android 平台的设计模式、平台集成和性能问题。
And there are so many considerations I really hadn't made in the first version of my api that I've had to refactor
并且在我的 api 的第一个版本中我确实没有考虑到很多因素,以至于我不得不重构
回答by Soumya Simanta
Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)
此外,当我点击 post(Config.getURL("login"), values) 时,应用程序似乎暂停了一段时间(看起来很奇怪 - 认为服务背后的想法是它在不同的线程上运行!)
No you have to createa thread yourself, a Localservice runs in the UI thread by default.
不,您必须自己创建一个线程,默认情况下本地服务在 UI 线程中运行。
回答by panchicore
I know @Martyn does not want full code, but I think this annotation its good for this question:
我知道@Martyn 不想要完整的代码,但我认为这个注释对这个问题有好处:
10 Open Source Android Apps which every Android developer must look into
每个 Android 开发人员都必须研究的 10 个开源 Android 应用程序
Foursquared for Android is open-source, and have an interesting code pattern interacting with the foursquare REST API.
Foursquared for Android 是开源的,并且具有与foursquare REST API 交互的有趣代码模式。
回答by Pete
I would highly recommend the REST client Retrofit.
我强烈推荐 REST 客户端Retrofit。
I have found this well written blog post extremely helpful, it also contains simple example code. The author uses Retrofitto make the network calls and Ottoto implement a data bus pattern:
我发现这篇写得很好的博客文章非常有帮助,它还包含简单的示例代码。作者使用Retrofit进行网络调用,使用Otto实现数据总线模式:
http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html
http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html
回答by StlTenny
Just wanted to point you all in the direction of an standalone class I rolled that incorporates all of the functionality.
只是想向大家指出我推出的包含所有功能的独立类的方向。
http://github.com/StlTenny/RestService
http://github.com/StlTenny/RestService
It executes the request as non-blocking, and returns the results in an easy to implement handler. Even comes with an example implementation.
它以非阻塞方式执行请求,并在易于实现的处理程序中返回结果。甚至带有一个示例实现。
回答by Nikhil_Katre
Lets say I want to start the service on an event - onItemClicked() of a button. The Receiver mechanism would not work in that case because :-
a) I passed the Receiver to the service (as in Intent extra) from onItemClicked()
b) Activity moves to the background. In onPause() I set the receiver reference within the ResultReceiver to null to avoid leaking the Activity.
c) Activity gets destroyed.
d) Activity gets created again. However at this point the Service will not be able to make a callback to the Activity as that receiver reference is lost.
The mechanism of a limited broadcast or a PendingIntent seems to be more usefull in such scenarios- refer to Notify activity from service
假设我想在事件上启动服务 - 按钮的 onItemClicked()。Receiver 机制在这种情况下不起作用,因为:-
a) 我将 Receiver 从 onItemClicked() 传递给服务(如 Intent extra 中)
b) Activity 移动到后台。在 onPause() 中,我将 ResultReceiver 中的接收器引用设置为 null 以避免泄漏 Activity。
c) 活动被破坏。
d) 活动再次被创建。然而,此时服务将无法回调到 Activity,因为该接收器引用丢失。
在这种情况下,有限广播或 PendingIntent 的机制似乎更有用 - 请参阅来自服务的通知活动
回答by TjerkW
Note that the solution from Robby Pond is somehow lacking: in this way you only allow todo one api call at a time since the IntentService only handles one intent at a time. Often you want to perform parallel api calls. If you want todo this you have to extend Service instead of IntentService and create your own thread.
请注意,Robby Pond 的解决方案在某种程度上缺乏:通过这种方式,您一次只允许执行一个 api 调用,因为 IntentService 一次只处理一个意图。通常您想要执行并行 api 调用。如果你想这样做,你必须扩展 Service 而不是 IntentService 并创建你自己的线程。
回答by Aakash
Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)
此外,当我点击 post(Config.getURL("login"), values) 时,应用程序似乎暂停了一段时间(看起来很奇怪 - 认为服务背后的想法是它在不同的线程上运行!)
In this case its better to use asynctask, which runs on a different thread and return result back to the ui thread on completion.
在这种情况下,最好使用 asynctask,它在不同的线程上运行并在完成时将结果返回给 ui 线程。
回答by Jose L Ugia
There is another approach here which basically helps you to forget about the whole management of the requests. It is based on an async queue method and a callable/callback based response. The main advantage is that by using this method you'll be able to make the whole process (request, get and parse response, sabe to db) completely transparent for you. Once you get the response code the work is already done. After that you just need to make a call to your db and you are done. It helps as well with the problematic of what happens when your activity is not active. What will happen here is that you'll have all your data saved in your local database but the response won't be processed by your activity, that's the ideal way.
这里还有另一种方法,它基本上可以帮助您忘记对请求的整个管理。它基于异步队列方法和基于可调用/回调的响应。主要优点是通过使用此方法,您将能够使整个过程(请求、获取和解析响应、sabe 到 db)对您完全透明。一旦你得到响应代码,工作就已经完成了。之后,您只需要调用您的数据库即可完成。它也有助于解决当您的活动不活跃时发生的问题。这里会发生的是,您将所有数据保存在本地数据库中,但您的活动不会处理响应,这是理想的方式。
I wrote about a general approach here http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/
我在这里写了一个通用的方法 http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/
I'll be putting specific sample code in upcoming posts. Hope it helps, feel free to contact me for sharing the approach and solving potential doubts or issues.
我将在即将发布的帖子中放置特定的示例代码。希望它有所帮助,请随时与我联系以分享方法并解决潜在的疑问或问题。