Restful APIサービス


226

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

    }

};

5
これは、Android RESTクライアントの実装を学ぶ人にとって非常に役立つかもしれません。PDFへのDobjanschiのプレゼンテーション転写:drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/...
ケイゼッド

多くの人がVirgil Dobjanschiのプレゼンテーションを推奨しており、IO 2010へのリンクが壊れているため、YouTubeへの直接リンクは次のとおりです: youtube.com/watch
v

回答:


283

サービスがアプリケーションの一部になる場合は、必要以上に複雑にします。RESTful Webサービスからデータを取得する単純なユースケースがあるため、ResultReceiverおよびIntentServiceを調べる必要があります。

このService + ResultReceiverパターンは、何らかのアクションを実行したいときにstartService()を使用してサービスを開始またはバインドすることで機能します。実行する操作を指定して、ResultReceiver(アクティビティ)をIntentのエクストラを介して渡すことができます。

このサービスでは、InHandleIntentを実装して、Intentで指定された操作を実行します。操作が完了すると、あなたがするResultReceiverに渡さ使用送る、その時点での活動へのメッセージの背中を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アプリケーションを試してみて、すごい...それはかなり複雑ですが、機能しているものがあります。今、それが機能する理由を理解する必要があります。しかし、はい、これは私が取り組んでいる基本的なパターンです。どうもありがとうございました。
Martyn

29
答えへの小さな追加:mReceiver.setReceiver(null);を行うとき onPauseメソッドでは、mReceiver.setReceiver(this);を実行する必要があります。onResumeメソッド内。それ以外の場合、アクティビティを再作成せずに再開すると、イベントを受信できない可能性があります
Vincent Mimoun-Prat

7
IntentServiceが代行してくれるので、ドキュメントはstopSelfを呼び出す必要がないと言っていませんか?
Mikael Ohlson 2012

2
@MikaelOhlson正解です。サブクラス化する場合呼び出さstopSelfないでください。呼び出すと、IntentService保留中の同じリクエストが失われますIntentService
quietmint 2012

1
IntentServiceタスクが完了するthis.stopSelf()と自動的に終了するため、不要です。
Euporie 2014年

17

Android RESTクライアントアプリケーションの開発は、私にとって素晴らしいリソースでした。スピーカーはコードを表示せず、Androidで堅実なRest Apiを作成する際の設計上の考慮事項と手法を検討します。ポッドキャストがちょっとした人でもしなくても、少なくとも1回は聞くことをお勧めしますが、個人的にはこれまで4〜5回聞いたことがあり、おそらくもう一度聞くつもりです。

Android RESTクライアントアプリケーションの開発
著者:Virgil Dobjanschi
説明:

このセッションでは、AndroidプラットフォームでRESTfulアプリケーションを開発するためのアーキテクチャ上の考慮事項について説明します。Androidプラットフォームに固有の設計パターン、プラットフォーム統合、パフォーマンスの問題に焦点を当てています。

また、APIの最初のバージョンで実際に行っていなかった考慮事項がたくさんあるため、リファクタリングする必要がありました。


4
+1これには、最初は考えられないような考慮事項が含まれています。
Thomas

ええ、Restクライアントの開発への私の最初の進出は、ほぼ正確に何をすべきでないかについての彼の説明でした(ありがたいことに、これを見る前に、これの多くが間違っていたことに気付きました)。私はそれを少しだけ頼みました。
Terrance、2009

このビデオを2回以上見たことがありますが、2番目のパターンを実装しています。私の問題は、サーバーから取得した新しいデータからローカルデータを更新するために、複雑なデータベースモデルでトランザクションを使用する必要があることです。ContentProviderインターフェイスでは、その方法が提供されません。テランス、何か提案はありますか?
フラビオ・ファリア

2
注意:HttpClientに関するDobjanschiのコメントは保持されなくなりました。参照してくださいstackoverflow.com/a/15524143/939250
ドナル・ラファティ

はい、HttpURLConnectionが優先されるようになりました。さらに、Android 6.0のリリースに伴い、Apache HTTPクライアントのサポートが正式に削除されました
RonR 2015年

