Lưu ý: Bạn nên dùng WorkManager là giải pháp được đề xuất cho hầu hết các trường hợp sử dụng xử lý ở chế độ nền. Vui lòng tham khảo hướng dẫn xử lý nền để tìm hiểu giải pháp nào phù hợp nhất với bạn.
Trong các bài học trước của lớp này, bạn đã tìm hiểu cách tạo thành phần bộ điều hợp đồng bộ hoá đóng gói mã chuyển dữ liệu và cách thêm các thành phần bổ sung cho phép bạn cắm bộ điều hợp đồng bộ hoá vào hệ thống. Giờ đây, bạn đã có mọi thông tin cần thiết để cài đặt một ứng dụng bao gồm bộ điều hợp đồng bộ hoá, nhưng không có mã nào trong số mã bạn thấy thực sự chạy bộ điều hợp đồng bộ hoá.
Bạn nên cố gắng chạy bộ điều hợp đồng bộ hoá dựa trên lịch biểu hoặc dưới dạng kết quả gián tiếp của một số sự kiện. Ví dụ: bạn có thể muốn bộ điều hợp đồng bộ hoá chạy theo lịch biểu định kỳ, sau một khoảng thời gian hoặc thời điểm cụ thể trong ngày. Bạn cũng có thể muốn chạy quá trình đồng bộ hoá của mình bộ chuyển đổi khi có thay đổi đối với dữ liệu được lưu trữ trên thiết bị. Bạn nên tránh chạy bộ điều hợp đồng bộ hoá dưới dạng kết quả trực tiếp của hành động của người dùng, bởi vì khi thực hiện việc này, bạn sẽ không có tận dụng khả năng lên lịch của khung bộ điều hợp đồng bộ hoá. Ví dụ: bạn nên tránh cung cấp nút làm mới trong giao diện người dùng.
Bạn có các tuỳ chọn sau để chạy bộ điều hợp đồng bộ hoá:
- Khi dữ liệu máy chủ thay đổi
- Chạy bộ điều hợp đồng bộ hoá để phản hồi thông báo từ máy chủ, cho biết rằng nền tảng này đã thay đổi. Tuỳ chọn này cho phép bạn làm mới dữ liệu từ máy chủ đến thiết bị mà không làm giảm hiệu suất hay làm lãng phí thời lượng pin bằng cách thăm dò máy chủ.
- Khi dữ liệu thiết bị thay đổi
- Chạy bộ điều hợp đồng bộ hoá khi dữ liệu thay đổi trên thiết bị. Lựa chọn này cho phép bạn gửi sửa đổi dữ liệu từ thiết bị đến máy chủ và đặc biệt hữu ích nếu bạn cần đảm bảo rằng máy chủ luôn có dữ liệu mới nhất của thiết bị. Tuỳ chọn này dễ hiểu nếu bạn thực sự lưu trữ dữ liệu trong trình cung cấp nội dung của mình. Nếu bạn đang dùng mã giả lập nhà cung cấp nội dung, nhưng việc phát hiện các thay đổi về dữ liệu có thể khó phát hiện hơn.
- Định kỳ
- Chạy bộ điều hợp đồng bộ hoá sau khi hết khoảng thời gian bạn chọn hoặc chạy vào một khoảng thời gian nhất định mỗi ngày.
- Theo yêu cầu
- Chạy bộ điều hợp đồng bộ hoá để phản hồi hành động của người dùng. Tuy nhiên, để cung cấp cho người dùng tốt nhất bạn nên chủ yếu dựa vào một trong số các tùy chọn tự động hơn. Bằng cách sử dụng tự động, bạn tiết kiệm pin và tài nguyên mạng.
Phần còn lại của bài học này sẽ mô tả chi tiết hơn về từng tuỳ chọn.
Chạy bộ điều hợp đồng bộ hoá khi dữ liệu máy chủ thay đổi
Nếu ứng dụng của bạn chuyển dữ liệu từ một máy chủ và dữ liệu máy chủ thay đổi thường xuyên, bạn có thể sử dụng
bộ điều hợp đồng bộ hoá để tải xuống nhằm phản hồi thay đổi về dữ liệu. Để chạy bộ điều hợp đồng bộ hoá, hãy
thì máy chủ sẽ gửi một thông báo đặc biệt đến BroadcastReceiver
trong ứng dụng của bạn.
Để phản hồi thông báo này, hãy gọi ContentResolver.requestSync()
để báo hiệu cho khung bộ điều hợp đồng bộ hoá chạy
bộ điều hợp đồng bộ hoá.
Nhắn tin qua đám mây của Google (GCM) cung cấp cả
máy chủ và thiết bị bạn cần để hệ thống nhắn tin này hoạt động. Sử dụng GCM để kích hoạt
quá trình chuyển sẽ đáng tin cậy và hiệu quả hơn so với máy chủ thăm dò trạng thái. Trong khi thăm dò ý kiến
yêu cầu một Service
luôn hoạt động, GCM sử dụng
BroadcastReceiver
được kích hoạt khi có thư đến. Trong khi thăm dò ý kiến
định kỳ sử dụng nguồn pin ngay cả khi không có bản cập nhật, GCM chỉ gửi
khi cần.
Lưu ý: Nếu bạn sử dụng GCM để kích hoạt bộ điều hợp đồng bộ hóa qua thông báo truyền tin tới tất cả trên các thiết bị cài đặt ứng dụng của bạn, hãy nhớ rằng họ nhận được tin nhắn của bạn tại gần như cùng lúc. Trường hợp này có thể khiến nhiều thực thể của bộ điều hợp đồng bộ hoá chạy cùng một lúc, gây ra tình trạng quá tải cho máy chủ và mạng. Để tránh trường hợp này khi phát sóng cho tất cả thiết bị, bạn nên cân nhắc trì hoãn thời gian bắt đầu bộ điều hợp đồng bộ hoá trong một khoảng thời gian dành riêng cho mỗi thiết bị.
Đoạn mã sau đây hướng dẫn bạn cách chạy
requestSync()
để phản hồi một
tin nhắn đến GCM:
Kotlin
... // Constants // Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Account type const val ACCOUNT_TYPE = "com.example.android.datasync" // Account const val ACCOUNT = "default_account" // Incoming Intent key for extended data const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST" ... class GcmBroadcastReceiver : BroadcastReceiver() { ... override fun onReceive(context: Context, intent: Intent) { // Get a GCM object instance val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context) // Get the type of GCM message val messageType: String? = gcm.getMessageType(intent) /* * Test the message type and examine the message contents. * Since GCM is a general-purpose messaging system, you * may receive normal messages that don't require a sync * adapter run. * The following code tests for a a boolean flag indicating * that the message is requesting a transfer from the device. */ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) { /* * Signal the framework to run your sync adapter. Assume that * app initialization has already created the account. */ ContentResolver.requestSync(mAccount, AUTHORITY, null) ... } ... } ... }
Java
public class GcmBroadcastReceiver extends BroadcastReceiver { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Incoming Intent key for extended data public static final String KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"; ... @Override public void onReceive(Context context, Intent intent) { // Get a GCM object instance GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context); // Get the type of GCM message String messageType = gcm.getMessageType(intent); /* * Test the message type and examine the message contents. * Since GCM is a general-purpose messaging system, you * may receive normal messages that don't require a sync * adapter run. * The following code tests for a a boolean flag indicating * that the message is requesting a transfer from the device. */ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType) && intent.getBooleanExtra(KEY_SYNC_REQUEST)) { /* * Signal the framework to run your sync adapter. Assume that * app initialization has already created the account. */ ContentResolver.requestSync(mAccount, AUTHORITY, null); ... } ... } ... }
Chạy bộ điều hợp đồng bộ hoá khi dữ liệu của trình cung cấp nội dung thay đổi
Nếu ứng dụng của bạn thu thập dữ liệu thông qua một nhà cung cấp nội dung và bạn muốn cập nhật máy chủ bất cứ khi nào
bạn cập nhật nhà cung cấp, bạn có thể thiết lập ứng dụng để tự động chạy bộ điều hợp đồng bộ hoá. Việc cần làm
thao tác này, bạn đăng ký trình quan sát cho trình cung cấp nội dung. Khi dữ liệu trong trình cung cấp nội dung của bạn
thì khung trình cung cấp nội dung sẽ gọi trình quan sát. Trong trình quan sát, gọi
requestSync()
để yêu cầu khung chạy
bộ điều hợp đồng bộ hoá của bạn.
Lưu ý: Nếu đang sử dụng một nhà cung cấp nội dung giả lập, thì bạn không có dữ liệu nào trong
trình cung cấp nội dung và onChange()
là
chưa bao giờ được gọi. Trong trường hợp này, bạn phải cung cấp cơ chế riêng để phát hiện các thay đổi cho
dữ liệu trên thiết bị của bạn. Cơ chế này cũng chịu trách nhiệm gọi
requestSync()
khi dữ liệu thay đổi.
Để tạo trình quan sát cho trình cung cấp nội dung, hãy mở rộng lớp này
ContentObserver
rồi triển khai cả hai dạng
onChange()
. Trong
onChange()
, gọi
requestSync()
để khởi động bộ điều hợp đồng bộ hoá.
Để đăng ký trình quan sát, hãy truyền đối tượng này dưới dạng một đối số trong lệnh gọi đến
registerContentObserver()
. Trong
lệnh gọi này, bạn cũng phải chuyển vào một URI nội dung cho dữ liệu mà bạn muốn xem. Nội dung
khung của nhà cung cấp so sánh URI theo dõi này với các URI nội dung được truyền dưới dạng đối số để
ContentResolver
phương thức sửa đổi nhà cung cấp của bạn, chẳng hạn như
ContentResolver.insert()
Nếu có kết quả trùng khớp,
cách triển khai ContentObserver.onChange()
sẽ được gọi.
Đoạn mã sau đây cho bạn biết cách xác định ContentObserver
gọi requestSync()
khi một bảng
các thay đổi:
Kotlin
// Constants // Content provider scheme const val SCHEME = "content://" // Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Path for the content provider table const val TABLE_PATH = "data_table" ... class MainActivity : FragmentActivity() { ... // A content URI for the content provider's data table private lateinit var uri: Uri // A content resolver for accessing the provider private lateinit var mResolver: ContentResolver ... inner class TableObserver(...) : ContentObserver(...) { /* * Define a method that's called when data in the * observed content provider changes. * This method signature is provided for compatibility with * older platforms. */ override fun onChange(selfChange: Boolean) { /* * Invoke the method signature available as of * Android platform version 4.1, with a null URI. */ onChange(selfChange, null) } /* * Define a method that's called when data in the * observed content provider changes. */ override fun onChange(selfChange: Boolean, changeUri: Uri?) { /* * Ask the framework to run your sync adapter. * To maintain backward compatibility, assume that * changeUri is null. */ ContentResolver.requestSync(account, AUTHORITY, null) } ... } ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Get the content resolver object for your app mResolver = contentResolver // Construct a URI that points to the content provider data table uri = Uri.Builder() .scheme(SCHEME) .authority(AUTHORITY) .path(TABLE_PATH) .build() /* * Create a content observer object. * Its code does not mutate the provider, so set * selfChange to "false" */ val observer = TableObserver(false) /* * Register the observer for the data table. The table's path * and any of its subpaths trigger the observer. */ mResolver.registerContentObserver(uri, true, observer) ... } ... }
Java
public class MainActivity extends FragmentActivity { ... // Constants // Content provider scheme public static final String SCHEME = "content://"; // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Path for the content provider table public static final String TABLE_PATH = "data_table"; // Account public static final String ACCOUNT = "default_account"; // Global variables // A content URI for the content provider's data table Uri uri; // A content resolver for accessing the provider ContentResolver mResolver; ... public class TableObserver extends ContentObserver { /* * Define a method that's called when data in the * observed content provider changes. * This method signature is provided for compatibility with * older platforms. */ @Override public void onChange(boolean selfChange) { /* * Invoke the method signature available as of * Android platform version 4.1, with a null URI. */ onChange(selfChange, null); } /* * Define a method that's called when data in the * observed content provider changes. */ @Override public void onChange(boolean selfChange, Uri changeUri) { /* * Ask the framework to run your sync adapter. * To maintain backward compatibility, assume that * changeUri is null. */ ContentResolver.requestSync(mAccount, AUTHORITY, null); } ... } ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver object for your app mResolver = getContentResolver(); // Construct a URI that points to the content provider data table uri = new Uri.Builder() .scheme(SCHEME) .authority(AUTHORITY) .path(TABLE_PATH) .build(); /* * Create a content observer object. * Its code does not mutate the provider, so set * selfChange to "false" */ TableObserver observer = new TableObserver(false); /* * Register the observer for the data table. The table's path * and any of its subpaths trigger the observer. */ mResolver.registerContentObserver(uri, true, observer); ... } ... }
Chạy bộ điều hợp đồng bộ hoá định kỳ
Bạn có thể chạy bộ điều hợp đồng bộ hoá định kỳ bằng cách đặt khoảng thời gian chờ giữa các lần chạy, hoặc bằng cách chạy ứng dụng vào những thời điểm nhất định trong ngày hoặc cả hai. Chạy bộ điều hợp đồng bộ hoá định kỳ giúp bạn có được khoảng thời gian cập nhật gần như khớp với máy chủ của bạn.
Tương tự, bạn có thể tải dữ liệu lên từ thiết bị khi máy chủ của bạn tương đối ở trạng thái rảnh, bằng cách đang lên lịch để bộ điều hợp đồng bộ hoá chạy vào ban đêm. Hầu hết người dùng vẫn bật nguồn và cắm nguồn vào ban đêm, nên thời gian này thường rảnh. Ngoài ra, thiết bị hiện không thực hiện các tác vụ khác tại cùng lúc với bộ điều hợp đồng bộ hoá. Tuy nhiên, nếu áp dụng phương pháp này, bạn cần đảm bảo rằng mỗi thiết bị sẽ kích hoạt quá trình chuyển dữ liệu tại một thời điểm hơi khác nhau. Nếu tất cả thiết bị đều chạy bộ điều hợp đồng bộ hoá cùng lúc, bạn có thể làm quá tải dữ liệu máy chủ và dữ liệu của nhà cung cấp dịch vụ di động mạng.
Nhìn chung, các lần chạy định kỳ là hợp lý nếu người dùng không cần bản cập nhật tức thì, nhưng vẫn muốn có các bản cập nhật thường xuyên. Việc chạy định kỳ cũng sẽ hợp lý nếu bạn muốn cân bằng tính sẵn có của dữ liệu mới nhất với hiệu quả của bộ điều hợp đồng bộ hoá nhỏ hơn mà không sử dụng thiết bị quá mức của chúng tôi.
Để chạy bộ điều hợp đồng bộ hoá theo định kỳ, hãy gọi
addPeriodicSync()
. Việc này sẽ lên lịch
bộ điều hợp đồng bộ hoá để chạy sau một khoảng thời gian nhất định đã trôi qua. Vì khung bộ điều hợp đồng bộ hoá
phải tính đến các hoạt động thực thi bộ điều hợp đồng bộ hoá khác và cố gắng tối đa hoá hiệu quả pin,
thời gian đã trôi qua có thể chênh lệch vài giây. Ngoài ra, khung này sẽ không chạy bộ điều hợp đồng bộ hoá nếu
mạng không khả dụng.
Lưu ý rằng addPeriodicSync()
không
chạy bộ điều hợp đồng bộ hoá vào thời điểm cụ thể trong ngày. Để chạy bộ điều hợp đồng bộ hoá ở mức khoảng
vào cùng một thời điểm hằng ngày, hãy dùng chuông báo lặp lại làm kích hoạt. Các chuông báo lặp lại được mô tả trong phần mô tả chi tiết hơn
trong tài liệu tham khảo cho AlarmManager
. Nếu bạn sử dụng
phương thức setInexactRepeating()
để đặt
điều kiện kích hoạt thời gian trong ngày có một số biến thể, bạn vẫn nên sắp xếp ngẫu nhiên thời gian bắt đầu để
đảm bảo rằng bộ điều hợp đồng bộ hoá chạy từ các thiết bị khác nhau được bố trí so le.
Phương thức addPeriodicSync()
không
tắt setSyncAutomatically()
,
do đó bạn có thể thực hiện nhiều lần đồng bộ hoá trong một khoảng thời gian tương đối ngắn. Ngoài ra, chỉ một vài
cờ điều khiển bộ điều hợp đồng bộ hoá được phép trong lệnh gọi đến
addPeriodicSync()
; những cờ
được mô tả trong tài liệu tham khảo đối với
addPeriodicSync()
Đoạn mã sau đây cho bạn biết cách lên lịch chạy bộ điều hợp đồng bộ hoá định kỳ:
Kotlin
// Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Account const val ACCOUNT = "default_account" // Sync interval constants const val SECONDS_PER_MINUTE = 60L const val SYNC_INTERVAL_IN_MINUTES = 60L const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE ... class MainActivity : FragmentActivity() { ... // A content resolver for accessing the provider private lateinit var mResolver: ContentResolver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Get the content resolver for your app mResolver = contentResolver /* * Turn on periodic syncing */ ContentResolver.addPeriodicSync( mAccount, AUTHORITY, Bundle.EMPTY, SYNC_INTERVAL) ... } ... }
Java
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account public static final String ACCOUNT = "default_account"; // Sync interval constants public static final long SECONDS_PER_MINUTE = 60L; public static final long SYNC_INTERVAL_IN_MINUTES = 60L; public static final long SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE; // Global variables // A content resolver for accessing the provider ContentResolver mResolver; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver for your app mResolver = getContentResolver(); /* * Turn on periodic syncing */ ContentResolver.addPeriodicSync( mAccount, AUTHORITY, Bundle.EMPTY, SYNC_INTERVAL); ... } ... }
Chạy bộ điều hợp đồng bộ hoá theo yêu cầu
Chạy bộ điều hợp đồng bộ hoá để phản hồi yêu cầu của người dùng là chiến lược ít được ưu tiên nhất để chạy bộ điều hợp đồng bộ hoá. Khung này được thiết kế đặc biệt để tiết kiệm pin khi nó chạy bộ điều hợp đồng bộ hoá theo lịch biểu. Các tuỳ chọn chạy đồng bộ hoá để phản hồi dữ liệu các thay đổi sẽ sử dụng hiệu quả nguồn pin, vì nguồn điện được dùng để cung cấp dữ liệu mới.
Trong khi đó, việc cho phép người dùng chạy đồng bộ hoá theo yêu cầu có nghĩa là quá trình đồng bộ hoá đó sẽ tự chạy. là sử dụng tài nguyên mạng và nguồn điện không hiệu quả. Ngoài ra, việc cung cấp tính năng đồng bộ hoá theo yêu cầu sẽ giúp người dùng yêu cầu đồng bộ hoá ngay cả khi không có bằng chứng cho thấy dữ liệu đã thay đổi và tiến hành đồng bộ hoá không làm mới dữ liệu là sử dụng pin không hiệu quả. Nhìn chung, ứng dụng của bạn nên: sử dụng các tín hiệu khác để kích hoạt quá trình đồng bộ hoá hoặc lên lịch đồng bộ hoá định kỳ mà không cần hoạt động đầu vào của người dùng.
Tuy nhiên, nếu bạn vẫn muốn chạy bộ điều hợp đồng bộ hoá theo yêu cầu, hãy đặt cờ bộ điều hợp đồng bộ hoá cho
chạy bộ điều hợp đồng bộ hoá thủ công, sau đó gọi
ContentResolver.requestSync()
.
Chạy quy trình chuyển theo yêu cầu với các cờ sau:
-
SYNC_EXTRAS_MANUAL
-
Buộc đồng bộ hoá thủ công. Khung bộ điều hợp đồng bộ hoá bỏ qua các chế độ cài đặt hiện có,
chẳng hạn như cờ do
setSyncAutomatically()
đặt. -
SYNC_EXTRAS_EXPEDITED
- Buộc quá trình đồng bộ hoá bắt đầu ngay lập tức. Nếu bạn không đặt cài đặt này, hệ thống có thể đợi vài phút giây trước khi chạy yêu cầu đồng bộ hoá vì yêu cầu này cố gắng tối ưu hoá việc sử dụng pin bằng cách lên lịch nhiều yêu cầu trong một khoảng thời gian ngắn.
Đoạn mã sau đây cho bạn biết cách gọi
requestSync()
để phản hồi một nút
nhấp vào:
Kotlin
// Constants // Content provider authority val AUTHORITY = "com.example.android.datasync.provider" // Account type val ACCOUNT_TYPE = "com.example.android.datasync" // Account val ACCOUNT = "default_account" ... class MainActivity : FragmentActivity() { ... // Instance fields private lateinit var mAccount: Account ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... /* * Create the placeholder account. The code for CreateSyncAccount * is listed in the lesson Creating a Sync Adapter */ mAccount = createSyncAccount() ... } /** * Respond to a button click by calling requestSync(). This is an * asynchronous operation. * * This method is attached to the refresh button in the layout * XML file * * @param v The View associated with the method call, * in this case a Button */ fun onRefreshButtonClick(v: View) { // Pass the settings flags by inserting them in a bundle val settingsBundle = Bundle().apply { putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) } /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle) }
Java
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... /* * Create the placeholder account. The code for CreateSyncAccount * is listed in the lesson Creating a Sync Adapter */ mAccount = CreateSyncAccount(this); ... } /** * Respond to a button click by calling requestSync(). This is an * asynchronous operation. * * This method is attached to the refresh button in the layout * XML file * * @param v The View associated with the method call, * in this case a Button */ public void onRefreshButtonClick(View v) { // Pass the settings flags by inserting them in a bundle Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_EXPEDITED, true); /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle); }