ChatGPT解决这个技术问题 Extra ChatGPT

RESTful API 服务

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

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

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

目前我有一个带有 post 和 get 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);
}

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

    }

};
这可能对学习 Android REST 客户端实现的人很有帮助。 Dobjanschi 的演示文稿转录成 PDF:drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/…
由于许多人推荐了 Virgil Dobjanschi 的演示文稿,而 IO 2010 的链接现在已断开,因此这里是 YT 视频的直接链接:youtube.com/watch?v=xHXn3Kg2IQE

M
Marurban

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

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

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

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

您创建意图并调用 startService。服务中的操作开始,并向活动发送一条消息,说明它已启动。活动处理消息并显示进度。该服务完成操作并将一些数据发送回您的活动。您的活动处理数据并放入列表视图中该服务向您发送一条消息,说明它已完成,它会自行终止。活动获取完成消息并隐藏进度对话框。

我知道您提到您不想要代码库,但开源 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);
        }
    }
}

非常好,谢谢!我最终浏览了 Google iosched 应用程序,哇……它非常复杂,但我有一些工作正常,现在我只需要弄清楚它为什么工作!但是,是的,这是我工作的基本模式。非常感谢您。
答案的一个小补充:就像你做的那样 mReceiver.setReceiver(null);在 onPause 方法中,您应该执行 mReceiver.setReceiver(this);在 onResume 方法中。否则,如果您的活动在没有重新创建的情况下恢复,您可能不会收到事件
文档不是说您不必调用 stopSelf,因为 IntentService 会为您执行此操作吗?
@MikaelOhlson 正确,如果您将 IntentService 子类化,您应该调用 stopSelf,因为如果这样做,您将丢失对同一 IntentService 的任何待处理请求。
IntentService 会在任务完成时自杀,因此 this.stopSelf() 是不必要的。
T
Terrance

开发 Android REST 客户端应用程序对我来说是一个很棒的资源。演讲者没有展示任何代码,他只是回顾了在 android 中组合一个坚如磐石的 Rest Api 的设计考虑和技术。如果你的播客有点像人,我建议至少听一次,但是,到目前为止,我个人已经听了 4 到 5 次,我可能会再听一次。

Developing Android REST client applications
作者:Virgil Dobjanschi
描述:

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

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


+1 这包含您刚开始时永远不会想到的各种考虑因素。
是的,我第一次尝试开发 Rest 客户端几乎完全是他对不该做什么的描述(谢天谢地,在我看到这个之前,我意识到大部分都是错误的)。我有点不耐烦了。
我不止一次看过这个视频,我正在实施第二种模式。我的问题是我需要在我的复杂数据库模型中使用事务来从来自服务器的新数据中更新本地数据,而 ContentProvider 接口并没有为我提供一种方法来做到这一点。你有什么建议吗,特伦斯?
注意:Dobjanschi 对 HttpClient 的评论不再成立。请参阅stackoverflow.com/a/15524143/939250
是的,现在首选 HttpURLConnection。此外,随着 Android 6.0 的发布,对 Apache HTTP 客户端的支持有 officially been removed
S
Soumya Simanta

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

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


p
panchicore

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

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

适用于 Android 的 Foursquared 是 open-source,具有与foursquare REST API 交互的有趣代码模式。


P
Pete

我强烈推荐 REST 客户端 Retrofit

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

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


S
StlTenny

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

http://github.com/StlTenny/RestService

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


C
Community

假设我想在事件上启动服务 - 按钮的 onItemClicked()。在这种情况下,接收器机制将不起作用,因为:-
a) 我从 onItemClicked()
将接收器传递给服务(如 Intent extra)b) 活动移至后台。在 onPause() 中,我将 ResultReceiver 中的接收器引用设置为 null 以避免泄漏 Activity。
c) Activity 被破坏。
d) Activity 再次被创建。然而,此时服务将无法对 Activity 进行回调,因为接收器引用丢失。
有限广播或 PendingIntent 机制似乎在这种情况下更有用 - 请参阅 Notify activity from service


你说的有问题。也就是说,当活动移动到后台时,它不会被破坏......所以接收器仍然存在,活动上下文也存在。
@DArkO 当 Activity 暂停或停止时,它可能会在内存不足的情况下被 Android 系统杀死。请参阅Activity Lifecycle
T
TjerkW

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


您仍然可以通过将 webservice api 调用委托给 executor-thread-service 来对 IntentService 进行多次调用,作为 IntentService 派生类的成员变量
A
Aakash

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

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


A
Andrew Halloran

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


J
Jose L Ugia

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


死链接。域已过期。
链接不再存在。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