宁静的API服务


226

我正在寻找一种可以用来调用基于Web的REST API的服务。

基本上,我想在应用程序init上启动服务,然后我希望能够要求该服务请求url并返回结果。同时,我希望能够显示进度窗口或类似的窗口。

我已经创建了一个当前使用IDL的服务,我读过某个地方,您实际上只需要此即可进行跨应用程序通信,因此认为这些需求已经剥离,但是不确定如果没有它,如何进行回调。另外,当我按下该post(Config.getURL("login"), values)应用程序时,该应用程序似乎暂停了一段时间(似乎很奇怪-认为服务背后的想法是它在不同的线程上运行!)

目前,我有一个内部带有post和get http方法的服务,几个AIDL文件(用于双向通信),一个处理服务的启动,停止,绑定等的ServiceManager,并且正在动态创建带有特定代码的Handler。根据需要进行回调。

我不想让任何人给我一个完整的代码库,但是有些指针将不胜感激。

代码(大部分)完整:

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) {...}
};

几个AIDL文件:

package com.something.android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

package com.something.android
import com.something.android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

和服务经理:

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;
    }
}

服务初始化和绑定:

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

服务功能调用:

// 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);
        }

    }

};

5
这对于学习Android REST客户端实施的人们可能非常有帮助。Dobjanschi的演示文稿转录为PDF:drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…–
Kay Zed

由于许多人推荐了Virgil Dobjanschi的演示文稿,并且现在到IO 2010的链接已断开,因此,这里是YT视频的直接链接: youtube.com/watch?
v=xHXn3Kg2IQE

Answers:


283

如果您的服务将成为您应用程序的一部分,那么您会使它变得比所需的更加复杂。由于您有一个简单的用例,可从RESTful Web服务获取数据,因此应查看ResultReceiverIntentService

当您要执行某些操作时,此Service + ResultReceiver模式通过使用startService()启动或绑定到该服务来工作。您可以指定要执行的操作,并通过Intent中的其他函数传入ResultReceiver(活动)。

在服务中,您实现onHandleIntent来执行Intent中指定的操作。操作完成后,您可以使用传入的ResultReceiver 消息发送回Activity,这时将调用onReceiveResult

因此,例如,您想从Web服务中提取一些数据。

  1. 您创建意图并调用startService。
  2. 服务中的操作开始,并向活动发送一条消息,说明活动已开始
  3. 该活动处理消息并显示进度。
  4. 该服务完成操作并将一些数据发送回您的活动。
  5. 您的活动处理数据并放入列表视图
  6. 该服务会向您发送一条消息,指出已完成,并且会杀死自己。
  7. 该活动获取完成消息并隐藏进度对话框。

我知道您提到过您不想要代码库,但开放源Google I / O 2010应用程序以我所描述的方式使用了一项服务。

更新以添加示例代码:

活动。

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;
    }
}

服务:

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扩展-编辑以实现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);
        }
    }
}

1
非常好,谢谢!我最终浏览了Google iosched应​​用程序,哇...这很复杂,但是我有一些正在运行的东西,现在我只需要弄清楚它为什么起作用!但是,是的,这是我工作的基本模式。非常感谢你。
马丁·

29
答案的一个小补充:当您执行mReceiver.setReceiver(null); 在onPause方法中,您应该执行mReceiver.setReceiver(this); 在onResume方法中。否则,如果您的活动没有被重新创建而恢复,则可能不会收到事件
Vincent Mimoun-Prat 2012年

7
文档不是说您不必调用stopSelf,因为IntentService会为您这样做吗?
Mikael Ohlson

2
@MikaelOhlson正确的,你应该stopSelf,如果你的子类IntentService,因为如果你这样做,你就会失去任何未决请求到相同IntentService
quietmint 2012年

1
IntentService会在任务完成时杀死自己,所以this.stopSelf()没有必要。
Euporie 2014年

17

对我来说,开发Android REST客户端应用程序是一个很棒的资源。演讲者没有显示任何代码,他只是研究了将Android上坚固的Rest Api组合在一起的设计注意事项和技术。如果您不是一个播客,那么我建议您至少给一位听众,但就我个人而言,到目前为止,我已经听过4到5次了,我可能会再听一次。

