2010-07-07 133 views
210

我正在尋找一個服務,我可以使用它來調用基於Web的REST API。Restful API服務

基本上我想在app init上啓動一個服務,然後我希望能夠請求該服務請求一個url並返回結果。同時我希望能夠顯示進度窗口或類似的東西。

我已經創建了一個當前使用IDL的服務,我已經在某處讀過你只需要跨應用程序通信的地方,所以認爲這些需求會剝離出來,但不確定如何在沒有它的情況下執行回調。此外,當我點擊post(Config.getURL("login"), values)該應用程序似乎暫停了一段時間(似乎很奇怪 - 認爲服務背後的想法是,它運行在不同的線程!)

目前我有一個服務與郵政和獲取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; 
    } 
} 

服務init和綁定:

// 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客戶端實現非常有幫助。 Dobjanschi的演示文稿轉錄爲PDF:https://drive.google.com/file/d/0B2dn_3573C3RdlVpU2JBWXdSb3c/edit?usp=sharing – 2013-12-27 05:34:41

+0

由於許多人都推薦Virgil Dobjanschi的演示文稿,並且現在IO 2010的鏈接已損壞,因此這裏是直接鏈接到YT視頻:http://www.youtube.com/watch?v = xHXn3Kg2IQE – 2014-03-31 19:20:27

回答

278

如果您的服務將是,那麼你使它方式複雜得多,它需要你的應用程序的一部分。由於您從RESTful Web服務獲取某些數據的用例很簡單,因此您應該查看ResultReceiverIntentService

此服務+ ResultReceiver模式的工作方式是,當您想要執行某項操作時,通過啓動或綁定到服務startService()。您可以指定操作來執行並通過Intent中的附加組件傳遞ResultReceiver(活動)。

在您實施onHandleIntent的服務中執行意圖中指定的操作。當操作完成後,您使用傳入的ResultReceiver返回send一條消息回到活動,在該點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); 
     } 
    } 
} 
+0

好的答案,儘管模式有點難以完全包裹我的頭。你介意添加一些示例代碼嗎?那真是太棒了! – idolize 2010-07-08 02:16:13

+0

是的。一旦你看到它的工作,這很簡單。 – 2010-07-08 02:41:09

+1

非常好 - 謝謝! 我最終通過谷歌iOS應用程序和哇......這是非常複雜的,但我有一些工作,現在我只需要解決它爲什麼工作!但是,是的,這是我工作的基本模式。 非常感謝。 – Martyn 2010-07-08 13:47:08

15

此外,當我打 後(Config.getURL(「登錄「), 值)應用程序似乎暫停爲 while(似乎很奇怪 - 壽ught想法 後面的服務是它運行在 不同的線程!)

不,你必須創建線程自己,本地服務在默認情況下,UI線程中運行。

4

可以說我想在一個事件onItemClicked()的按鈕上啓動服務。接收機制在這種情況下將不起作用,因爲: -
a)我從onItemClicked() 將接收機傳遞給了服務(與Intent相同)b)活動移至背景。在onPause()中,我將ResultReceiver中的接收者引用設置爲null以避免泄露Activity。
c)活動被破壞。
d)再次創建活動。但是,此時服務將無法對該活動進行回調,因爲該接收者引用丟失。
有限廣播的機制或的PendingIntent似乎更加有用在這樣scenarios-指Notify activity from service

+1

你說的話有問題。那就是當活動移動到背景時它不會被銷燬......所以接收器仍然存在,並且活動上下文也是。 – DArkO 2011-09-05 06:31:12

+0

@DArkO當活動暫停或停止時,它可以在內存不足的情況下被Android系統殺死。請參閱[活動生命週期](https://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle)。 – jk7 2017-03-14 17:37:42

4

注意,從羅比池解決方案以某種方式缺乏:在這種方式,您只允許在待辦事項一個API調用因爲IntentService一次只處理一個意圖。通常你想執行並行API調用。如果你想要這個,你必須擴展Service而不是IntentService並創建你自己的線程。

+1

您仍然可以通過將webservice api調用委託給executor-thread-service(作爲IntentService派生類的成員變量提供)來對IntentService執行多個調用 – Viren 2011-07-25 08:05:32

16

開發Android REST客戶端應用程序對我來說是一個很棒的資源。演講者沒有顯示任何代碼,他只是在設計考慮和技術上將Android中的一個堅實的Rest Api放在一起。如果你的播客有點不喜歡,我建議至少給一個聽一個聽,但個人來說,我已經聽過這樣的4到5次了,我可能會再聽一次。

Developing Android REST client applications
作者:維吉爾Dobjanschi
說明:

本次會議將介紹建築方面的考慮開發Android平臺的RESTful應用程序。它專注於Android平臺特有的設計模式,平臺集成和性能問題。

而且有我真的沒有在我的API的第一個版本,我已經有重構

+4

+1這包含您開始時不會想到的各種考慮事項。 – 2011-09-21 14:29:57

+0

是的,我第一次嘗試開發一個Rest客戶端幾乎就是他對什麼都不做的描述(謝天謝地,在我看到這個之前我意識到這很錯誤)。我對此表示贊同。 – Terrance 2011-09-21 14:33:30

+0

我已經多次看過此視頻,並且正在實施第二種模式。我的問題是,我需要使用複雜數據庫模型中的事務來更新來自服務器的新數據的本地數據,而ContentProvider接口並不能爲我提供一種方法。你有什麼建議嗎,Terrance? – 2011-10-19 20:06:17

2

此外,當我打的崗位上做出了這麼多的因素(Config.getURL(「登錄」 )值)的應用程序似乎停頓了一會兒(似乎不可思議! - 想到了一個服務背後的想法是,它運行在不同的線程)

在這種情況下,它能夠更好地使用的AsyncTask,它運行在一個不同的線程並在完成時將結果返回給UI線程。

2

這裏還有另一種方法,基本上可以幫助您忘記請求的整個管理。它基於異步隊列方法和基於可回調/回調的響應。 主要優點是通過使用這種方法,您可以使整個過程(請求,獲取和解析響應,sabe到數據庫)對您完全透明。一旦你得到響應代碼,工作已經完成。之後,你只需要打個電話給你的分貝,你就完成了。 當您的活動未處於活動狀態時,會發生什麼問題也會有所幫助。 這裏會發生什麼是您將所有數據保存在本地數據庫中,但響應不會被您的活動處理,這是理想的方式。

我寫了一個通用的方法在這裏 http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

我會投入具體的示例代碼中即將到來的職位。 希望它有幫助,隨時與我聯繫分享方法和解決潛在的疑慮或問題。

+0

無效鏈接。該域名已過期。 – jk7 2017-03-14 16:54:06

5

只是想指出你所有的方向,我把一個獨立的類,它包含了所有的功能。

http://github.com/StlTenny/RestService

它執行請求作爲無阻塞,並且返回以容易的結果,以實現處理程序。甚至附帶一個示例實現。

0

羅比提供了一個很好的答案,儘管我可以看到你仍然在尋找更多的信息。我實現了REST API調用easy但錯誤的方式。直到看到這個Google I/O video,我才明白我出錯的地方。這不像使用HttpUrlConnection get/put調用將AsyncTask放在一起那麼簡單。

+0

死鏈接。以下是更新後的版本 - [Google I/O 2010 - Android REST客戶端應用程序](http://goo.gl/95zfmf) – zim 2014-04-03 20:23:04