我正在寻找一个服务,我可以使用它来调用基于web的REST API。

我想在app init上启动一个服务然后我想让这个服务请求一个url并返回结果。与此同时,我希望能够显示一个进度窗口或类似的东西。

我目前创建了一个使用IDL的服务,我在某处读到过,你只需要它来跨应用程序通信,所以认为这些需求剥离,但不确定如何做回调没有它。另外,当我点击帖子(Config.getURL(“login”),值)时,应用程序似乎暂停了一段时间(看起来很奇怪-以为服务背后的想法是它在不同的线程上运行!)

目前,我有一个服务与post和获取http方法在里面,一对AIDL文件(双向通信),一个ServiceManager处理启动,停止,绑定等到服务,我动态创建一个处理器与特定的代码回调需要。

我不希望任何人给我一个完整的代码库来工作,但一些指针将非常感激。

代码(大部分)完整:

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

and

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

    }

};

如果您的服务将成为应用程序的一部分,那么您将使它比实际需要的复杂得多。由于您有一个从rest式Web服务获取数据的简单用例,您应该研究ResultReceiver和IntentService。

这个Service + ResultReceiver模式的工作原理是,当你想要执行某些操作时,使用startService()启动或绑定到服务。你可以指定要执行的操作,并通过Intent中的extras传入ResultReceiver (activity)。

在服务中实现onHandleIntent来执行Intent中指定的操作。当操作完成时,使用ResultReceiver传递的消息向Activity发送消息,此时onReceiveResult将被调用。

例如,您希望从Web服务中提取一些数据。

创建意图并调用startService。 服务中的操作启动,并向活动发送消息,称其已启动 活动处理消息并显示进度。 服务完成操作并将一些数据发送回您的活动。 您的活动处理数据并将其放入列表视图中 该服务向你发送消息,说它已经完成了,然后它就自杀了。 活动获取完成消息并隐藏进度对话框。

我知道你提到过你不想要一个代码库,但开源谷歌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。接收机

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

当我点击 帖子(Config.getURL(“登录”), 值)应用程序似乎暂停了一个 虽然(看起来很奇怪——这个想法想 服务的背后是它运行在 不同的线程!)

不,你必须自己创建一个线程,默认情况下本地服务运行在UI线程中。


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


注意,Robby Pond的解决方案在某种程度上是缺乏的:在这种方式下,你只允许一次执行一个api调用,因为IntentService一次只处理一个意图。通常你想要执行并行api调用。如果你想这样做,你必须扩展Service而不是IntentService,并创建你自己的线程。


我知道@Martyn不想要完整的代码,但我认为这个注释对这个问题有好处:

10个开源Android应用,每个Android开发者都必须看看

Android版foursquare是开源的,并且有一个有趣的代码模式与foursquare REST API交互。


开发Android REST客户端应用程序对我来说是一个很棒的资源。演讲者没有展示任何代码,他只是介绍了设计考虑因素和技术,在android中把一个坚实的Rest Api放在一起。不管你是不是播客达人,我建议你至少听一遍,不过就我个人而言,我已经听了4到5遍了,我可能还会再听一遍。

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

本节课将介绍在Android平台上开发RESTful应用程序的架构考虑事项。它着重于Android平台的设计模式、平台集成和性能问题。

在我的api的第一个版本中,有很多我没有考虑到的因素,我不得不进行重构


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

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


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.


我只是想给你们指出一个独立的类,它包含了所有的功能。

http://github.com/StlTenny/RestService

它以非阻塞的方式执行请求,并以易于实现的处理程序返回结果。甚至还附带了一个示例实现。


Robby提供了一个很好的答案,尽管我可以看到你还在寻找更多的信息。我用简单但错误的方式实现了REST api调用。直到看了这个谷歌I/O视频,我才明白我错在哪里。这并不像将AsyncTask与HttpUrlConnection get/put调用放在一起那么简单。


我强烈推荐REST客户端Retrofit。

我发现这篇写得很好的博客文章非常有用,它还包含简单的示例代码。 作者使用了Retrofit进行网络调用,Otto实现了一个数据总线模式:

http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html