web-dev-qa-db-ja.com

安らかなAPIサービス

WebベースのREST AP​​Iを呼び出すために使用できるサービスを作成したいと考えています。

基本的に、アプリの初期化でサービスを開始し、そのサービスにURLを要求して結果を返すようにしたいのです。それまでの間、進行状況ウィンドウなどを表示できるようにしたいと考えています。

現在IDLを使用するサービスを作成しました。アプリ間通信にのみこれが本当に必要なことをどこかで読みました。また、post(Config.getURL("login"), values)を押すと、アプリがしばらく停止しているように見えます(奇妙に思えます-サービスの背後にある考えは、別のスレッドで実行されるということです!)

現在、postとget httpメソッドを含むサービス、2つの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);
        }

    }

};
219
Martyn

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

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

サービスで onHandleIntent を実装して、Intentで指定された操作を実行します。操作が完了したら、渡されたResultReceiverを使用して send に戻るメッセージをアクティビティに戻します onReceiveResult が呼び出されます。

そのため、たとえば、Webサービスからいくつかのデータを引き出したいとします。

  1. インテントを作成し、startServiceを呼び出します。
  2. サービス内の操作が開始され、開始されたことを示すメッセージがアクティビティに送信されます
  3. アクティビティはメッセージを処理し、進行状況を表示します。
  4. サービスは操作を終了し、アクティビティにデータを送り返します。
  5. アクティビティはデータを処理し、リストビューに入れます
  6. サービスは、それが完了したことを示すメッセージを送信し、自分自身を殺します。
  7. アクティビティは終了メッセージを取得し、進行状況ダイアログを非表示にします。

コードベースは必要ないと言っていたのは知っていますが、オープンソース Google I/O 201 アプリはこの方法でサービスを使用しています。

サンプルコードを追加するために更新:

アクティビティ。

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);
        }
    }
}
283
Robby Pond

Android RESTクライアントアプリケーションの開発は私にとって素晴らしいリソースでした。スピーカーにはコードは表示されません。Androidで堅牢なRest Apiを組み立てる際の設計上の考慮事項とテクニックについて説明します。あなたがポッドキャストのような人であるかどうかにかかわらず、私はこの人に少なくとも1回は聴いてもらうことをお勧めしますが、個人的にはこれまでに4〜5回聴いてきたので、おそらくまた聴きます。

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

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

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

17
Terrance

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

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

16
Soumya Simanta

@Martynは完全なコードを必要としないことは知っていますが、この注釈はこの質問に適していると思います。

10個のオープンソースAndroidすべてのAndroid開発者が調べる必要のあるアプリ

AndroidのFoursquaredは open-source であり、foursquare REST AP​​Iと対話する興味深いコードパターンがあります。

11
panchicore

REST client Retrofit を強くお勧めします。

このよく書かれたブログ投稿は非常に役立つことがわかりました。また、簡単なサンプルコードも含まれています。著者は Retrofit を使用してネットワーク呼び出しを行い、 Otto を使用してデータバスパターンを実装します。

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

6
Pete

すべての機能を組み込んだ、私がロールバックしたスタンドアロンクラスの方向に皆さんを向けたかっただけです。

http://github.com/StlTenny/RestService

要求を非ブロッキングとして実行し、実装しやすいハンドラーで結果を返します。実装例も付属しています。

5
StlTenny

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

4
Nikhil_Katre

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

4
TjerkW

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

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

2
Aakash

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

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

今後の投稿に特定のサンプルコードを追加します。それが役立つことを望み、アプローチを共有し、潜在的な疑問や問題を解決するために私に連絡してください。

2
Jose L Ugia

ロビーは素晴らしい答えを提供しますが、まだ詳細を探しているあなたを見ることができます。 REST apiを実装しましたが、簡単ですが間違った方法で呼び出します。これを見るのは、これが Google I/Oビデオ でした。 AsyncTaskをHttpUrlConnection get/put呼び出しと組み合わせるほど簡単ではありません。

1
Andrew Halloran