Dịch vụ API đầy đủ


226

Tôi đang tìm cách tạo một dịch vụ mà tôi có thể sử dụng để thực hiện các cuộc gọi đến API REST dựa trên web.

Về cơ bản tôi muốn bắt đầu một dịch vụ trên ứng dụng init thì tôi muốn có thể yêu cầu dịch vụ đó yêu cầu một url và trả về kết quả. Trong khi đó tôi muốn có thể hiển thị một cửa sổ tiến trình hoặc một cái gì đó tương tự.

Tôi đã tạo một dịch vụ hiện đang sử dụng IDL, tôi đã đọc ở đâu đó rằng bạn chỉ thực sự cần dịch vụ này để liên lạc qua ứng dụng chéo, vì vậy hãy nghĩ rằng những nhu cầu này bị tước bỏ nhưng không chắc chắn làm thế nào để thực hiện cuộc gọi lại mà không có nó. Ngoài ra khi tôi nhấn post(Config.getURL("login"), values)ứng dụng dường như tạm dừng một lúc (có vẻ kỳ lạ - nghĩ rằng ý tưởng đằng sau một dịch vụ là nó chạy trên một luồng khác!)

Hiện tại tôi có một dịch vụ có các phương thức post và nhận http, một vài tệp AIDL (để liên lạc hai chiều), ServiceManager liên quan đến việc bắt đầu, dừng, ràng buộc vv với dịch vụ và tôi đang tự động tạo một Trình xử lý với mã cụ thể cho các cuộc gọi lại khi cần thiết.

Tôi không muốn bất cứ ai cho tôi một cơ sở mã hoàn chỉnh để làm việc, nhưng một số gợi ý sẽ được đánh giá rất cao.

Mã trong (hầu hết) đầy đủ:

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

Một vài tệp 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);
}

và người quản lý dịch vụ:

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

Dịch vụ init và ràng buộc:

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

chức năng gọi dịch vụ:

// 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
Điều này có thể rất hữu ích cho những người học triển khai ứng dụng khách Android REST. Bản trình bày của Dobjanschi được sao chép thành PDF: drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/
Kay Zed

Vì nhiều người đề xuất phần trình bày của Virgil Dobjanschi và liên kết đến IO 2010 đã bị hỏng ngay bây giờ, đây là liên kết trực tiếp đến video YT: youtube.com/watch?v=xHXn3Kg2IQE
Jose_GD

Câu trả lời:


283

Nếu dịch vụ của bạn sẽ là một phần của ứng dụng của bạn thì bạn đang làm cho nó phức tạp hơn mức cần thiết. Vì bạn có một trường hợp sử dụng đơn giản của việc một số dữ liệu từ một dịch vụ Web RESTful, bạn nên xem xét ResultReceiverIntentService .

Mẫu Dịch vụ + Kết quả này hoạt động bằng cách bắt đầu hoặc ràng buộc với dịch vụ với startService () khi bạn muốn thực hiện một số hành động. Bạn có thể chỉ định thao tác để thực hiện và chuyển trong Kết quả kết quả (hoạt động) thông qua các tính năng bổ sung trong Mục đích.

Trong dịch vụ bạn triển khai onHandleIntent để thực hiện thao tác được chỉ định trong Mục đích. Khi hoàn thành thao tác, bạn sử dụng thông qua trong resultReceiver để gửi tin nhắn trở lại Hoạt động tại điểm onReceiveResult sẽ được gọi.

Vì vậy, ví dụ, bạn muốn lấy một số dữ liệu từ Dịch vụ web của mình.

  1. Bạn tạo ý định và gọi startService.
  2. Hoạt động trong dịch vụ bắt đầu và nó gửi cho hoạt động một thông báo cho biết nó đã bắt đầu
  3. Các hoạt động xử lý tin nhắn và hiển thị một tiến trình.
  4. Dịch vụ kết thúc hoạt động và gửi một số dữ liệu trở lại hoạt động của bạn.
  5. Hoạt động của bạn xử lý dữ liệu và đưa vào chế độ xem danh sách
  6. Dịch vụ gửi cho bạn một thông báo nói rằng nó đã được thực hiện và nó sẽ tự giết.
  7. Các hoạt động nhận được thông báo kết thúc và ẩn hộp thoại tiến trình.

Tôi biết bạn đã đề cập rằng bạn không muốn có cơ sở mã nhưng ứng dụng Google I / O 2010 nguồn mở sử dụng một dịch vụ theo cách tôi đang mô tả.

Cập nhật để thêm mã mẫu:

Hoạt động.

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

Dịch vụ:

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

