[解決済み】Restful APIサービス
質問
WebベースのREST APIを呼び出すために使用できるサービスを作りたいと思っています。
基本的には、app initでサービスを開始し、そのサービスにurlを要求して結果を返せるようにしたい。 その間、プログレスウィンドウかそれに類するものを表示できるようにしたい。
私はIDLを使用するサービスを現在作成しました。私は、これが本当にアプリ間の通信にのみ必要であるとどこかで読んだので、これらは取り除く必要があると思いますが、それなしでコールバックを行う方法は不明です。 また、私が
post(Config.getURL("login"), values)
アプリがしばらく停止するようです(変な感じです。サービスのアイデアは、別のスレッドで実行されることだと思いました!)
現在、私はpostとget 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);
}
}
};
解決方法は?
もしサービスがアプリケーションの一部になるのであれば、必要以上に複雑にしていることになります。RESTful Web Service からデータを取得するという単純なユースケースであるため、次のようなものを検討する必要があります。 結果レシーバ と インテントサービス .
このService + ResultReceiverパターンは、次のようにサービスを開始またはバインドすることで機能します。 startService() を実行したいとき。Intentのextrasで、実行する操作を指定し、ResultReceiver(アクティビティ)を渡すことができます。
を実装したサービスでは onHandleIntent で、Intentで指定された操作を実行します。操作が完了したら、渡されたResultReceiverを使って 送る アクティビティにメッセージを返し、その時点で オンレシーブリザルト が呼び出されます。
例えば、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);
}
}
}
関連
-
[解決済み】Androidエミュレータのエラーメッセージ。"PANIC: Missing emulator engine program for 'x86' CPUS." (パニック: エミュレータ・エンジン・プログラムがありません)
-
[解決済み】プロガードを有効にすると、タスク ':app:crashlyticsStoreDeobsDebug' の依存関係を判断できない。
-
[解決済み】sendUserActionEvent()がnullである。
-
[解決済み】Android Studioで最新バージョンのgradleを使用する方法
-
[解決済み] Looper.prepare()を呼び出していないスレッドではハンドラを作成できない
-
[解決済み] android.support.design.widget.FloatingActionButton クラスの展開に失敗しました。
-
[解決済み] android.intent.action.MAINの意味は何ですか?
-
[解決済み] RESTful WebServiceにファイルや関連データをJSONで投稿する。
-
[解決済み] RESTとRESTfulの違いは何ですか?
-
[解決済み】RESTful 認証
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】Android - SDKバージョン23のアップデート後、ACTION-VIEWインテントフィルタを持つアクティビティを少なくとも1つ追加する。
-
[解決済み】Android Studioでパラメータ化されたユニットテストを実行すると、指定されたインクルードに対するテストが見つからないエラーが発生する
-
[解決済み】OnFragmentInteractionListenerを実装する方法
-
[解決済み】Androidエミュレータのエラーメッセージ。"PANIC: Missing emulator engine program for 'x86' CPUS." (パニック: エミュレータ・エンジン・プログラムがありません)
-
[解決済み】android.content.ActivityNotFoundException: 明示的なアクティビティクラスを見つけることができません。
-
[解決済み】sendUserActionEvent()がnullである。
-
[解決済み] 複数のデバイスを接続しているときにADB Shellを使用するには?error: more than one device and emulator "で失敗します。
-
[解決済み] Android: @drawable/picture を drawable に変換するのに失敗しました。
-
[解決済み] Android Fragment onAttach() 非推奨
-
[解決済み] Android REST クライアント、サンプル?