AndroidのリモヌトプロシヌゞャコヌルRPCコマンドパタヌン

たえがき



最近、Androidプラットフォヌムに぀いお知り合いたした。 特定の段階では、リモヌトプロシヌゞャコヌル、たたはより簡単にはクラむアントずサヌバヌのやり取りで状況がどうなっおいるかを確認する必芁がありたした。

最初は、プラットフォヌムがEJBテクノロゞヌの䜿甚を蚱可するこずが望たれおいたした。 むンタヌネットでいく぀か怜玢した埌、私はそれがそれほど単玔ではないず確信したした。 ほずんどの゜ヌスは、Webサヌビスを代替ずしお䜿甚するこずを掚奚しおいたす。 EJBはAndroidには重すぎたす。 Webサヌビスの堎合、 ksoap2-androidフレヌムワヌクが掚奚されたした。

ksoap2の最初の研究䞭に別のレヌキに぀たずいたので、サヌバヌから独自のカスタムタむプのオブゞェクトを送受信する必芁がある段階に達したした。 怜玢を䜿甚しお、 この蚘事をここで芋぀けたした 。 そこから、各カスタムオブゞェクトにKvmSerializableむンタヌフェむスを実装する必芁があるこずを孊びたした。 同じこずは、オブゞェクトをシリアル化および逆シリアル化するためのメ゜ッドを実装する必芁があるこずを暗瀺しおいたす。 理論䞊は100を超える独自のオブゞェクトを䜿甚するこずになっおいたため、各オブゞェクトに察しおKvmSerializableの実装を蚘述するずいう考えは、どういうわけか私の熱意を喚起したせんでした。

䜕をすべきか、Androidプラットフォヌム䞊で長幎にわたっおRPCを敎理する䟿利な方法が存圚しなかったこずは本圓にありたすか 怜玢は続けられたした。 倚くの゜ヌスがJSONの䜿甚を掚奚しおいたす。 しかし、私もJSONのシリアル化を曞きたくはありたせんでした。 しかし、少し埌に、 gsonラむブラリヌに぀いお蚀及したしたが、そこはそれほど悪くないようです。

最埌の垌望はGWT-RPCテクノロゞヌでした。 GWTずAndroidは1぀の䌁業の発案であるため、AndroidクラむアントからGWT-RPCメ゜ッドを呌び出す簡単な方法があるはずです。 残念ながら、この方法は私には芋぀かりたせんでした。 gwt-phonegapラむブラリはありたすが、どういうわけかRPCに関する情報をすぐに芋぀けるこずができたせんでした。

圌の怜玢結果にほが完党に倱望し、圌はすでにこのビゞネスを攟棄したかった。 しかし、 興味深い蚘事がありたした。 著者は、バむナリシリアル化の䜿甚を提案したした。 Javaプラットフォヌムの暙準であり、HTTPプロトコルずAndroidに組み蟌たれたApache HTTPクラむアントを䜿甚しおオブゞェクトを送信したす。 確かに、このアプロヌチはすべおのオブゞェクトに察しお機胜するずは限らないず芏定されおいたした。 しかし、利点の䞭で、これにより開発時間が本圓に節玄されるこずが指摘されたした。 私は著者の考えを少しテストし、倚くのオブゞェクトにずっお、このタむプのシリアラむれヌションずトランスポヌトが適切であるず確信したした。 もちろん、倚くの開発者はAndroidのバむナリシリアル化方法を悪だず蚀っおいたす。 サヌバヌずクラむアントにクラスずバヌゞョンを保持するのは困難です。 原則ずしお、私は倧衆のために䜕も曞く぀もりはなかったので、このアプロヌチでは自分にずっお悪いこずは䜕も芋たせんでした。 ちょっず耇雑なオブゞェクトごずにこのビゞネスをテストする必芁があるこずをメモしたした。

トランスポヌトずシリアル化では、倚かれ少なかれ決めたようです。 ここで、アプリケヌションで䜿甚するための䟿利なツヌルが必芁でした。 それから、GWTに぀いお、぀たり察凊しなければならない玠晎らしいgwt-dispatchフレヌムワヌクに぀いお、もう䞀床考えなければなりたせんでした。 ハブラヌにはすでに圌に関する良い蚘事がありたした。 Gwt-dispatchはオヌプン゜ヌスプロゞェクトであり、基本的にGWT RemoteServiceServlet䞊に構築されたす。 䞊蚘の情報を分析した埌、このフレヌムワヌクを通垞のサヌブレットのラッパヌずしお再䜜成するこずは可胜であり、それほど難しくないように思えたした。 そしお、すでにAndroid偎で、httpクラむアントを䜿甚しお必芁なメ゜ッドを呌び出したす。

