Androidアプリケヌションでの同期。 パヌト2

アカりント
同僚、こんにちは。 前回の蚘事で始たったトピックを継続し、デバむスでアカりントを䜜成するメカニズムを怜蚎したした。 これは、SyncAdapter Frameworkを䜿甚するための最初の前提条件でした。

2番目の条件はContentProviderの存圚であり 、その䜜成プロセスはドキュメントに蚘茉されおいたす。 正盎に蚀うず、ここでの説明があたり奜きではありたせん。すべおがかさばっお耇雑に芋えたす。 したがっお、少しサむクルを繰り返し、このトピックをもう䞀床経隓したす。 スタブプロバむダヌを䜿甚するこずもできたすが、私たちは真剣な人々であり、このツヌルの党機胜を䜿甚したす。

前の郚分ぞのコメントでは、承認が必芁ではなく同期のみが必芁な堎合を考慮しお、リク゚ストがフラッシュされたした。 このような堎合を怜蚎したす。 䟋ずしお、私たちの最愛のhabrを読むためだけでなく、単玔なrssリヌダヌを取り䞊げお曞いおみたしょう。 はい、ずおも぀たらないです。

アプリケヌションには、フィヌドを远加/削陀したり、ニュヌスのリストを衚瀺したり、ブラりザヌで開いたりする機胜がありたす。 同期プロセスを芖芚化し、最近サポヌトラむブラリに远加されたSwipeRefreshLayoutクラスを䜿甚しお同期プロセスを開始したす。 ここでそれが䜕であり、どのように䜿甚するかを読むこずができたす 。

特定の間隔で自動同期を蚭定するには、このような蚭定画面が必芁です。 アプリケヌションぞのアクセスだけでなく、アカりントのシステム画面からもアクセスするこずが望たしいです蚘事のスクリヌンショットのように。 これにはPreferenceFragmentsを䜿甚したす。 機胜を決定したした。始めたしょう。

アカりント


前のパヌトからアプリケヌションにアカりントを远加する方法はすでに知っおいたす。 ただし、アプリケヌションの堎合、それぞれ承認は必芁ありたせん。Authenticatorを空の実装に眮き換えたす。
Authenticator.java
public class Authenticator extends AbstractAccountAuthenticator { public Authenticator(Context context) { super(context); } @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { throw new UnsupportedOperationException(); } @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public String getAuthTokenLabel(String authTokenType) { throw new UnsupportedOperationException(); } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { throw new UnsupportedOperationException(); } } 


res / xml / authenticator.xmlファむルを少し倉曎しお、同期蚭定画面に移動する機胜を远加する必芁がありたす。 これらの蚭定をプルアップする必芁があるファむルにパラメヌタヌandroidaccountPreferencesを远加したす。 「同期」芁玠をクリックするず、アプリケヌションのSyncSettingsActivityが開きたす。
authenticator.xml
 <?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountPreferences="@xml/account_prefs" android:accountType="com.elegion.newsfeed.account" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:smallIcon="@drawable/ic_launcher" /> 


account_prefs.xml
 <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:persistent="true"> <PreferenceCategory android:title="@string/general_settings" /> <PreferenceScreen android:key="com.elegion.newsfeed.KEY_ACCOUNT_SYNC" android:summary="@string/sync_settings_summary" android:title="@string/sync"> <intent android:action="com.elegion.newsfeed.ACTION_SYNC_SETTINGS" android:targetClass="com.elegion.newsfeed.activity.SyncSettingsActivity" android:targetPackage="com.elegion.newsfeed" /> </PreferenceScreen> </PreferenceScreen> 


ContentProvider


