如何使用適用於 Android 的 Azure Mobile Apps SDK
本指南說明如何使用 Android 用戶端 SDK for Mobile Apps 來實作常見案例,例如:
- 查詢資料(插入、更新和刪除)。
- 驗證。
- 處理錯誤。
- 自訂用戶端。
本指南著重於用戶端 Android SDK。 若要深入瞭解Mobile Apps的伺服器端 SDK,請參閱 使用 .NET 後端 SDK 或 如何使用 Node.js 後端 SDK。
參考文件
您可以在 GitHub 上找到 Android 用戶端連結庫的 Javadocs API 參考 。
支援的平台
適用於 Android 的 Azure Mobile Apps SDK 支援適用於手機和平板電腦尺寸的 API 層級 19 到 24 級(KitKat 到 Nougat)。 特別是,驗證會利用常見的 Web 架構方法來收集認證。 伺服器流程驗證不適用於小型尺寸裝置,例如手錶。
設定和必要條件
完成Mobile Apps快速入門教學課程。 此工作可確保已符合開發 Azure Mobile Apps 的所有必要條件。 本快速入門也可協助您設定帳戶,並建立您的第一個行動應用程式後端。
如果您決定不完成快速入門教學課程,請完成下列工作:
- 建立要與您的 Android 應用程式搭配使用的行動應用程式後端 。
- 在 Android Studio 中, 更新 Gradle 組建檔案。
- 啟用因特網許可權。
更新 Gradle 組建檔案
變更兩個 build.gradle 檔案:
將此程式代碼新增至 Project 層級 build.gradle 檔案:
buildscript { repositories { jcenter() google() } } allprojects { repositories { jcenter() google() } }
將此程式代碼新增至相依性標籤的Module 應用程式層級 build.gradle 檔案:
implementation 'com.microsoft.azure:azure-mobile-android:3.4.0@aar'
目前最新版本為 3.4.0。 支援的版本會列 在 bintray 上。
啟用因特網許可權
若要存取 Azure,您的應用程式必須啟用因特網許可權。 如果尚未啟用,請將下列程式代碼行新增至 您的 AndroidManifest.xml 檔案:
<uses-permission android:name="android.permission.INTERNET" />
建立客戶端連線
Azure Mobile Apps 為您的行動應用程式提供四個功能:
- 數據存取和離線同步處理與 Azure Mobile Apps Service。
- 呼叫以 Azure Mobile Apps Server SDK 撰寫的自定義 API。
- 使用 Azure App 服務 驗證和授權進行驗證。
- 使用通知中樞進行推播通知註冊。
這些函式會先要求您建立 MobileServiceClient
物件。 您的行動用戶端內應該只建立一個 MobileServiceClient
對象(也就是說,它應該是Singleton 模式)。 若要建立 MobileServiceClient
物件︰
MobileServiceClient mClient = new MobileServiceClient(
"<MobileAppUrl>", // Replace with the Site URL
this); // Your application Context
<MobileAppUrl>
是指向行動後端的字串或URL物件。 如果您使用 Azure App 服務 來裝載行動後端,請確定您使用URL的安全https://
版本。
用戶端也需要存取 Activity 或 Context - this
範例中的 參數。 MobileServiceClient 建構應該發生在 onCreate()
檔案中所參考活動的方法內 AndroidManifest.xml
。
最佳做法是,您應該將伺服器通訊抽象化為自己的 (singleton-pattern) 類別。 在此情況下,您應該在建構函式內傳遞 Activity,以適當地設定服務。 例如:
package com.example.appname.services;
import android.content.Context;
import com.microsoft.windowsazure.mobileservices.*;
public class AzureServiceAdapter {
private String mMobileBackendUrl = "https://myappname.azurewebsites.net";
private Context mContext;
private MobileServiceClient mClient;
private static AzureServiceAdapter mInstance = null;
private AzureServiceAdapter(Context context) {
mContext = context;
mClient = new MobileServiceClient(mMobileBackendUrl, mContext);
}
public static void Initialize(Context context) {
if (mInstance == null) {
mInstance = new AzureServiceAdapter(context);
} else {
throw new IllegalStateException("AzureServiceAdapter is already initialized");
}
}
public static AzureServiceAdapter getInstance() {
if (mInstance == null) {
throw new IllegalStateException("AzureServiceAdapter is not initialized");
}
return mInstance;
}
public MobileServiceClient getClient() {
return mClient;
}
// Place any public methods that operate on mClient here.
}
您現在可以在主要活動的 方法中onCreate()
呼叫 AzureServiceAdapter.Initialize(this);
。 任何其他需要存取用戶端的方法,都使用 AzureServiceAdapter.getInstance();
來取得服務配接器的參考。
資料作業
Azure Mobile Apps SDK 的核心是提供在行動應用程式後端上 SQL Azure 內儲存資料的存取權。 您可以使用強型別類別(慣用)或不具類型的查詢來存取此數據(不建議)。 本節的大部分內容會處理使用強型別類別。
定義客戶端資料類別
若要從 SQL Azure 資料表存取資料,請定義對應至行動應用程式後端中數據表的用戶端資料類別。 本主題中的範例假設名為 MyDataTable 的數據表,其中包含下列數據行:
- id
- text
- 完成
對應的具型別客戶端物件位於名為 MyDataTable.java 的檔案中:
public class ToDoItem {
private String id;
private String text;
private Boolean complete;
}
針對您新增的每個欄位新增 getter 和 setter 方法。 如果您的 SQL Azure 資料表包含更多資料行,您會將對應的欄位新增至此類別。 例如,如果 DTO (資料傳輸物件) 有整數 Priority 數據行,則您可以加入此欄位及其 getter 和 setter 方法:
private Integer priority;
/**
* Returns the item priority
*/
public Integer getPriority() {
return mPriority;
}
/**
* Sets the item priority
*
* @param priority
* priority to set
*/
public final void setPriority(Integer priority) {
mPriority = priority;
}
若要瞭解如何在Mobile Apps 後端中建立其他數據表,請參閱 如何:定義數據表控制器 (.NET 後端)或使用 動態架構 定義數據表(Node.js後端)。
Azure Mobile Apps 後端數據表會定義五個特殊欄位,其中四個可供用戶端使用:
String id
:記錄的全域唯一標識符。 最佳做法是將標識碼設為 UUID 物件的 String 表示法。DateTimeOffset updatedAt
:上次更新的日期/時間。 updatedAt 字段是由伺服器所設定,不應該由用戶端程式代碼設定。DateTimeOffset createdAt
:建立物件的日期/時間。 createdAt 欄位是由伺服器所設定,而且絕對不應該由您的用戶端程式代碼設定。byte[] version
:通常以字串表示,版本也會由伺服器設定。boolean deleted
:表示記錄已刪除,但尚未清除。 請勿在deleted
類別中使用 做為 屬性。
id
是必填欄位。 updatedAt
欄位和version
欄位分別用於離線同步處理(用於累加同步處理和衝突解決)。 欄位 createdAt
是參考欄位,用戶端不會使用。 這些名稱是屬性的「跨線」名稱,且無法調整。 不過,您可以使用 gson 連結庫,在物件與「跨網路」名稱之間建立對應。 例如:
package com.example.zumoappname;
import com.microsoft.windowsazure.mobileservices.table.DateTimeOffset;
public class ToDoItem
{
@com.google.gson.annotations.SerializedName("id")
private String mId;
public String getId() { return mId; }
public final void setId(String id) { mId = id; }
@com.google.gson.annotations.SerializedName("complete")
private boolean mComplete;
public boolean isComplete() { return mComplete; }
public void setComplete(boolean complete) { mComplete = complete; }
@com.google.gson.annotations.SerializedName("text")
private String mText;
public String getText() { return mText; }
public final void setText(String text) { mText = text; }
@com.google.gson.annotations.SerializedName("createdAt")
private DateTimeOffset mCreatedAt;
public DateTimeOffset getCreatedAt() { return mCreatedAt; }
protected void setCreatedAt(DateTimeOffset createdAt) { mCreatedAt = createdAt; }
@com.google.gson.annotations.SerializedName("updatedAt")
private DateTimeOffset mUpdatedAt;
public DateTimeOffset getUpdatedAt() { return mUpdatedAt; }
protected void setUpdatedAt(DateTimeOffset updatedAt) { mUpdatedAt = updatedAt; }
@com.google.gson.annotations.SerializedName("version")
private String mVersion;
public String getVersion() { return mVersion; }
public final void setVersion(String version) { mVersion = version; }
public ToDoItem() { }
public ToDoItem(String id, String text) {
this.setId(id);
this.setText(text);
}
@Override
public boolean equals(Object o) {
return o instanceof ToDoItem && ((ToDoItem) o).mId == mId;
}
@Override
public String toString() {
return getText();
}
}
建立數據表參考
若要存取數據表,請先在MobileServiceClient上呼叫 getTable 方法,以建立MobileServiceTable 物件。 這個方法有兩個多載:
public class MobileServiceClient {
public <E> MobileServiceTable<E> getTable(Class<E> clazz);
public <E> MobileServiceTable<E> getTable(String name, Class<E> clazz);
}
在下列程序代碼中, mClient 是 MobileServiceClient 對象的參考。 第一個多載用於類別名稱和資料表名稱相同的地方,而 是快速入門中使用的多載:
MobileServiceTable<ToDoItem> mToDoTable = mClient.getTable(ToDoItem.class);
當數據表名稱與類別名稱不同時,會使用第二個多載:第一個參數是數據表名稱。
MobileServiceTable<ToDoItem> mToDoTable = mClient.getTable("ToDoItemBackup", ToDoItem.class);
查詢後端數據表
首先,取得數據表參考。 然後在數據表參考上執行查詢。 查詢是下列任一組合:
子句必須以上述順序呈現。
篩選結果
查詢的一般形式為:
List<MyDataTable> results = mDataTable
// More filters here
.execute() // Returns a ListenableFuture<E>
.get() // Converts the async into a sync result
上述範例會傳回所有結果(最多由伺服器設定的頁面大小上限)。 方法 .execute()
會在後端執行查詢。 查詢會 先轉換成 OData v3 查詢,再傳輸至 Mobile Apps 後端。 在收到時,Mobile Apps 後端會在 SQL Azure 實例上執行查詢之前,先將查詢轉換成 SQL 語句。 由於網路活動需要一些時間,此方法 .execute()
會傳 ListenableFuture<E>
回 。
篩選傳回的數據
下列查詢執行會從 ToDoItem 數據表傳回所有專案,其中 complete 等於 false。
List<ToDoItem> result = mToDoTable
.where()
.field("complete").eq(false)
.execute()
.get();
mToDoTable 是我們先前建立之行動服務數據表的參考。
使用 數據表參考上的 where 方法呼叫來定義篩選。 where 方法後面接著 field 方法,後面接著指定邏輯述詞的方法。 可能的述詞方法包括 eq (equals)、ne (不等於)、gt (大於)、ge (大於或等於)、lt (小於)、le (小於或等於)。 這些方法可讓您比較數位和字串字段與特定值。
您可以篩選日期。 下列方法可讓您比較日期的整個日期欄位或日期部分:年、月、日、小時、分鐘和秒。 下列範例會新增到期日等於 2013 的項目篩選。
List<ToDoItem> results = MToDoTable
.where()
.year("due").eq(2013)
.execute()
.get();
下列方法支援字串字段的複雜篩選:startsWith、endsWith、concat、subString、indexOf、replace、toLower、toUpper、trim 和 length。 下列範例會篩選文字數據行開頭為 「PRI0」 的資料表數據列。
List<ToDoItem> results = mToDoTable
.where()
.startsWith("text", "PRI0")
.execute()
.get();
數位欄位支援下列運算符方法:add、sub、mul、div、mod、floor、ceiling 和 round。 下列範例會篩選持續時間為偶數的數據表數據列。
List<ToDoItem> results = mToDoTable
.where()
.field("duration").mod(2).eq(0)
.execute()
.get();
您可以將述詞與下列邏輯方法結合:和,或,而非。 下列範例結合上述兩個範例。
List<ToDoItem> results = mToDoTable
.where()
.year("due").eq(2013).and().startsWith("text", "PRI0")
.execute()
.get();
群組和巢狀邏輯運算符:
List<ToDoItem> results = mToDoTable
.where()
.year("due").eq(2013)
.and(
startsWith("text", "PRI0")
.or()
.field("duration").gt(10)
)
.execute().get();
如需更詳細的討論和篩選範例,請參閱 探索 Android 用戶端查詢模型的豐富性。
排序傳回的數據
下列程式代碼會從以文字欄位遞增排序的 ToDoItems 數據表傳回所有專案。 mToDoTable 是您先前建立之後端數據表的參考:
List<ToDoItem> results = mToDoTable
.orderBy("text", QueryOrder.Ascending)
.execute()
.get();
orderBy 方法的第一個參數是等於要排序之域名的字串。 第二個參數會 使用 QueryOrder 列舉來指定是要排序遞增還是遞減。 如果您要使用 where 方法進行篩選,則必須在 orderBy 方法之前叫用 where 方法。
選取特定數據行
下列程式代碼說明如何從 ToDoItems 數據表傳回所有專案,但只會顯示完整和文字欄位。 mToDoTable 是我們先前建立之後端數據表的參考。
List<ToDoItemNarrow> result = mToDoTable
.select("complete", "text")
.execute()
.get();
select 函式的參數是您要傳回之資料表數據行的字串名稱。 select 方法必須遵循 where 和 orderBy 等方法。 其後面可以接著跳頁和頂端等分頁方法。
傳回頁面中的數據
數據一 律 會在頁面中傳回。 伺服器會設定傳回的記錄數目上限。 如果用戶端要求更多記錄,則伺服器會傳回記錄數目上限。 根據預設,伺服器上的頁面大小上限為50筆記錄。
第一個範例示範如何從數據表中選取前五個專案。 查詢會從 ToDoItems 的數據表傳回專案。 mToDoTable 是您先前建立之後端數據表的參考:
List<ToDoItem> result = mToDoTable
.top(5)
.execute()
.get();
以下是略過前五個專案的查詢,然後傳回接下來的五個專案:
List<ToDoItem> result = mToDoTable
.skip(5).top(5)
.execute()
.get();
如果您要取得資料表中的所有記錄,請實作程式代碼來逐一查看所有頁面:
List<MyDataModel> results = new ArrayList<>();
int nResults;
do {
int currentCount = results.size();
List<MyDataModel> pagedResults = mDataTable
.skip(currentCount).top(500)
.execute().get();
nResults = pagedResults.size();
if (nResults > 0) {
results.addAll(pagedResults);
}
} while (nResults > 0);
使用此方法的所有記錄要求,至少會建立兩個對Mobile Apps 後端的要求。
提示
選擇正確的頁面大小是在要求發生時,記憶體使用量之間的平衡,頻寬使用量和完全接收數據的延遲。 預設 [50 筆記錄] 適用於所有裝置。 如果您只對較大的記憶體裝置運作,請增加最多 500 個。 我們發現,增加超過500筆記錄的頁面大小會導致無法接受的延遲和大型記憶體問題。
如何:串連查詢方法
查詢後端數據表中使用的方法可以串連。 鏈結查詢方法可讓您選取已排序和分頁之篩選數據列的特定數據行。 您可以建立複雜的邏輯篩選。 每個查詢方法都會傳回 Query 物件。 若要結束一系列的方法並實際執行查詢,請呼叫 execute 方法。 例如:
List<ToDoItem> results = mToDoTable
.where()
.year("due").eq(2013)
.and(
startsWith("text", "PRI0").or().field("duration").gt(10)
)
.orderBy(duration, QueryOrder.Ascending)
.select("id", "complete", "text", "duration")
.skip(200).top(100)
.execute()
.get();
鏈結的查詢方法必須依下列方式排序:
- 篩選 (where) 方法。
- 排序 (orderBy) 方法。
- 選取範圍 (select) 方法。
- 分頁 (skip 和 top) 方法。
將數據系結至使用者介面
資料系結包含三個元件:
- 數據源
- 畫面配置
- 將兩者系結在一起的配接器。
在我們的範例程式代碼中,我們會將數據從Mobile Apps SQL Azure 資料表 ToDoItem 傳回數位。 此活動是資料應用程式的常見模式。 資料庫查詢通常會傳回用戶端在清單或陣列中取得的數據列集合。 在此範例中,陣列是數據源。 此程式代碼會指定螢幕配置,定義出現在裝置上之數據的檢視。 這兩者會與配接器系結在一起,在此程式代碼中是ArrayAdapter<ToDoItem>類別的延伸模組。
定義版面配置
配置是由數個 XML 程式代碼段所定義。 假設有現有的配置,下列程式代碼代表 我們想要填入伺服器數據的 ListView 。
<ListView
android:id="@+id/listViewToDo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/row_list_to_do" >
</ListView>
在上述程式代碼中 ,listitem 屬性會指定清單中個別數據列的版面配置標識碼。 此程式代碼會指定複選框及其相關聯的文字,並針對清單中的每個專案具現化一次。 此版面配置不會顯示 標識符 字段,而且更複雜的版面配置會在顯示中指定其他欄位。 此程式代碼位於 row_list_to_do.xml 檔案中。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkToDoItem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/checkbox_text" />
</LinearLayout>
定義配接器
由於檢視的數據源是 ToDoItem 的數位,因此我們會從 ArrayAdapter ToDoItem> 類別子類別化配接器。< 此子類別會使用 row_list_to_do 版面配置,為每個 ToDoItem 產生檢視。 在我們的程式代碼中,我們會定義下列類別,這是 ArrayAdapter<E> 類別的擴充功能:
public class ToDoItemAdapter extends ArrayAdapter<ToDoItem> {
}
覆寫配接器 getView 方法。 例如:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
final ToDoItem currentItem = getItem(position);
if (row == null) {
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
row = inflater.inflate(R.layout.row_list_to_do, parent, false);
}
row.setTag(currentItem);
final CheckBox checkBox = (CheckBox) row.findViewById(R.id.checkToDoItem);
checkBox.setText(currentItem.getText());
checkBox.setChecked(false);
checkBox.setEnabled(true);
checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
if (checkBox.isChecked()) {
checkBox.setEnabled(false);
if (mContext instanceof ToDoActivity) {
ToDoActivity activity = (ToDoActivity) mContext;
activity.checkItem(currentItem);
}
}
}
});
return row;
}
我們會在活動中建立此類別的實例,如下所示:
ToDoItemAdapter mAdapter;
mAdapter = new ToDoItemAdapter(this, R.layout.row_list_to_do);
ToDoItemAdapter 建構函式的第二個參數是配置參考。 我們現在可以將 ListView 具現化,並將配接器指派給 ListView。
ListView listViewToDo = (ListView) findViewById(R.id.listViewToDo);
listViewToDo.setAdapter(mAdapter);
使用配接器系結至UI
您現在已準備好使用數據系結。 下列程式代碼示範如何取得數據表中的專案,並以傳回的專案填滿本機配接器。
public void showAll(View view) {
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>(){
@Override
protected Void doInBackground(Void... params) {
try {
final List<ToDoItem> results = mToDoTable.execute().get();
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.clear();
for (ToDoItem item : results) {
mAdapter.add(item);
}
}
});
} catch (Exception exception) {
createAndShowDialog(exception, "Error");
}
return null;
}
};
runAsyncTask(task);
}
隨時修改 ToDoItem 資料表時呼叫配接器。 由於修改是以記錄為基礎來完成,因此您可以處理單一數據列,而不是集合。 當您插入專案時,請在配接器上呼叫 add 方法;刪除時,呼叫 remove 方法。
您可以在 Android 快速入門專案中找到完整的範例。
將數據插入後端
具現化 ToDoItem 類別的實例,並設定其屬性。
ToDoItem item = new ToDoItem();
item.text = "Test Program";
item.complete = false;
然後使用 insert() 插入物件:
ToDoItem entity = mToDoTable
.insert(item) // Returns a ListenableFuture<ToDoItem>
.get();
傳回的實體會比對插入後端數據表的數據,包括標識符和後端上設定的任何其他值(例如 createdAt
、 updatedAt
和 version
欄位)。
Mobile Apps 數據表需要名為 id 的主鍵數據行。此數據行必須是字串。 標識符數據行的預設值是 GUID。 您可以提供其他唯一值,例如電子郵件地址或用戶名稱。 當插入的記錄未提供字元串標識符值時,後端會產生新的 GUID。
字串識別碼值提供下列優點:
- 您可以產生標識碼,而不需往返資料庫。
- 記錄更容易從不同的數據表或資料庫合併。
- 標識碼值與應用程式的邏輯更緊密整合。
離線同步處理支援需要字串識別碼值。 一旦標識符儲存在後端資料庫中,就無法變更標識符。
更新行動應用程式中的數據
若要更新數據表中的數據,請將新的 對象傳遞至 update() 方法。
mToDoTable
.update(item) // Returns a ListenableFuture<ToDoItem>
.get();
在此範例中,item 是 ToDoItem 數據表中數據列的參考,其中已進行一些變更。 具有相同標識碼的數據列會更新。
刪除行動應用程式中的數據
下列程式代碼示範如何藉由指定資料對象來刪除資料表中的數據。
mToDoTable
.delete(item);
您也可以指定要 刪除之資料列的識別元 欄位來刪除專案。
String myRowId = "2FA404AB-E458-44CD-BC1B-3BC847EF0902";
mToDoTable
.delete(myRowId);
依標識碼查閱特定專案
使用 lookUp() 方法查閱具有特定識別符欄位的專案:
ToDoItem result = mToDoTable
.lookUp("0380BAFB-BCFF-443C-B7D5-30199F730335")
.get();
如何:使用不具類型的數據
不具類型的程式設計模型可讓您完全控制 JSON 串行化。 在某些情況下,您可能想要使用不具類型的程序設計模型。 例如,如果您的後端數據表包含許多數據行,而且您只需要參考數據行的子集。 具類型的模型會要求您定義數據類別中Mobile Apps 後端中定義的所有數據行。 大部分用於存取數據的 API 呼叫都類似於具型別的程式設計呼叫。 主要差異在於,在不具類型的模型中,您會在MobileServiceJsonTable物件上叫用方法,而不是MobileServiceTable物件。
建立不具類型的數據表實例
類似於具類型的模型,您一開始會取得數據表參考,但在此情況下,它是 MobileServicesJsonTable 物件。 在用戶端實例上呼叫 getTable 方法,以取得參考:
private MobileServiceJsonTable mJsonToDoTable;
//...
mJsonToDoTable = mClient.getTable("ToDoItem");
建立 MobileServiceJsonTable 的實例之後,它幾乎具有與具型別程序設計模型相同的 API。 在某些情況下,方法會採用不具類型的參數,而不是具類型的參數。
插入至不具類型的數據表
下列程式代碼示範如何執行插入。 第一個步驟是建立 JsonObject,這是 gson 連結庫的一部分。
JsonObject jsonItem = new JsonObject();
jsonItem.addProperty("text", "Wake up");
jsonItem.addProperty("complete", false);
然後,使用 insert() 將不具類型的物件插入數據表中。
JsonObject insertedItem = mJsonToDoTable
.insert(jsonItem)
.get();
如果您需要取得插入對象的標識碼,請使用 getAsJsonPrimitive() 方法。
String id = insertedItem.getAsJsonPrimitive("id").getAsString();
從不具類型的數據表中刪除
下列程式代碼示範如何刪除實例,在此案例中,與先前插入範例中建立的 JsonObject 實例相同。 程序代碼與具型別大小寫相同,但 方法會有不同的簽章,因為它參考 JsonObject。
mToDoTable
.delete(insertedItem);
您也可以使用實體識別碼直接刪除實例:
mToDoTable.delete(ID);
從不具類型的數據表傳回所有數據列
下列程式代碼示範如何擷取整個數據表。 由於您使用的是 JSON 數據表,因此您可以選擇性地只擷取部分數據表的數據行。
public void showAllUntyped(View view) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
final JsonElement result = mJsonToDoTable.execute().get();
final JsonArray results = result.getAsJsonArray();
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.clear();
for (JsonElement item : results) {
String ID = item.getAsJsonObject().getAsJsonPrimitive("id").getAsString();
String mText = item.getAsJsonObject().getAsJsonPrimitive("text").getAsString();
Boolean mComplete = item.getAsJsonObject().getAsJsonPrimitive("complete").getAsBoolean();
ToDoItem mToDoItem = new ToDoItem();
mToDoItem.setId(ID);
mToDoItem.setText(mText);
mToDoItem.setComplete(mComplete);
mAdapter.add(mToDoItem);
}
}
});
} catch (Exception exception) {
createAndShowDialog(exception, "Error");
}
return null;
}
}.execute();
}
適用於具型別模型的篩選、篩選和分頁方法集,適用於不具型別的模型。
實作離線同步處理
Azure Mobile Apps 用戶端 SDK 也會使用 SQLite 資料庫在本機儲存伺服器數據的複本,來實作數據的離線同步處理。 在離線數據表上執行的作業不需要行動裝置連線才能運作。 離線同步處理可協助復原和效能,而犧牲了更複雜的衝突解決邏輯。 Azure Mobile Apps 用戶端 SDK 會實作下列功能:
- 累加同步:只會下載更新和新的記錄,以節省頻寬和記憶體耗用量。
- 開放式並行存取:假設作業會成功。 衝突解決會延後,直到伺服器上執行更新為止。
- 衝突解決:SDK 會在伺服器上進行衝突的變更時偵測,並提供勾點來警示使用者。
- 虛刪除:已刪除的記錄會標示為已刪除,允許其他裝置更新其離線快取。
初始化離線同步處理
每個離線數據表都必須在離線快取中定義,才能使用。 一般而言,數據表定義會在建立客戶端之後立即完成:
AsyncTask<Void, Void, Void> initializeStore(MobileServiceClient mClient)
throws MobileServiceLocalStoreException, ExecutionException, InterruptedException
{
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override
protected void doInBackground(Void... params) {
try {
MobileServiceSyncContext syncContext = mClient.getSyncContext();
if (syncContext.isInitialized()) {
return null;
}
SQLiteLocalStore localStore = new SQLiteLocalStore(mClient.getContext(), "offlineStore", null, 1);
// Create a table definition. As a best practice, store this with the model definition and return it via
// a static method
Map<String, ColumnDataType> toDoItemDefinition = new HashMap<String, ColumnDataType>();
toDoItemDefinition.put("id", ColumnDataType.String);
toDoItemDefinition.put("complete", ColumnDataType.Boolean);
toDoItemDefinition.put("text", ColumnDataType.String);
toDoItemDefinition.put("version", ColumnDataType.String);
toDoItemDefinition.put("updatedAt", ColumnDataType.DateTimeOffset);
// Now define the table in the local store
localStore.defineTable("ToDoItem", toDoItemDefinition);
// Specify a sync handler for conflict resolution
SimpleSyncHandler handler = new SimpleSyncHandler();
// Initialize the local store
syncContext.initialize(localStore, handler).get();
} catch (final Exception e) {
createAndShowDialogFromTask(e, "Error");
}
return null;
}
};
return runAsyncTask(task);
}
取得離線快取數據表的參考
針對線上資料表,您可以使用 .getTable()
。 針對離線資料表,請使用 .getSyncTable()
:
MobileServiceSyncTable<ToDoItem> mToDoTable = mClient.getSyncTable("ToDoItem", ToDoItem.class);
在線數據表的所有可用方法(包括篩選、排序、分頁、插入數據、更新數據及刪除數據)在在線和離線數據表上同樣運作良好。
同步處理本機離線快取
同步處理位於您應用程式的控制範圍內。 以下是範例同步處理方法:
private AsyncTask<Void, Void, Void> sync(MobileServiceClient mClient) {
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>(){
@Override
protected Void doInBackground(Void... params) {
try {
MobileServiceSyncContext syncContext = mClient.getSyncContext();
syncContext.push().get();
mToDoTable.pull(null, "todoitem").get();
} catch (final Exception e) {
createAndShowDialogFromTask(e, "Error");
}
return null;
}
};
return runAsyncTask(task);
}
如果查詢名稱提供給 .pull(query, queryname)
方法,則累加同步處理只會傳回自上次成功完成提取后已建立或變更的記錄。
在離線同步處理期間處理衝突
如果在工作期間 .push()
發生衝突, MobileServiceConflictException
則會擲回 。 伺服器發出的專案內嵌在例外狀況中,而且可以在例外狀況上擷取 .getItem()
。 在 MobileServiceSyncContext 物件上呼叫下列專案,以調整推送:
.cancelAndDiscardItem()
.cancelAndUpdateItem()
.updateOperationAndItem()
一旦所有衝突都標示為您想要,請再次呼叫 .push()
以解決所有衝突。
呼叫自定義 API
自定義 API 可讓您定義自訂端點,以公開不會對應至插入、更新、刪除或讀取作業的伺服器功能。 藉由使用自定義 API,您可以更充分掌控傳訊,包括讀取和設定 HTTP 訊息標頭,以及定義 JSON 以外的訊息本文格式。
從 Android 用戶端呼叫 invokeApi 方法,以呼叫自定義 API 端點。 下列範例示範如何呼叫名為 completeAll 的 API 端點,這會傳回名為 MarkAllResult 的集合類別。
public void completeItem(View view) {
ListenableFuture<MarkAllResult> result = mClient.invokeApi("completeAll", MarkAllResult.class);
Futures.addCallback(result, new FutureCallback<MarkAllResult>() {
@Override
public void onFailure(Throwable exc) {
createAndShowDialog((Exception) exc, "Error");
}
@Override
public void onSuccess(MarkAllResult result) {
createAndShowDialog(result.getCount() + " item(s) marked as complete.", "Completed Items");
refreshItemsFromTable();
}
});
}
在 用戶端上呼叫 invokeApi 方法,它會將 POST 要求傳送至新的自定義 API。 自定義 API 傳回的結果會顯示在訊息對話框中,如同任何錯誤一樣。 其他版本的 invokeApi 可讓您選擇性地在要求主體中傳送物件、指定 HTTP 方法,以及使用要求傳送查詢參數。 也會提供不具類型的invokeApi版本。
將驗證新增至您的 Window 應用程式
教學課程已詳細說明如何新增這些功能。
App Service 支援 使用各種外部識別提供者來驗證應用程式使用者 :Facebook、Google、Microsoft 帳戶、Twitter 和 Azure Active Directory。 您可以設定資料表的許可權,將特定作業的存取限制為僅限已驗證的使用者。 您也可以使用已驗證使用者的身分識別,在後端實作授權規則。
支援兩個 驗證流程:伺服器 流程和 用戶端 流程。 伺服器流程提供最簡單的驗證體驗,因為它依賴識別提供者 Web 介面。 實作伺服器流程驗證不需要額外的 SDK。 伺服器流程驗證不會提供對行動裝置的深入整合,而且只建議用於概念證明案例。
用戶端流程可讓您更深入地整合裝置特定功能,例如單一登錄,因為它依賴身分識別提供者所提供的 SDK。 例如,您可以將 Facebook SDK 整合到行動應用程式。 行動用戶端會交換至 Facebook 應用程式,並在交換回行動應用程式之前確認您的登入。
在應用程式中啟用驗證需要四個步驟:
- 向識別提供者註冊您的應用程式以進行驗證。
- 設定 App Service 後端。
- 僅限 App Service 後端上的已驗證使用者限制數據表許可權。
- 將驗證碼新增至您的應用程式。
您可以設定資料表的許可權,將特定作業的存取限制為僅限已驗證的使用者。 您也可以使用已驗證使用者的 SID 來修改要求。 如需詳細資訊,請參閱 開始使用驗證 和伺服器 SDK HOWTO 檔。
驗證:伺服器流程
下列程式代碼會使用Google提供者啟動伺服器流程登入程式。 由於Google提供者的安全性需求,因此需要額外的設定:
MobileServiceUser user = mClient.login(MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE);
此外,將下列方法新增至主要 Activity 類別:
// You can choose any unique number here to differentiate auth providers from each other. Note this is the same code at login() and onActivityResult().
public static final int GOOGLE_LOGIN_REQUEST_CODE = 1;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// When request completes
if (resultCode == RESULT_OK) {
// Check the request code matches the one we send in the login request
if (requestCode == GOOGLE_LOGIN_REQUEST_CODE) {
MobileServiceActivityResult result = mClient.onActivityResult(data);
if (result.isLoggedIn()) {
// login succeeded
createAndShowDialog(String.format("You are now logged in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
createTable();
} else {
// login failed, check the error message
String errorMessage = result.getErrorMessage();
createAndShowDialog(errorMessage, "Error");
}
}
}
}
GOOGLE_LOGIN_REQUEST_CODE
在主要 Activity 中定義的 會用於 login()
方法和方法內onActivityResult()
。 您可以選擇任何唯一的數位,只要在 方法和方法onActivityResult()
中使用login()
相同的數位。 如果您將用戶端程式代碼抽象化為服務配接器(如先前所示),您應該在服務配接器上呼叫適當的方法。
您也需要設定自訂索引標籤的專案。 首先指定 redirect-URL。 將下列代碼段新增至 AndroidManifest.xml
:
<activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="{url_scheme_of_your_app}" android:host="easyauth.callback"/>
</intent-filter>
</activity>
將 redirectUriScheme 新增至 build.gradle
應用程式的檔案:
android {
buildTypes {
release {
// … …
manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
}
debug {
// … …
manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
}
}
}
最後,將 新增 com.android.support:customtabs:28.0.0
至 檔案中的 build.gradle
相依性清單:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.code.gson:gson:2.3'
implementation 'com.google.guava:guava:18.0'
implementation 'com.android.support:customtabs:28.0.0'
implementation 'com.squareup.okhttp:okhttp:2.5.0'
implementation 'com.microsoft.azure:azure-mobile-android:3.4.0@aar'
implementation 'com.microsoft.azure:azure-notifications-handler:1.0.1@jar'
}
使用 getUserId 方法,從 MobileServiceUser 取得登入使用者的識別碼。 如需如何使用 Futures 呼叫異步登入 API 的範例,請參閱 開始使用驗證。
警告
提及的 URL 配置會區分大小寫。 請確定符合案例 {url_scheme_of_you_app}
的所有專案。
快取驗證令牌
快取驗證令牌會要求您在裝置本機儲存使用者標識碼和驗證令牌。 下次應用程式啟動時,您會檢查快取,如果存在這些值,您可以略過登入程式,並使用此資料將用戶端解除凍結。 不過,此數據很機密,而且應該儲存為安全加密,以防手機遭竊。 您可以在快取驗證令牌一節中看到如何快取驗證令牌的完整範例。
當您嘗試使用過期的令牌時,您會收到 401 未經授權的 回應。 您可以使用篩選來處理驗證錯誤。 篩選對 App Service 後端的攔截要求。 篩選程式代碼會測試 401 的回應、觸發登入程式,然後繼續產生 401 的要求。
使用重新整理令牌
Azure App 服務 驗證和授權所傳回的令牌已定義存留期為1小時。 在此期間之後,您必須重新驗證使用者。 如果您使用透過用戶端流程驗證收到的長期令牌,則可以使用相同的令牌重新驗證 Azure App 服務 驗證和授權。 新的存留期會產生另一個 Azure App 服務 令牌。
您也可以註冊提供者以使用重新整理令牌。 重新整理令牌不一定可供使用。 需要額外的設定:
針對 Azure Active Directory,設定 Azure Active Directory 應用程式的客戶端密碼。 設定 Azure Active Directory 驗證時,請在 Azure App 服務 中指定客戶端密碼。 呼叫
.login()
時,以參數的形式傳遞response_type=code id_token
:HashMap<String, String> parameters = new HashMap<String, String>(); parameters.put("response_type", "code id_token"); MobileServiceUser user = mClient.login MobileServiceAuthenticationProvider.AzureActiveDirectory, "{url_scheme_of_your_app}", AAD_LOGIN_REQUEST_CODE, parameters);
針對 Google,傳遞 作為
access_type=offline
參數:HashMap<String, String> parameters = new HashMap<String, String>(); parameters.put("access_type", "offline"); MobileServiceUser user = mClient.login MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE, parameters);
針對 [Microsoft帳戶],選取
wl.offline_access
範圍。
若要重新整理權杖,請呼叫 .refreshUser()
:
MobileServiceUser user = mClient
.refreshUser() // async - returns a ListenableFuture<MobileServiceUser>
.get();
最佳做法是建立篩選,以偵測來自伺服器的 401 回應,並嘗試重新整理使用者令牌。
使用用戶端流程驗證登入
使用用戶端流程驗證登入的一般程式如下:
將 Azure App 服務 驗證和授權設定為伺服器流程驗證。
整合驗證提供者 SDK 以進行驗證,以產生存取令牌。
.login()
呼叫 方法,如下所示 (result
應該是AuthenticationResult
:JSONObject payload = new JSONObject(); payload.put("access_token", result.getAccessToken()); ListenableFuture<MobileServiceUser> mLogin = mClient.login("{provider}", payload.toString()); Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() { @Override public void onFailure(Throwable exc) { exc.printStackTrace(); } @Override public void onSuccess(MobileServiceUser user) { Log.d(TAG, "Login Complete"); } });
請參閱下一節的完整程式碼範例。
將 onSuccess()
方法取代為您想要在成功登入上使用的任何程序代碼。 字串是 {provider}
有效的提供者: aad (Azure Active Directory)、 facebook、 google、 microsoftaccount 或 twitter。 如果您已實作自定義驗證,您也可以使用自訂驗證提供者標籤。
使用 Active Directory 驗證連結庫驗證使用者 (ADAL)
您可以使用 Active Directory 驗證連結庫 (ADAL) 來使用 Azure Active Directory 將使用者登入您的應用程式。 使用用戶端流程登入通常較適合使用 loginAsync()
方法,因為它提供更原生的UX風格,並允許進行其他自定義。
遵循 如何設定 App Service for Active Directory 登入 教學課程,設定 AAD 登入的行動應用程式後端。 請務必完成註冊原生用戶端應用程式的選擇性步驟。
變更 build.gradle 檔案以包含下列定義來安裝 ADAL:
repositories { mavenCentral() flatDir { dirs 'libs' } maven { url "YourLocalMavenRepoPath\\.m2\\repository" } } packagingOptions { exclude 'META-INF/MSFTSIG.RSA' exclude 'META-INF/MSFTSIG.SF' } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation('com.microsoft.aad:adal:1.16.1') { exclude group: 'com.android.support' } // Recent version is 1.16.1 implementation 'com.android.support:support-v4:28.0.0' }
將下列程式代碼新增至您的應用程式,進行下列取代:
- 將 INSERT-AUTHORITY-HERE 取代為您佈建應用程式的租用戶名稱。 格式應為 https://login.microsoftonline.com/contoso.onmicrosoft.com。
- 以行動應用程式後端的用戶端標識碼取代 INSERT-RESOURCE-ID-HERE 。 您可以從入口網站中 Azure Active Directory 設定下的 [進階] 索引標籤取得用戶端識別碼。
- 將 INSERT-CLIENT-ID-HERE 取代為您從原生用戶端應用程式複製的用戶端識別碼。
- 使用 HTTPS 配置,將 INSERT-REDIRECT-URI-HERE 取代為您網站的 /.auth/login/done 端點。 此值應該類似 https://contoso.azurewebsites.net/.auth/login/done。
private AuthenticationContext mContext;
private void authenticate() {
String authority = "INSERT-AUTHORITY-HERE";
String resourceId = "INSERT-RESOURCE-ID-HERE";
String clientId = "INSERT-CLIENT-ID-HERE";
String redirectUri = "INSERT-REDIRECT-URI-HERE";
try {
mContext = new AuthenticationContext(this, authority, true);
mContext.acquireToken(this, resourceId, clientId, redirectUri, PromptBehavior.Auto, "", callback);
} catch (Exception exc) {
exc.printStackTrace();
}
}
private AuthenticationCallback<AuthenticationResult> callback = new AuthenticationCallback<AuthenticationResult>() {
@Override
public void onError(Exception exc) {
if (exc instanceof AuthenticationException) {
Log.d(TAG, "Cancelled");
} else {
Log.d(TAG, "Authentication error:" + exc.getMessage());
}
}
@Override
public void onSuccess(AuthenticationResult result) {
if (result == null || result.getAccessToken() == null
|| result.getAccessToken().isEmpty()) {
Log.d(TAG, "Token is empty");
} else {
try {
JSONObject payload = new JSONObject();
payload.put("access_token", result.getAccessToken());
ListenableFuture<MobileServiceUser> mLogin = mClient.login("aad", payload.toString());
Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
@Override
public void onFailure(Throwable exc) {
exc.printStackTrace();
}
@Override
public void onSuccess(MobileServiceUser user) {
Log.d(TAG, "Login Complete");
}
});
}
catch (Exception exc){
Log.d(TAG, "Authentication error:" + exc.getMessage());
}
}
}
};
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mContext != null) {
mContext.onActivityResult(requestCode, resultCode, data);
}
}
調整客戶端-伺服器通訊
用戶端連線通常是使用 Android SDK 所提供的基礎 HTTP 連結庫的基本 HTTP 連線。 有數個原因導致您想要變更:
- 您想要使用替代 HTTP 連結庫來調整逾時。
- 您想要提供進度列。
- 您要新增自訂標頭以支援 API 管理功能。
- 您想要攔截失敗的回應,以便實作重新驗證。
- 您想要將後端要求記錄至分析服務。
使用替代 HTTP 連結庫
.setAndroidHttpClientFactory()
建立客戶端參考之後,立即呼叫 方法。 例如,若要將連線逾時設定為 60 秒(而不是預設 10 秒):
mClient = new MobileServiceClient("https://myappname.azurewebsites.net");
mClient.setAndroidHttpClientFactory(new OkHttpClientFactory() {
@Override
public OkHttpClient createOkHttpClient() {
OkHttpClient client = new OkHttpClient();
client.setReadTimeout(60, TimeUnit.SECONDS);
client.setWriteTimeout(60, TimeUnit.SECONDS);
return client;
}
});
實作進度篩選
您可以藉由實作 來實 ServiceFilter
作每個要求的攔截。 例如,下列會更新預先建立的進度列:
private class ProgressFilter implements ServiceFilter {
@Override
public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback next) {
final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mProgressBar != null) mProgressBar.setVisibility(ProgressBar.VISIBLE);
}
});
ListenableFuture<ServiceFilterResponse> future = next.onNext(request);
Futures.addCallback(future, new FutureCallback<ServiceFilterResponse>() {
@Override
public void onFailure(Throwable e) {
resultFuture.setException(e);
}
@Override
public void onSuccess(ServiceFilterResponse response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (mProgressBar != null)
mProgressBar.setVisibility(ProgressBar.GONE);
}
});
resultFuture.set(response);
}
});
return resultFuture;
}
}
您可以將此篩選附加至用戶端,如下所示:
mClient = new MobileServiceClient(applicationUrl).withFilter(new ProgressFilter());
自定義要求標頭
使用下列命令 ServiceFilter
,並以 與 相同的方式 ProgressFilter
附加篩選:
private class CustomHeaderFilter implements ServiceFilter {
@Override
public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback next) {
runOnUiThread(new Runnable() {
@Override
public void run() {
request.addHeader("X-APIM-Router", "mobileBackend");
}
});
SettableFuture<ServiceFilterResponse> result = SettableFuture.create();
try {
ServiceFilterResponse response = next.onNext(request).get();
result.set(response);
} catch (Exception exc) {
result.setException(exc);
}
}
}
設定自動串行化
您可以使用 gson API 指定套用至每個資料列的轉換策略。 Android 用戶端連結庫會在將數據傳送至 Azure App 服務 之前,在幕後使用 gson 將 Java 物件串行化為 JSON 數據。 下列程式代碼會使用 setFieldNamingStrategy() 方法來設定策略。 本範例會針對每個功能變數名稱刪除初始字元 (“m”),然後將下一個字元小寫。 例如,它會將 「mId」 轉換成 「id」。實作轉換策略,以減少大部分欄位的註釋需求 SerializedName()
。
FieldNamingStrategy namingStrategy = new FieldNamingStrategy() {
public String translateName(File field) {
String name = field.getName();
return Character.toLowerCase(name.charAt(1)) + name.substring(2);
}
}
client.setGsonBuilder(
MobileServiceClient
.createMobileServiceGsonBuilder()
.setFieldNamingStrategy(namingStrategy)
);
必須先執行此程序代碼,才能使用 MobileServiceClient建立行動客戶端參考。