Tiện ích mở rộng resultReceiver - được chỉnh sửa để triển khai 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
Tuyệt vời cảm ơn bạn! Cuối cùng tôi đã đi qua ứng dụng Google iosched và wow ... nó khá phức tạp nhưng tôi có một cái gì đó đang hoạt động, bây giờ tôi chỉ cần tìm ra lý do tại sao nó hoạt động! Nhưng vâng, đây là mô hình cơ bản mà tôi đã làm việc. Cảm ơn rât nhiều.
Martyn

29
Một bổ sung nhỏ cho câu trả lời: khi bạn thực hiện mReceiver.setReceiver (null); trong phương thức onPause, bạn nên thực hiện mReceiver.setReceiver (cái này); trong phương thức onResume. Khác bạn có thể không nhận được các sự kiện nếu hoạt động của bạn được tiếp tục mà không được tạo lại
Vincent Mimoun-Prat

7
Đừng các tài liệu nói rằng bạn không phải gọi stopSelf, vì IntentService làm điều đó cho bạn?
Mikael Ohlson

2
@MikaelOhlson Đúng, bạn không nên gọi stopSelfnếu bạn phân lớp IntentServicevì nếu bạn làm như vậy, bạn sẽ mất mọi yêu cầu đang chờ xử lý IntentService.
quietmint

1
IntentServicesẽ tự sát khi nhiệm vụ hoàn thành, vì vậy this.stopSelf()không cần thiết.
Euporie

17

Phát triển ứng dụng khách Android REST là một tài nguyên tuyệt vời đối với tôi. Người nói không hiển thị bất kỳ mã nào, anh ta chỉ xem xét các thiết kế và kỹ thuật trong việc kết hợp một Rest Api vững chắc trong Android. Nếu bạn là người thích podcast hay không, tôi khuyên bạn nên cho người này nghe ít nhất một lần nhưng cá nhân tôi đã nghe nó như thế 4 hoặc 5 lần cho đến nay và có lẽ tôi sẽ nghe lại lần nữa.

Phát triển ứng dụng khách Android REST
Tác giả: Virgil Dobjanschi
Mô tả:

Phiên này sẽ trình bày các cân nhắc về kiến ​​trúc để phát triển các ứng dụng RESTful trên nền tảng Android. Nó tập trung vào các mẫu thiết kế, tích hợp nền tảng và các vấn đề hiệu suất dành riêng cho nền tảng Android.

Và có rất nhiều cân nhắc tôi thực sự đã không thực hiện trong phiên bản đầu tiên của api mà tôi phải tái cấu trúc


4
+1 Điều này chứa các loại cân nhắc bạn sẽ không bao giờ nghĩ đến khi bạn bắt đầu.
Thomas Ahle

Vâng, bước đột phá đầu tiên của tôi trong việc phát triển ứng dụng khách Rest gần như chính xác là mô tả của anh ấy về những gì chính xác không nên làm (Rất may tôi nhận ra nhiều điều này là sai trước khi tôi xem điều này). Tôi nói dối một chút.
Terrance

Tôi đã xem video này hơn một lần và tôi đang thực hiện mẫu thứ hai. Vấn đề của tôi là tôi cần sử dụng các giao dịch trong mô hình cơ sở dữ liệu phức tạp của mình để cập nhật dữ liệu cục bộ từ dữ liệu mới xuất phát từ máy chủ và giao diện ContentProvider không cung cấp cho tôi cách thực hiện. Bạn có gợi ý nào không, Terrance?
Flávio Faria

2
NB: Nhận xét của Dobjanschi trên HttpClient không còn được giữ. Xem stackoverflow.com/a/15524143/939250
Donal Lafferty

Có, kết nối httpURLC hiện được ưu tiên. Hơn nữa, với việc phát hành Android 6.0, hỗ trợ cho Máy khách HTTP Apache đã chính thức bị xóa .
RonR

16

Ngoài ra, khi tôi nhấn vào bài đăng (Config.getURL ("đăng nhập"), các giá trị) ứng dụng dường như tạm dừng một lúc (có vẻ kỳ lạ - nghĩ rằng ý tưởng đằng sau một dịch vụ là nó chạy trên một luồng khác!)

Không, bạn phải tự tạo một luồng, một dịch vụ cục bộ chạy trong luồng UI theo mặc định.




5

Chỉ muốn hướng tất cả các bạn theo hướng của một lớp độc lập mà tôi đã kết hợp tất cả các chức năng.

http://github.com/StlTenny/RestService

Nó thực hiện yêu cầu là không chặn và trả về kết quả trong một trình xử lý dễ thực hiện. Thậm chí đi kèm với một thực hiện ví dụ.


4