私はプロゞェクトの゜ヌスコヌドを勉匷し始めたした。 サヌバヌ偎を簡玠化し、GWTずのすべおの通信を切断する必芁がありたした。 すべおのActionオブゞェクトは、GWT IsSerializableの代わりに通垞のSerializableむンタヌフェむスを実装する必芁がありたした 。 数日間の仕事の埌、結果はコミュニティず共有したい結果でした。 そこで、 http-dispatchずいうラむブラリに入れたした。 そのコアは、実際にはわずかに再蚭蚈されたgwt-dispatchフレヌムワヌクです。 しかし䜕よりも、ラむブラリはテストの準備ができおおり、できればAndroidプラットフォヌムで䜿甚できたす。 少なくずも゚ミュレヌタヌずタブレットでの最初のテストは成功したした。 コミュニティの助けを借りお、結果を思い起こさせるこずが可胜になるこずを願っおいたす。

序文はここで終わりたす。 倚くの読者が実際にここに来たずいうこずです。

実甚郚



コマンドパタヌンは、クラむアントが事前定矩されたタむプの特定のコマンドをサヌバヌに送信するこずを意味したす。 サヌバヌはそれを認識し、コマンドを匕数ずしお䜿甚しおそれに関連付けられたアクションを実行したす。 アクションが完了するず、クラむアントは特定の結果を返したす。

http-dispatchフレヌムワヌクを䜿甚しお簡単なpingコマンドを䜜成する方法を瀺したす。 このコマンドは、任意のオブゞェクトをサヌバヌに送信し、同じオブゞェクトを受信したす。

共通のクラむアントサヌバヌ郚分



たず、クラむアントずサヌバヌの䞡方の操䜜に必芁なオブゞェクトに぀いお説明したす。

たず、コマンドの結果。 各結果には、 net.customware.http.dispatch.shared.Resultむンタヌフェヌスを実装する必芁がありたす。 結果はAbstractSimpleResult抜象クラスを拡匵したす。これは、1぀のオブゞェクトがサヌバヌから返される状況に適しおいたす。

PingActionResult.java
import net.customware.http.dispatch.shared.AbstractSimpleResult; public class PingActionResult extends AbstractSimpleResult<Object> { private static final long serialVersionUID = 1L; public PingActionResult(Object object) { super(object); } } 


次に、サヌバヌに送信されるコマンドを盎接蚘述したす。 埌者は、前のステップで説明した結果を返したす。 各チヌムは、汎甚むンタヌフェヌスnet.customware.http.dispatch.shared.Actionを実装する必芁がありたす。 実装パラメヌタヌは、結果のタむプを指定する必芁がありたす。 これは、前のステップのPingActionResultになりたす。 私たちのチヌムは、サヌバヌ䞊でデシリアラむズされ、結果ずしおクラむアントに送り返され、 PingActionResultにラップされたオブゞェクトを含みたす。 トレヌニング資料でサヌバヌの状態のいく぀かのケヌスを衚瀺したいので、null結果を返し、䟋倖をスロヌするオプションもチヌムに远加したす。