开发Android REST客户端应用程序
作者:Virgil Dobjanschi
描述:

本次会议将提出在Android平台上开发RESTful应用程序的架构注意事项。它着重于Android平台特有的设计模式,平台集成和性能问题。

在我的api的第一个版本中,我确实没有考虑太多因素,因此我不得不对其进行重构


4
+1其中包含您一开始就不会想到的各种注意事项。
Thomas Ahle

是的,我对开发Rest客户的第一次尝试几乎就是他对完全不该做的事情的描述(非常感谢,在我看到这个之前,我意识到很多事情是错误的)。我对此有点感激。
Terrance

我已经看过这个视频不止一次了,我正在实现第二种模式。我的问题是,我需要在复杂的数据库模型中使用事务来更新来自服务器的新数据中的本地数据,而ContentProvider接口没有提供执行此操作的方法。你有什么建议吗,泰伦斯?
弗拉维奥里亚

2
注意:Dobjanschi对HttpClient的评论不再成立。参见stackoverflow.com/a/15524143/939250
Donal Lafferty

是的,现在首选使用HttpURLConnection。此外,随着Android 6.0的发布,对Apache HTTP Client的支持已被正式删除
RonR 2015年

16

另外,当我点击post(Config.getURL(“ login”),values)时,该应用似乎暂停了一段时间(似乎很奇怪-认为服务背后的想法是它在不同的线程上运行!)

无需您自己创建线程,默认情况下,本地服务在UI线程中运行。





4

可以说我要在按钮的onItemClicked()事件上启动服务。在这种情况下,Receiver机制不起作用,因为:
-a)我从onItemClicked()将Receiver传递给了服务(如Intent extra)
b)Activity移到了后台。在onPause()中,我将ResultReceiver中的接收者引用设置为null,以避免泄漏Activity。
c)活动被破坏。
d)重新创建活动。但是,这时服务将无法对活动进行回调,因为该接收者引用已丢失。
在这种情况下,有限广播或PendingIntent的机制似乎更有用-请参阅从服务通知活动


1
你说的话有问题。也就是说,当活动移至后台时,它不会被破坏...因此接收方仍然存在,活动上下文也是如此。
DArkO 2011年

@DArkO当活动被暂停或停止时,在内存不足的情况下,Android系统可以将其杀死。请参阅活动生命周期
jk7

4

请注意,某种程度上缺乏Robby Pond的解决方案:由于IntentService一次只能处理一个intent,因此您一次只能允许一个api调用。通常,您想执行并行api调用。如果要执行此操作,则必须扩展Service而不是IntentService并创建自己的线程。


1
您仍然可以通过将webservice api调用委托给执行程序线程服务来对IntentService进行多次调用,该执行程序作为IntentService派生类的成员变量存在
Viren

2

另外,当我点击post(Config.getURL(“ login”),values)时,该应用似乎暂停了一段时间(似乎很奇怪-认为服务背后的想法是它在不同的线程上运行!)

在这种情况下,最好使用asynctask,它在另一个线程上运行,并在完成时将结果返回给ui线程。


2

这里有另一种方法,它基本上可以帮助您忘记请求的整个管理。它基于异步队列方法和基于可调用/回调的响应。主要优点是通过使用此方法,您可以使整个过程(请求,获取和解析响应,对数据库的sabe)对您完全透明。获得响应代码后,工作已经完成。之后,您只需要调用您的数据库就可以了。它也有助于解决活动不活跃时发生的问题。这里将发生的事情是,您将所有数据保存在本地数据库中,但是响应不会被您的活动处理,这是理想的方法。

我在这里写了一个通用方法 http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

我将在即将发布的帖子中放置特定的示例代码。希望有帮助,请随时与我联系以分享方法并解决潜在的疑问或问题。


无效链接。域已过期。
jk7

1

Robby提供了一个很好的答案,尽管我可以看到您仍在寻找更多信息。我实现了REST API,但调用错误的方式却很容易。直到观看此Google I / O视频,我才知道我出了错。这并不像将AsyncTask与HttpUrlConnection get / put调用放在一起那样简单。


无效链接。这是更新的版本
-Google
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.