Hãy nói rằng tôi muốn bắt đầu dịch vụ trên một sự kiện - onItemClicky () của một nút. Cơ chế Người nhận sẽ không hoạt động trong trường hợp đó vì: -
a) Tôi đã chuyển Người nhận đến dịch vụ (như trong Ý định bổ sung) từ onItemClicky ()
b) Hoạt động di chuyển đến nền. Trong onPause () tôi đặt tham chiếu người nhận trong resultReceiver thành null để tránh rò rỉ Activity.
c) Hoạt động bị phá hủy.
d) Hoạt động được tạo lại. Tuy nhiên, tại thời điểm này, Dịch vụ sẽ không thể thực hiện cuộc gọi lại cho Hoạt động vì tham chiếu người nhận bị mất.
Cơ chế phát sóng giới hạn hoặc PendingIntent dường như hữu ích hơn trong các tình huống như vậy - tham khảo hoạt động Thông báo từ dịch vụ


1
có một vấn đề với những gì bạn đang nói. đó là khi hoạt động chuyển sang nền thì nó không bị phá hủy ... vì vậy người nhận vẫn tồn tại và bối cảnh hoạt động cũng vậy.
DArkO

@DArkO Khi một Hoạt động bị tạm dừng hoặc dừng, nó có thể bị hệ thống Android giết chết trong các tình huống bộ nhớ thấp. Tham khảo Vòng đời hoạt động .
jk7

4

Lưu ý rằng giải pháp từ Robby Pond bằng cách nào đó còn thiếu: theo cách này, bạn chỉ cho phép thực hiện một cuộc gọi api tại một thời điểm vì IntentService chỉ xử lý một mục đích tại một thời điểm. Thường thì bạn muốn thực hiện các cuộc gọi api song song. Nếu bạn muốn làm điều này, bạn phải mở rộng Dịch vụ thay vì IntentService và tạo chủ đề của riêng bạn.


1
Bạn vẫn có thể thực hiện nhiều cuộc gọi trên IntentService bằng cách ủy quyền các cuộc gọi api của dịch vụ web đến dịch vụ thực thi-luồng, hiện dưới dạng một biến thành viên của lớp có nguồn gốc IntentService của bạn
Viren

2

Ngoài ra, khi tôi nhấn vào bài đăng (Config.getURL ("đăng nhập"), các giá trị) ứng dụng dường như tạm dừng một lúc (có vẻ kỳ lạ - nghĩ rằng ý tưởng đằng sau một dịch vụ là nó chạy trên một luồng khác!)

Trong trường hợp này, tốt hơn là sử dụng asynctask, chạy trên một luồng khác và trả lại kết quả cho luồng ui khi hoàn thành.


2

Có một cách tiếp cận khác ở đây về cơ bản giúp bạn quên đi toàn bộ việc quản lý các yêu cầu. Nó dựa trên một phương thức hàng đợi không đồng bộ và phản hồi dựa trên cuộc gọi / gọi lại. Ưu điểm chính là bằng cách sử dụng phương pháp này, bạn sẽ có thể làm cho toàn bộ quá trình (yêu cầu, nhận và phân tích phản hồi, sabe thành db) hoàn toàn minh bạch cho bạn. Khi bạn nhận được mã phản hồi, công việc đã hoàn thành. Sau đó, bạn chỉ cần thực hiện một cuộc gọi đến db của bạn và bạn đã hoàn tất. Nó cũng giúp giải quyết vấn đề xảy ra khi hoạt động của bạn không hoạt động. Điều sẽ xảy ra ở đây là bạn sẽ lưu tất cả dữ liệu trong cơ sở dữ liệu cục bộ của mình nhưng phản hồi sẽ không được xử lý bởi hoạt động của bạn, đó là cách lý tưởng.

Tôi đã viết về một cách tiếp cận chung ở đây http://ugiagonzalez.com/2012/07/02/theres-life-after-asynct task-in-android /

Tôi sẽ đặt mã mẫu cụ thể trong các bài viết sắp tới. Hy vọng nó sẽ giúp, hãy liên hệ với tôi để chia sẻ cách tiếp cận và giải quyết các nghi ngờ hoặc vấn đề tiềm ẩn.


Liên kết chết. Tên miền đã hết hạn.
jk7

1

Robby cung cấp một câu trả lời tuyệt vời, mặc dù tôi có thể thấy bạn vẫn đang tìm kiếm thêm thông tin. Tôi đã triển khai REST api gọi BUT dễ dàng sai cách. Mãi đến khi xem video Google I / O này , tôi mới hiểu mình đã sai ở đâu. Điều này không đơn giản bằng việc kết hợp AsyncTask với một cuộc gọi nhận / đặt cuộc gọi HTTPUrlConnection.


Liên kết chết. Đây là bản cập nhật - Google I / O 2010 - Ứng dụng khách Android REST
zim
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.