PingAction.java
 public class PingAction implements Action<PingActionResult> { private static final long serialVersionUID = 1L; private Object object; //      private boolean generateException; //   null    private boolean nullResult; public PingAction(Object object) { this.object = object; } public PingAction(boolean nullResult, boolean generateException) { this.generateException = generateException; this.nullResult = nullResult; } public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } public boolean isGenerateException() { return generateException; } public void setGenerateException(boolean generateException) { this.generateException = generateException; } public boolean isNullResult() { return nullResult; } public void setNullResult(boolean nullResult) { this.nullResult = nullResult; } } 


この段階で、クラむアントずサヌバヌの䞡方に存圚する郚分を特定したした。

サヌバヌ偎



たず、PingActionチヌムのコントロヌルクラスを䜜成したす。 そのような各制埡クラスは、 net.customware.http.dispatch.server.ActionHandlerむンタヌフェヌスを実装する必芁がありたす。 新しいハンドラヌの䜜成を容易にするために、 ActionHandlerむンタヌフェヌスメ゜ッドのいく぀かを既に実装しおいる高レベルの抜象クラスSimpleActionHandlerがありたす。

PingActionHandler.java
 import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import net.customware.http.dispatch.server.ExecutionContext; import net.customware.http.dispatch.server.SimpleActionHandler; import net.customware.http.dispatch.shared.ActionException; import net.customware.http.dispatch.shared.DispatchException; import net.customware.http.dispatch.test.shared.PingAction; import net.customware.http.dispatch.test.shared.PingActionResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Inject; import com.google.inject.Provider; public class PingActionHandler extends SimpleActionHandler<PingAction, PingActionResult> { protected final Logger log = LoggerFactory.getLogger(getClass()); @Override public PingActionResult execute(PingAction action, ExecutionContext context) throws DispatchException { try { //      if (action.isGenerateException()) { throw new Exception("Generated exception"); //    null  } else if (action.isNullResult()) { return null; //       } else { Object object = action.getObject(); log.debug("Received object " + object); return new PingActionResult(object); } } catch (Exception cause) { log.error("Unable to perform ping action", cause); //     ,  //       .  //    ActionException,   //   http-dispatch throw new ActionException(cause); } } // rollback         ,  //  BatchAction. ,      //   . @Override public void rollback(PingAction action, PingActionResult result, ExecutionContext context) throws DispatchException { log.debug("PingAction rollback called"); } } 


次に、制埡サヌブレットにハンドラヌを登録する必芁がありたす。 安党でないサヌブレットの䟋を䜿甚しお、Guiceを䜿甚せずにこれを行う方法を初めお玹介したす。

各制埡サヌブレットは、クラスnet.customware.http.dispatch.server.standard.AbstractStandardDispatchServletたたはAbstractSecureDispatchServletを拡匵する必芁がありたす。 暙準の制埡サヌブレットは次のようになりたす

StandardDispatcherTestService.java
 import net.customware.http.dispatch.server.BatchActionHandler; import net.customware.http.dispatch.server.DefaultActionHandlerRegistry; import net.customware.http.dispatch.server.Dispatch; import net.customware.http.dispatch.server.InstanceActionHandlerRegistry; import net.customware.http.dispatch.server.SimpleDispatch; import net.customware.http.dispatch.server.standard.AbstractStandardDispatchServlet; import net.customware.http.dispatch.test.server.handler.PingActionHandler; /** */ public class StandardDispatcherTestService extends AbstractStandardDispatchServlet { private static final long serialVersionUID = 1L; private Dispatch dispatch; public StandardDispatcherTestService() { // Setup for test case InstanceActionHandlerRegistry registry = new DefaultActionHandlerRegistry(); registry.addHandler( new BatchActionHandler() ); registry.addHandler(new PingActionHandler()); dispatch = new SimpleDispatch( registry ); } @Override protected Dispatch getDispatch() { return dispatch; } } 


ここでは、 PingActionHandlerに加えお、暙準のBatchActionHandlerも登録したす。 BatchActionコマンドパッケヌゞを凊理したす 。

web.xmlにサヌブレットの説明を远加したす
web.xml
 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>DispatchServlet</servlet-name> <servlet-class>net.customware.http.dispatch.test.server.standard.StandardDispatcherTestService</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatchServlet</servlet-name> <url-pattern>/standard_dispatch</url-pattern> </servlet-mapping> </web-app> 


それはすべおサヌバヌ郚分にありたす。

お客様



クラむアント偎に移りたしょう。 http-dispatchフレヌムワヌクがサヌバヌずのすべおの操䜜が非同期であるず想定するこずを盎ちに予玄したす。 同期呌び出しの可胜性はありたすが、䜿甚するこずはお勧めしたせん。

暙準の非同期むンタヌフェむスを䜿甚するAndroidプラットフォヌム甚のシンプルなクラむアントを䜜成したす。 サヌバヌず察話するには、状況に応じおむンタヌフェヌスnet.customware.http.dispatch.client.standard.StandardDispatchServiceAsyncたたはSecureDispatchServiceAsyncを実装する必芁がありたす。 Androidプラットフォヌムでの小さな経隓にもかかわらず、私はそれのためのむンタヌフェヌスの簡単な実装を曞く自由を取りたした。 今回はnet.customware.http.dispatch.client.android.AndroidStandardDispatchServiceAsyncを䜿甚したす 。 この実装の特城は、すべおのコマンドが個別のスレッドで実行されるこずです。 サヌバヌから結果を返すず、EDTストリヌムで凊理が行われたす。 次のようになりたす。

 public <R extends Result> void execute( final Action<R> action, final AsyncCallback<R> callback) { //  -"edt"    "edt"  final Handler uiThreadCallback = new Handler(); new Thread() { @Override public void run() { final Object result = getResult(action); //  і  "edt" ,   //   final Runnable runInUIThread = new Runnable() { @Override public void run() { HttpUtils.processResult(result, callback); } }; uiThreadCallback.post(runInUIThread); } }.start(); } 


おそらく、このアプロヌチは正しくなく、私の経隓が乏しいため、これを理解しおいたせん。 したがっお、AndroidクラむアントからRPCを呌び出すためのより適切な゜リュヌションを瀺しおくれた経隓豊富なAndroid開発者に感謝したす。

クラむアントコヌドで、 DispatchAsyncクラスのオブゞェクトを䜜成したす。 これは次のように実行できたす。

 DispatchAsync dispatch = new StandardDispatchAsync( new DefaultExceptionHandler(), new AndroidStandardDispatchServiceAsync(DISPATCH_URL_STANDARD)); 


サヌバヌにアクセスする堎合、結果甚にAsyncCallbackタむプのオブゞェクトを䜜成する必芁がありたす。

 AsyncCallback<PingRequestResult> callback = new AsyncCallback<PingRequestResult>() 


メ゜ッドを実装する

 public void onSuccess(PingRequestResult result) 


そしお

 public void onFailure(Throwable caught) 


コマンドが正垞に実行され、䟋倖的な状況が発生するず、それに応じお呌び出されたす。

次に、実行するコマンドを指定したす。

 dispatcher.execute(pingRequest, callback); 


RPCUtilsクラスに぀いお説明したす。RPCUtilsクラスは、サヌバヌに察しお異なるパラメヌタヌを䜿甚しおいく぀かのテスト呌び出しを行いたす。 DISPATCH_URLをロヌカルサヌバヌのアドレスに眮き換える必芁があるこずに泚意しおください。 既存のものを䜿甚できたすが、これはJBoss Openshiftプラットフォヌムにデプロむされたテストアプリケヌションです。

RPCUtils.java
 import java.util.ArrayList; import java.util.List; import net.customware.http.dispatch.client.AsyncCallback; import net.customware.http.dispatch.client.DefaultExceptionHandler; import net.customware.http.dispatch.client.DispatchAsync; import net.customware.http.dispatch.client.android.AndroidSecureDispatchServiceAsync; import net.customware.http.dispatch.client.android.AndroidStandardDispatchServiceAsync; import net.customware.http.dispatch.client.guice.SecureDispatchModule; import net.customware.http.dispatch.client.secure.CookieSecureSessionAccessor; import net.customware.http.dispatch.client.standard.StandardDispatchAsync; import net.customware.http.dispatch.test.shared.PingRequest; import net.customware.http.dispatch.test.shared.PingRequestResult; public class RPCUtils { protected static final String DISPATCH_URL_STANDARD = "http://httpdispatch-ep.rhcloud.com/standard_dispatch"; static DispatchAsync dispatch = new StandardDispatchAsync( new DefaultExceptionHandler(), new AndroidStandardDispatchServiceAsync(DISPATCH_URL_STANDARD)); public static DispatchAsync getDispatchAsync() { return dispatch; } //     public static void runBasicStringTest(LogWrapper log) { String testObject = "Test String Object"; PingRequest pingRequest = new PingRequest(testObject); testCommon(pingRequest, log); } //      ArrayList<String> public static void runBasicListTest(LogWrapper log) { List<String> testList = new ArrayList<String>(); testList.add("one"); testList.add("two"); PingRequest pingRequest = new PingRequest(testList); testCommon(pingRequest, log); } //     null public static void runNullSubObjectTest(LogWrapper log) { PingRequest pingRequest = new PingRequest(null); testCommon(pingRequest, log); } //  null  public static void runNullObjectTest(LogWrapper log) { PingRequest pingRequest = new PingRequest(true, false); testCommon(pingRequest, log); } //       public static void runExceptionTest(LogWrapper log) { PingRequest pingRequest = new PingRequest(false, true); testCommon(pingRequest, log); } private static void testCommon(PingRequest pingRequest, final LogWrapper log) { final long start = System.currentTimeMillis(); log.log("Sending object: " + pingRequest.getObject()); DispatchAsync dispatcher = getDispatchAsync(); AsyncCallback<PingRequestResult> callback = new AsyncCallback<PingRequestResult>() { @Override public void onSuccess(PingRequestResult result) { if (result == null) { log.log("Received null at " + (System.currentTimeMillis() - start) + "ms"); } else { log.log("Received result: " + result.get() + " at " + (System.currentTimeMillis() - start) + "ms"); } } @Override public void onFailure(Throwable caught) { log.log("Received exception: " + caught.getMessage() + " at " + (System.currentTimeMillis() - start) + "ms"); } }; dispatcher.execute(pingRequest, callback); } } 


LogWrapper.java
 public interface LogWrapper { void log(String text); } 


アクティビティのレむアりトを䜜成したす

activity_main.xml
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <Spinner android:id="@+id/actionTypeSP" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" /> <Button android:id="@+id/runBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/actionTypeSP" android:text="Run" /> <ScrollView android:id="@+id/scroller" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/runBtn" android:background="#FFFFFF" > <TextView android:id="@+id/logView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="3dp" android:scrollHorizontally="false" android:scrollbars="vertical" android:textSize="15sp" /> </ScrollView> </RelativeLayout> 


さお、アクティビティ自䜓のコヌド

MainActivity.java
 import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; public class MainActivity extends Activity { enum ActionType { BASIC_STRING_OBJECT("Basic Object Send/Receive"), BASIC_ARRAYLIST_OBJECT("Basic ArrayList Send/Receive"), NULL_SUB_OBJECT("Null argument Send/Receive"), NULL_OBJECT("Null Receive"), EXCEPTION("Remote Exception") ; String description; ActionType(String description) { this.description = description; } @Override public String toString() { return description; } } Spinner actionTypeSP; TextView logView; ScrollView scroller; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } void init() { initActionList(); logView = (TextView) findViewById(R.id.logView); scroller = (ScrollView) findViewById(R.id.scroller); initRunButton(); } void initActionList() { actionTypeSP = (Spinner) findViewById(R.id.actionTypeSP); ArrayAdapter<ActionType> dataAdapter = new ArrayAdapter<ActionType>( this, android.R.layout.simple_spinner_item, ActionType.values()); dataAdapter .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); actionTypeSP.setAdapter(dataAdapter); } void initRunButton() { Button runBtn = (Button) findViewById(R.id.runBtn); final LogWrapper log = new LogWrapper() { @Override public void log(String text) { MainActivity.this.log(text); } }; runBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { ActionType actionType = (ActionType) actionTypeSP .getSelectedItem(); if (actionType != null) { switch (actionType) { case BASIC_STRING_OBJECT: RPCUtils.runBasicStringTest(log); break; case BASIC_ARRAYLIST_OBJECT: RPCUtils.runBasicListTest(log); break; case EXCEPTION: RPCUtils.runExceptionTest(log); break; case NULL_OBJECT: RPCUtils.runNullObjectTest(log); break; case NULL_SUB_OBJECT: RPCUtils.runNullSubObjectTest(log); break; default: break; } } } }); } void log(String str) { String text = logView.getText().toString(); text += str + "\n"; logView.setText(text); scroller.smoothScrollTo(0, logView.getBottom()); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } } 


たた、アプリケヌションにむンタヌネットを䜿甚する蚱可を远加するこずを忘れないでください

AndroidManifest.xml
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.customware.http.dispatch.test.client.android" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/title_activity_main" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 


結果は次のようになりたす。



䞊のフィヌルドでアクションのタむプを遞択しおから、「実行」ボタンをクリックする必芁がありたす。 アプリケヌションはサヌバヌにコマンドを送信し、結果がログに衚瀺されたす。 利甚可胜なアクションテスト文字列の送信、 ArrayList <String>型のオブゞェクトの送信、 nullの送信、 nullの結果の取埗、サヌバヌでの䟋倖のスロヌ。

今のずころすべおです。 フィヌドバック、コメント、蚂正に感謝したす。 ご枅聎ありがずうございたした。

参照資料



プロゞェクトペヌゞ code.google.com/p/http-dispatch
Androidテストアプリケヌション http-dispatch.googlecode.com/files/HTTP_Dispatch_Test_Android.apk
準備ができたWAR http-dispatch.googlecode.com/files/HTTP_Dispatch_Test_Server.war
Eclipse甚のAndroidアプリプロゞェクト
http-dispatch.googlecode.com/files/HTTP_Dispatch_Test_Android.zip

Gwt -dispatchフレヌムワヌク code.google.com/p/gwt-dispatch

PS
http-dispatchの基瀎ずなるgwt-dispatchフレヌムワヌクは、非垞に倚様です。 サヌバヌ偎ずクラむアント偎を蚘述する方法はいく぀かありたす。 次回は、Guiceず安党な制埡サヌブレットを䜿甚したより興味深い䟋を玹介したす。 さたざたな䟋ず既補のテストアプリケヌションをプロゞェクトペヌゞからダりンロヌドできたす。

Source: https://habr.com/ru/post/J148436/


All Articles