宁静的API服务

时间:2010-07-07 17:34:27

标签: android web-services api rest

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

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

我已经创建了一个当前使用IDL的服务,我已经读到了你真正需要这个用于跨应用程序通信的地方,所以认为这些需求需要剥离但不确定如何在没有它的情况下进行回调。此外,当我点击post(Config.getURL("login"), values)应用程序似乎暂停了一段时间(似乎很奇怪 - 认为服务背后的想法是它在不同的线程上运行!)

目前我有一个带post的服务,里面有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);
        }

    }

};

11 个答案:

答案 0 :(得分:283)

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

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

在服务中,您实现onHandleIntent以执行Intent中指定的操作。操作完成后,您可以使用传入的ResultReceiver将send消息返回到活动,此时将调用onReceiveResult

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

  1. 您创建意图并调用startService。
  2. 服务中的操作开始,并向活动发送一条消息,说明它已启动
  3. 活动处理消息并显示进度。
  4. 服务完成操作并将一些数据发送回您的活动。
  5. 您的活动处理数据并将其放入列表视图
  6. 服务会向您发送一条消息,说明已完成,并且会自行终止。
  7. 活动获取完成消息并隐藏进度对话框。
  8. 我知道您提到过您不想要代码库,但开源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 :(得分:17)

开发Android REST客户端应用程序对我来说是一个很棒的资源。演讲者没有显示任何代码,他只是在设计考虑因素和技术上将一个坚如磐石的Rest Api放在android中。如果你的播客有点与否,我建议给这个播放至少一个听,但是,就我个人而言,我已经听了4到5次,我可能会再听一遍。

Developing Android REST client applications
作者:Virgil Dobjanschi
说明

此会话将介绍在Android平台上开发RESTful应用程序的架构注意事项。它侧重于Android平台特有的设计模式,平台集成和性能问题。

在我的api的第一个版本中我真的没有做过很多考虑因素,我不得不重构

答案 2 :(得分:16)

  

当我击中时   帖子(Config.getURL(“login”),   应用程序似乎暂停了一个   虽然(看起来很奇怪 - 想到这个主意   服务的背后是它运行在   不同的线程!)

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

答案 3 :(得分:11)

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

10 Open Source Android Apps which every Android developer must look into

Android的Foursquared是open-source,并且有一个有趣的代码模式与foursquare REST API交互。

答案 4 :(得分:6)

我强烈推荐REST客户端Retrofit

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

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

答案 5 :(得分:5)

只是想让大家指出我所推出的独立类的方向,它包含了所有功能。

http://github.com/StlTenny/RestService

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

答案 6 :(得分:4)

让我们说我想在事件上启动服务 - onItemClicked()按钮。接收机制在这种情况下不起作用,因为: - a)我从onItemClicked()发送了Receiver到服务(如在Intent extra中) b)活动移至后台。在onPause()中,我将ResultReceiver中的接收器引用设置为null,以避免泄漏Activity c)活动被破坏 d)再次创建活动。但是,此时服务将无法回复活动,因为接收器参考丢失 在这种情况下,有限广播或PendingIntent的机制似乎更有用 - 参考Notify activity from service

答案 7 :(得分:4)

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

答案 8 :(得分:2)

  

此外,当我点击帖子(Config.getURL(“登录”),值)应用程序似乎暂停一段时间(似乎很奇怪 - 认为服务背后的想法是它在不同的线程上运行!)< / p>

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

答案 9 :(得分:2)

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

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

我将在即将发布的帖子中提供具体的示例代码。 希望它有所帮助,随时与我联系,分享方法并解决潜在的疑问或问题。

答案 10 :(得分:1)

Robby提供了一个很好的答案,但我可以看到你仍在寻找更多信息。我实现了REST api调用简单但错误的方式。直到看到这个Google I/O video我才知道我哪里出错了。它并不像将AsyncTask与HttpUrlConnection get / put调用放在一起那么简单。