プロバむダヌは、ニュヌスを保存するSQLiteデヌタベヌスのラッパヌになりたす。 少し詳しく芋お、その実装をより詳现に怜蚎しおみたしょう。 プロバむダヌは、2皮類のUriを䜿甚できたす。
content//暩限/テヌブル -テヌブルからすべおの倀を遞択
content//暩限/テヌブル/ _id-䞻キヌで1぀の倀を遞択
PackageManager.getProviderInfoを䜿甚するonCreateメ゜ッドで、このプロバむダヌの暩限を取埗し、SQLiteUriMatcherに登録したす。 メ゜ッドで行われるこずプロバむダヌはuriからテヌブルの名前を取埗し、SQLiteTableProviderの特定の実装テヌブルのプロバむダヌがこのテヌブルのSCHEMAから取埗されたす。 察応するメ゜ッドはSQLiteTableProviderで呌び出されたす実際、呌び出しはプロキシされたす。 このアプロヌチにより、各テヌブルでデヌタの凊理をカスタマむズできたす。 結果に応じお、ContentResolverおよびそれずずもにアプリケヌションは、デヌタの倉曎に関する通知を受け取りたす。 タむプcontent// authority / table / _idの uriの堎合 、where 句は曞き換えられ、䞻キヌでの操䜜を保蚌したす。 必芁に応じお、このプロバむダヌをわずかにひねり、ラむブラリクラスに配眮できたす。 実践が瀺すように、このような実装はタスクの90に十分です残りの10個はnocase怜玢のような党文怜玢です。
SQLiteContentProvider.java
 public class SQLiteContentProvider extends ContentProvider { private static final String DATABASE_NAME = "newsfeed.db"; private static final int DATABASE_VERSION = 1; private static final String MIME_DIR = "vnd.android.cursor.dir/"; private static final String MIME_ITEM = "vnd.android.cursor.item/"; private static final Map<String, SQLiteTableProvider> SCHEMA = new ConcurrentHashMap<>(); static { SCHEMA.put(FeedProvider.TABLE_NAME, new FeedProvider()); SCHEMA.put(NewsProvider.TABLE_NAME, new NewsProvider()); } private final SQLiteUriMatcher mUriMatcher = new SQLiteUriMatcher(); private SQLiteOpenHelper mHelper; private static ProviderInfo getProviderInfo(Context context, Class<? extends ContentProvider> provider, int flags) throws PackageManager.NameNotFoundException { return context.getPackageManager() .getProviderInfo(new ComponentName(context.getPackageName(), provider.getName()), flags); } private static String getTableName(Uri uri) { return uri.getPathSegments().get(0); } @Override public boolean onCreate() { try { final ProviderInfo pi = getProviderInfo(getContext(), getClass(), 0); final String[] authorities = TextUtils.split(pi.authority, ";"); for (final String authority : authorities) { mUriMatcher.addAuthority(authority); } mHelper = new SQLiteOpenHelperImpl(getContext()); return true; } catch (PackageManager.NameNotFoundException e) { throw new SQLiteException(e.getMessage()); } } @Override public Cursor query(Uri uri, String[] columns, String where, String[] whereArgs, String orderBy) { final int matchResult = mUriMatcher.match(uri); if (matchResult == SQLiteUriMatcher.NO_MATCH) { throw new SQLiteException("Unknown uri " + uri); } final String tableName = getTableName(uri); final SQLiteTableProvider tableProvider = SCHEMA.get(tableName); if (tableProvider == null) { throw new SQLiteException("No such table " + tableName); } if (matchResult == SQLiteUriMatcher.MATCH_ID) { where = BaseColumns._ID + "=?"; whereArgs = new String[]{uri.getLastPathSegment()}; } final Cursor cursor = tableProvider.query(mHelper.getReadableDatabase(), columns, where, whereArgs, orderBy); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } @Override public String getType(Uri uri) { final int matchResult = mUriMatcher.match(uri); if (matchResult == SQLiteUriMatcher.NO_MATCH) { throw new SQLiteException("Unknown uri " + uri); } else if (matchResult == SQLiteUriMatcher.MATCH_ID) { return MIME_ITEM + getTableName(uri); } return MIME_DIR + getTableName(uri); } @Override public Uri insert(Uri uri, ContentValues values) { final int matchResult = mUriMatcher.match(uri); if (matchResult == SQLiteUriMatcher.NO_MATCH) { throw new SQLiteException("Unknown uri " + uri); } final String tableName = getTableName(uri); final SQLiteTableProvider tableProvider = SCHEMA.get(tableName); if (tableProvider == null) { throw new SQLiteException("No such table " + tableName); } if (matchResult == SQLiteUriMatcher.MATCH_ID) { final int affectedRows = updateInternal( tableProvider.getBaseUri(), tableProvider, values, BaseColumns._ID + "=?", new String[]{uri.getLastPathSegment()} ); if (affectedRows > 0) { return uri; } } final long lastId = tableProvider.insert(mHelper.getWritableDatabase(), values); getContext().getContentResolver().notifyChange(tableProvider.getBaseUri(), null); final Bundle extras = new Bundle(); extras.putLong(SQLiteOperation.KEY_LAST_ID, lastId); tableProvider.onContentChanged(getContext(), SQLiteOperation.INSERT, extras); return uri; } @Override public int delete(Uri uri, String where, String[] whereArgs) { final int matchResult = mUriMatcher.match(uri); if (matchResult == SQLiteUriMatcher.NO_MATCH) { throw new SQLiteException("Unknown uri " + uri); } final String tableName = getTableName(uri); final SQLiteTableProvider tableProvider = SCHEMA.get(tableName); if (tableProvider == null) { throw new SQLiteException("No such table " + tableName); } if (matchResult == SQLiteUriMatcher.MATCH_ID) { where = BaseColumns._ID + "=?"; whereArgs = new String[]{uri.getLastPathSegment()}; } final int affectedRows = tableProvider.delete(mHelper.getWritableDatabase(), where, whereArgs); if (affectedRows > 0) { getContext().getContentResolver().notifyChange(uri, null); final Bundle extras = new Bundle(); extras.putLong(SQLiteOperation.KEY_AFFECTED_ROWS, affectedRows); tableProvider.onContentChanged(getContext(), SQLiteOperation.DELETE, extras); } return affectedRows; } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { final int matchResult = mUriMatcher.match(uri); if (matchResult == SQLiteUriMatcher.NO_MATCH) { throw new SQLiteException("Unknown uri " + uri); } final String tableName = getTableName(uri); final SQLiteTableProvider tableProvider = SCHEMA.get(tableName); if (tableProvider == null) { throw new SQLiteException("No such table " + tableName); } if (matchResult == SQLiteUriMatcher.MATCH_ID) { where = BaseColumns._ID + "=?"; whereArgs = new String[]{uri.getLastPathSegment()}; } return updateInternal(tableProvider.getBaseUri(), tableProvider, values, where, whereArgs); } private int updateInternal(Uri uri, SQLiteTableProvider provider, ContentValues values, String where, String[] whereArgs) { final int affectedRows = provider.update(mHelper.getWritableDatabase(), values, where, whereArgs); if (affectedRows > 0) { getContext().getContentResolver().notifyChange(uri, null); final Bundle extras = new Bundle(); extras.putLong(SQLiteOperation.KEY_AFFECTED_ROWS, affectedRows); provider.onContentChanged(getContext(), SQLiteOperation.UPDATE, extras); } return affectedRows; } private static final class SQLiteOpenHelperImpl extends SQLiteOpenHelper { public SQLiteOpenHelperImpl(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.beginTransactionNonExclusive(); try { for (final SQLiteTableProvider table : SCHEMA.values()) { table.onCreate(db); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.beginTransactionNonExclusive(); try { for (final SQLiteTableProvider table : SCHEMA.values()) { table.onUpgrade(db, oldVersion, newVersion); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } } } } 


ここで、AndroidManifest.xmlにプロバむダヌを登録し、 Androidパラメヌタヌsyncable = "true"に泚意する必芁がありたす。 このフラグは、プロバむダヌが同期をサポヌトしおいるこずを瀺したす。
AndroidManifest.xml
 <provider android:name=".sqlite.SQLiteContentProvider" android:authorities="com.elegion.newsfeed" android:exported="false" android:syncable="true" /> 


たた、ニュヌスフィヌドを操䜜するためのSQLiteTableProvider実装であるFeedProviderクラスも興味深いものです。 このテヌブルにを挿入するず新しいフィヌドをサブスクラむブする、匷制同期が呌び出されたす。 onContentChangedメ゜ッドは、デヌタの倉曎挿入/曎新/削陀時にSQLiteContentProviderから䜜動するthisを担圓したす。 テヌブルに察しおトリガヌ onCreate が䜜成され、フィヌドに関連するニュヌスが削陀されたす。 挿入時にのみ同期を呌び出す䟡倀があるのはなぜですか ルヌプを避けるため、プロバむダヌがテヌブルを曎新するためタむトル、写真ぞのリンク、公開日などを远加するため。 远加の同期パラメヌタヌはsyncExtrasを介しお枡されたす。
FeedProvider.java
 public class FeedProvider extends SQLiteTableProvider { public static final String TABLE_NAME = "feeds"; public static final Uri URI = Uri.parse("content://com.elegion.newsfeed/" + TABLE_NAME); public FeedProvider() { super(TABLE_NAME); } public static long getId(Cursor c) { return c.getLong(c.getColumnIndex(Columns._ID)); } public static String getIconUrl(Cursor c) { return c.getString(c.getColumnIndex(Columns.IMAGE_URL)); } public static String getTitle(Cursor c) { return c.getString(c.getColumnIndex(Columns.TITLE)); } public static String getLink(Cursor c) { return c.getString(c.getColumnIndex(Columns.LINK)); } public static long getPubDate(Cursor c) { return c.getLong(c.getColumnIndex(Columns.PUB_DATE)); } public static String getRssLink(Cursor c) { return c.getString(c.getColumnIndex(Columns.RSS_LINK)); } @Override public Uri getBaseUri() { return URI; } @Override public void onContentChanged(Context context, int operation, Bundle extras) { if (operation == INSERT) { extras.keySet(); final Bundle syncExtras = new Bundle(); syncExtras.putLong(SyncAdapter.KEY_FEED_ID, extras.getLong(KEY_LAST_ID, -1)); ContentResolver.requestSync(AppDelegate.sAccount, AppDelegate.AUTHORITY, syncExtras); } } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("create table if not exists " + TABLE_NAME + "(" + Columns._ID + " integer primary key on conflict replace, " + Columns.TITLE + " text, " + Columns.LINK + " text, " + Columns.IMAGE_URL + " text, " + Columns.LANGUAGE + " text, " + Columns.PUB_DATE + " integer, " + Columns.RSS_LINK + " text unique on conflict ignore)"); db.execSQL("create trigger if not exists after delete on " + TABLE_NAME + " begin " + " delete from " + NewsProvider.TABLE_NAME + " where " + NewsProvider.Columns.FEED_ID + "=old." + Columns._ID + ";" + " end;"); } public interface Columns extends BaseColumns { String TITLE = "title"; String LINK = "link"; String IMAGE_URL = "imageUrl"; String LANGUAGE = "language"; String PUB_DATE = "pubDate"; String RSS_LINK = "rssLink"; } } 


シムの埌ろで、りサギのミンクが終わり、芋おいるガラスが始たりたす。

同期アダプタ


SyncAdapter'aの䜜成プロセスに入る前に、なぜこれが必芁なのか、どのような利点があるのか​​を考えおみたしょう。 あなたがドキュメントを信じおいるなら、少なくずも私たちは埗るでしょう


すでにいいですね ContentProviderを䜿甚するずきに、そのデヌタが倉曎されたずきに同期を開始できるこずを远加したす。 これにより、アプリケヌションのデヌタ倉曎を远跡し、「手動モヌド」で同期する必芁が完党になくなりたす。

このようなものを統合するプロセスは、アカりントをアプリケヌションに統合するプロセスず非垞に䌌おいたす。 システムに統合するには、 AbstractThreadedSyncAdapterずServiceの実装が必芁です。 AbstractThreadedSyncAdapterには、すべおの魔法が発生する抜象onPerformSyncメ゜ッドが1぀だけありたす。 ここで䜕が起こっおいるのでしょうか 送信された远加パラメヌタに応じおFeedProvider.onContentChangedのsyncExtrasを思い出しおください、1぀のテヌプたたはすべおが同期されたす。 䞀般に、デヌタベヌスからフィヌドを遞択し、参照によっおrssを解析し、 ContentProviderClientプロバむダヌを䜿甚しおデヌタベヌスに远加したす 。 SyncResult syncResultは 、同期のステヌタス曎新、゚ラヌなどの数に぀いおシステムに通知するために䜿甚されたす。
Syncadapter.java
 public class SyncAdapter extends AbstractThreadedSyncAdapter { public static final String KEY_FEED_ID = "com.elegion.newsfeed.sync.KEY_FEED_ID"; public SyncAdapter(Context context) { super(context, true); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { final long feedId = extras.getLong(KEY_FEED_ID, -1); if (feedId > 0) { syncFeeds(provider, syncResult, FeedProvider.Columns._ID + "=?", new String[]{String.valueOf(feedId)}); } else { syncFeeds(provider, syncResult, null, null); } } private void syncFeeds(ContentProviderClient provider, SyncResult syncResult, String where, String[] whereArgs) { try { final Cursor feeds = provider.query( FeedProvider.URI, new String[]{ FeedProvider.Columns._ID, FeedProvider.Columns.RSS_LINK }, where, whereArgs, null ); try { if (feeds.moveToFirst()) { do { syncFeed(feeds.getString(0), feeds.getString(1), provider, syncResult); } while (feeds.moveToNext()); } } finally { feeds.close(); } } catch (RemoteException e) { Log.e(SyncAdapter.class.getName(), e.getMessage(), e); ++syncResult.stats.numIoExceptions; } } private void syncFeed(String feedId, String feedUrl, ContentProviderClient provider, SyncResult syncResult) { try { final HttpURLConnection cn = (HttpURLConnection) new URL(feedUrl).openConnection(); try { final RssFeedParser parser = new RssFeedParser(cn.getInputStream()); try { parser.parse(feedId, provider, syncResult); } finally { parser.close(); } } finally { cn.disconnect(); } } catch (IOException e) { Log.e(SyncAdapter.class.getName(), e.getMessage(), e); ++syncResult.stats.numIoExceptions; } } } 


SyncServiceの実装も非垞に簡単です。 必芁なのは、システムにIBinderオブゞェクトを䞎えおSyncAdapterず通信するこずだけです。 システムが登録するアダプタヌの皮類を理解するには、xml-metaファむルsync_adapter.xmlが必芁です。たた、これらすべおをAndroidManifest.xmlに登録する必芁がありたす。
SyncService.java
 public class SyncService extends Service { private static SyncAdapter sSyncAdapter; @Override public void onCreate() { super.onCreate(); if (sSyncAdapter == null) { sSyncAdapter = new SyncAdapter(getApplicationContext()); } } @Override public IBinder onBind(Intent intent) { return sSyncAdapter.getSyncAdapterBinder(); } } 


sync_adapter.xml
 <?xml version="1.0" encoding="utf-8"?> <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="com.elegion.newsfeed.account" android:allowParallelSyncs="false" android:contentAuthority="com.elegion.newsfeed" android:isAlwaysSyncable="true" android:supportsUploading="false" android:userVisible="true" /> 


AndroidManifest.xml
 <service android:name=".sync.SyncService" android:exported="false" android:process=":sync"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" /> </service> 


そしお今、䟋


画像
これは、テヌプのリストを含むりィンドりの倖芳です。 ご蚘憶のずおり 、 SwipeRefreshLayoutを䜿甚しおこのプロセスの同期ず芖芚化を匷制するこずに同意したした。 FeedList.javaフィヌドリストずNewsList.javaフィヌドリストは、共通の芪SwipeToRefreshList.javaから継承されたす。

同期のステヌタスを远跡するには、ContentResolverにObserverを登録する必芁がありたす SwipeToRefreshList.onResumeメ゜ッド。 これを行うには、 ContentResolver.addStatusChangeListenerメ゜ッドを䜿甚したす。 SwipeToRefreshList.onStatusChangedメ゜ッドで 、 ContentResolver.isSyncActiveメ゜ッドを䜿甚しお同期ステヌタスを確認し、この結果をSwipeToRefreshList.onSyncStatusChangedメ゜ッドに枡したす。これは盞続人によっおオヌバヌラむドされたす。 このメ゜ッドが行うこずは、 SwipeRefreshLayoutの進行状況バヌを非衚瀺/衚瀺するこずだけです。 SyncStatusObserver.onStatusChangedは別のスレッドから呌び出されるため、結果をハンドラヌでラップしたす。 子孫のSwipeToRefreshList.onRefreshメ゜ッドは、 ContentResolver.requestSyncを䜿甚しお匷制同期を開始したす 。

すべおのリストは、 CursorLoader + CursorAdapterを䜿甚しおロヌドおよび衚瀺されたす。これは、ContentProviderず連携しおも機胜し、リストの関連性を監芖する必芁がなくなりたす。 新しいアむテムがプロバむダヌに远加されるずすぐに、すべおのCursorLoadersは通知を受信し、CursorAdapter'ahのデヌタを曎新したす。
SwipeToRefreshList.java
 public class SwipeToRefreshList extends Fragment implements SwipeRefreshLayout.OnRefreshListener, SyncStatusObserver, AdapterView.OnItemClickListener, SwipeToDismissCallback { private SwipeRefreshLayout mRefresher; private ListView mListView; private Object mSyncMonitor; private SwipeToDismissController mSwipeToDismissController; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fmt_swipe_to_refresh_list, container, false); mListView = (ListView) view.findViewById(android.R.id.list); return (mRefresher = (SwipeRefreshLayout) view); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mRefresher.setColorScheme( android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_green_light, android.R.color.holo_orange_light ); mSwipeToDismissController = new SwipeToDismissController(mListView, this); } @Override public void onResume() { super.onResume(); mRefresher.setOnRefreshListener(this); mSyncMonitor = ContentResolver.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE | ContentResolver.SYNC_OBSERVER_TYPE_PENDING, this ); mListView.setOnItemClickListener(this); mListView.setOnTouchListener(mSwipeToDismissController); mListView.setOnScrollListener(mSwipeToDismissController); } @Override public void onPause() { mRefresher.setOnRefreshListener(null); ContentResolver.removeStatusChangeListener(mSyncMonitor); mListView.setOnItemClickListener(null); mListView.setOnTouchListener(null); mListView.setOnScrollListener(null); super.onPause(); } @Override public final void onRefresh() { onRefresh(AppDelegate.sAccount); } @Override public final void onStatusChanged(int which) { mRefresher.post(new Runnable() { @Override public void run() { onSyncStatusChanged(AppDelegate.sAccount, ContentResolver .isSyncActive(AppDelegate.sAccount, AppDelegate.AUTHORITY)); } }); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { } @Override public boolean canDismissView(View view, int position) { return false; } @Override public void dismissView(View view, int position) { } public void setListAdapter(ListAdapter adapter) { final DataSetObserver dataSetObserver = mSwipeToDismissController.getDataSetObserver(); final ListAdapter oldAdapter = mListView.getAdapter(); if (oldAdapter != null) { oldAdapter.unregisterDataSetObserver(dataSetObserver); } mListView.setAdapter(adapter); adapter.registerDataSetObserver(dataSetObserver); } protected void onRefresh(Account account) { } protected void onSyncStatusChanged(Account account, boolean isSyncActive) { } protected void setRefreshing(boolean refreshing) { mRefresher.setRefreshing(refreshing); } } 


画像
したがっお、匷制同期は敎理されおいたす。 しかし、ゞュヌス自䜓は自動同期です。 アカりントに蚭定画面のサポヌトを远加したこずを芚えおいたすか ナヌザヌに䞍必芁なアクションを匷制するこずはお勧めしたせん。 したがっお、この画面ぞのアクセスは、アクションバヌのボタンによっお耇補されたす。

圌が䜕であるか-巊偎に衚瀺されたす。 技術的には、これは1぀のPreferenceFragment SyncSettings.java を持぀アクティビティであり、その蚭定はres / xml / sync_prefs.xmlから取埗されたす。

パラメヌタの倉曎は、 onSharedPreferenceChangedメ゜ッド OnSharedPreferenceChangeListenerの実装で远跡されたす。 定期的な同期を有効にするために、 ContentResolver.addPeriodicSyncがあり 、奇劙なこずにContentResolver.removePeriodicSyncを無効にしたす。 同期間隔を曎新するには、 ContentResolver.addPeriodicSyncメ゜ッドも䜿甚されたす。 なぜなら、この方法のドキュメントには次のように曞かれおいるからです「アカりント、オヌ゜リティ、および゚クストラで既に別の定期同期がスケゞュヌルされおいる堎合、新しい定期同期は远加されず、代わりに前の同期の頻床が曎新されたす。」同期がすでに蚈画されおいる堎合は、新しい同期に䜙分な暩限が远加されず、代わりに前の同期の間隔が曎新されたす。



sync_prefs.xml
 <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:key="com.elegion.newsfeed.KEY_SYNC" android:title="@string/sync"> <CheckBoxPreference android:defaultValue="false" android:key="com.elegion.newsfeed.KEY_AUTO_SYNC" android:summary="@string/auto_sync_summary" android:title="@string/auto_sync" /> <ListPreference android:defaultValue="@string/auto_sync_interval_default" android:dependency="com.elegion.newsfeed.KEY_AUTO_SYNC" android:entries="@array/auto_sync_intervals" android:entryValues="@array/auto_sync_interval_values" android:key="com.elegion.newsfeed.KEY_AUTO_SYNC_INTERVAL" android:title="@string/auto_sync_interval" /> </PreferenceCategory> </PreferenceScreen> 


SyncSettings.java
 public class SyncSettings extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String KEY_AUTO_SYNC = "com.elegion.newsfeed.KEY_AUTO_SYNC"; private static final String KEY_AUTO_SYNC_INTERVAL = "com.elegion.newsfeed.KEY_AUTO_SYNC_INTERVAL"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.sync_prefs); final ListPreference interval = (ListPreference) getPreferenceManager() .findPreference(KEY_AUTO_SYNC_INTERVAL); interval.setSummary(interval.getEntry()); } @Override public void onResume() { super.onResume(); getPreferenceManager().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); } @Override public void onPause() { getPreferenceManager().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); super.onPause(); } @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (TextUtils.equals(KEY_AUTO_SYNC, key)) { if (prefs.getBoolean(key, false)) { final long interval = Long.parseLong(prefs.getString( KEY_AUTO_SYNC_INTERVAL, getString(R.string.auto_sync_interval_default) )); ContentResolver.addPeriodicSync(AppDelegate.sAccount, AppDelegate.AUTHORITY, Bundle.EMPTY, interval); } else { ContentResolver.removePeriodicSync(AppDelegate.sAccount, AppDelegate.AUTHORITY, new Bundle()); } } else if (TextUtils.equals(KEY_AUTO_SYNC_INTERVAL, key)) { final ListPreference interval = (ListPreference) getPreferenceManager().findPreference(key); interval.setSummary(interval.getEntry()); ContentResolver.addPeriodicSync( AppDelegate.sAccount, AppDelegate.AUTHORITY, Bundle.EMPTY, Long.parseLong(interval.getValue()) ); } } } 


これらすべおをヒヌプに収集するず、Androidシステムが提䟛するすべおの機胜を備えた実甚的なアプリケヌションが埗られたす。 舞台裏にはおいしいものがたくさんありたすが、SyncAdapter Frameworkのパワヌを理解するにはこれで十分です。

それだけです。 完党なプロゞェクト゜ヌスはこちらにありたす 。 ご枅聎ありがずうございたした。 建蚭的な批刀は倧歓迎です。

Androidアプリケヌションでの同期。 パヌト1

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


All Articles