16

また、post(Config.getURL( "login")、values)を押すと、アプリがしばらく一時停止しているように見えます(奇妙に思えます-サービスの背後にある考えは、別のスレッドで実行することだと思いました!)

自分でスレッドを作成する必要はありません。ローカルサービスはデフォルトでUIスレッドで実行されます。




5

すべての機能を組み込んだ、私が展開したスタンドアロンクラスの方向を示すために、すべての人を紹介したかっただけです。

http://github.com/StlTenny/RestService

リクエストをノンブロッキングとして実行し、実装しやすいハンドラで結果を返します。実装例も付いてきます。


4

イベント-ボタンのonItemClicked()でサービスを開始したいとしましょう。その場合、Receiverメカニズムは機能しません。理由は次の
とおりです。a)onItemClicked()から(Intent extraのように)Receiverをサービスに渡した
b)アクティビティがバックグラウンドに移動した。onPause()では、アクティビティのリークを回避するために、ResultReceiver内のレシーバー参照をnullに設定します。
c)アクティビティが破壊されます。
d)アクティビティが再度作成されます。ただし、この時点では、レシーバーの参照が失われているため、サービスはアクティビティにコールバックできません。
制限されたブロードキャストまたはPendingIntentのメカニズムは、このようなシナリオでより役立つようです- サービスからの通知アクティビティを参照してください


1
あなたが言っていることに問題があります。つまり、アクティビティがバックグラウンドに移動しても破棄されません...したがって、レシーバーはまだ存在し、アクティビティコンテキストも存在します。
DArkO

@DArkOアクティビティが一時停止または停止すると、メモリ不足の状況でAndroidシステムによって強制終了される可能性があります。アクティビティのライフサイクルを参照してください。
jk7 2017年

4

Robby Pondからのソリューションはどういうわけか不足していることに注意してください。この方法では、IntentServiceが一度に1つのインテントしか処理しないため、一度に1つのAPI呼び出ししか実行できません。多くの場合、並列API呼び出しを実行する必要があります。これを行うには、IntentServiceの代わりにServiceを拡張し、独自のスレッドを作成する必要があります。


1
IntentService派生クラスのメンバー変数として存在するexecutor-thread-serviceにwebservice api呼び出しを委任することにより、引き続きIntentServiceで複数の呼び出しを行うことができます
Viren

2

また、post(Config.getURL( "login")、values)を押すと、アプリがしばらく一時停止しているように見えます(奇妙に思えます-サービスの背後にある考えは、別のスレッドで実行することだと思いました!)

この場合、別のスレッドで実行され、完了時にUIスレッドに結果を返すasynctaskを使用することをお勧めします。


2

ここには、基本的にリクエストの管理全体を忘れるのに役立つ別のアプローチがあります。これは、非同期キューメソッドと呼び出し可能/コールバックベースの応答に基づいています。主な利点は、このメソッドを使用することで、プロセス全体(要求の取得、解析、応答、データベースへのサブセクション)を完全に透過的にできることです。応答コードを取得したら、作業はすでに完了しています。その後は、dbを呼び出すだけで完了です。それはまたあなたの活動がアクティブではないときに何が起こるかという問題の解決にも役立ちます。ここで何が起こるかというと、すべてのデータがローカルデータベースに保存されますが、応答はアクティビティによって処理されないということです。これが理想的な方法です。

私はここで一般的なアプローチについて書きました http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

私は今後の投稿に特定のサンプルコードを配置します。それがお役に立てば幸いです。アプローチを共有し、潜在的な疑問や問題を解決するために私に連絡してください。


リンク切れ。ドメインの有効期限が切れています。
jk7 2017年

1

Robbyは素晴らしい答えを提供しますが、まだ詳細情報を探しているようです。REST APIを実装しましたが、簡単ですが間違った方法で呼び出されます。このGoogle I / Oビデオを見て初めて、どこが間違っているのか理解できました。AsyncTaskとHttpUrlConnectionのget / put呼び出しを組み合わせるほど単純ではありません。


リンク切れ。これが更新されたものです
-Google
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.