
ååãããã«ã¡ã¯ã
ååã®èšäºã§å§ãŸã£ããããã¯ãç¶ç¶ããããã€ã¹ã§ã¢ã«ãŠã³ããäœæããã¡ã«ããºã ãæ€èšããŸããã ããã¯ãSyncAdapter Frameworkã䜿çšããããã®æåã®åææ¡ä»¶ã§ããã
2çªç®ã®æ¡ä»¶ã¯
ContentProviderã®ååšã§
ãã ããã®äœæããã»ã¹ã¯ããã¥ã¡ã³ãã«èšèŒãããŠããŸãã æ£çŽã«èšããšãããã§ã®èª¬æãããŸã奜ãã§ã¯ãããŸããããã¹ãŠãããã°ã£ãŠè€éã«èŠããŸãã ãããã£ãŠãå°ããµã€ã¯ã«ãç¹°ãè¿ãããã®ãããã¯ãããäžåºŠçµéšããŸãã ã¹ã¿ããããã€ããŒã䜿çšããããšãã§ããŸãããç§ãã¡ã¯çå£ãªäººã
ã§ããããã®ããŒã«ã®å
šæ©èœã䜿çšããŸãã
åã®éšåãžã®ã³ã¡ã³ãã§ã¯ãæ¿èªãå¿
èŠã§ã¯ãªãåæã®ã¿ãå¿
èŠãªå Žåãèæ
®ããŠããªã¯ãšã¹ãããã©ãã·ã¥ãããŸããã ãã®ãããªå Žåãæ€èšããŸãã äŸãšããŠãç§ãã¡ã®ææã®habrãèªãããã ãã§ãªããåçŽãªrssãªãŒããŒãåãäžããŠæžããŠã¿ãŸãããã ã¯ãããšãŠãã€ãŸããªãã§ãã
ã¢ããªã±ãŒã·ã§ã³ã«ã¯ããã£ãŒãã远å /åé€ãããããã¥ãŒã¹ã®ãªã¹ãã衚瀺ãããããã©ãŠã¶ãŒã§éãããããæ©èœããããŸãã åæããã»ã¹ãèŠèŠåããæè¿ãµããŒãã©ã€ãã©ãªã«è¿œå ããã
SwipeRefreshLayoutã¯ã©ã¹ã䜿çšããŠåæããã»ã¹ãéå§ããŸãã
ããã§ãããäœã§ãããã©ã®ããã«äœ¿çšããããèªãããšãã§ã
ãŸã ã
ç¹å®ã®ééã§èªååæãèšå®ããã«ã¯ããã®ãããªèšå®ç»é¢ãå¿
èŠã§ãã ã¢ããªã±ãŒã·ã§ã³ãžã®ã¢ã¯ã»ã¹ã ãã§ãªããã¢ã«ãŠã³ãã®ã·ã¹ãã ç»é¢ãããã¢ã¯ã»ã¹ããããšãæãŸããã§ãïŒèšäºã®ã¹ã¯ãªãŒã³ã·ã§ããã®ããã«ïŒã ããã«ã¯
PreferenceFragmentsã䜿çšããŸãã æ©èœã決å®ããŸãããå§ããŸãããã
ã¢ã«ãŠã³ã
åã®ããŒãããã¢ããªã±ãŒã·ã§ã³ã«ã¢ã«ãŠã³ãã远å ããæ¹æ³ã¯ãã§ã«ç¥ã£ãŠããŸãã ãã ããã¢ããªã±ãŒã·ã§ã³ã®å Žåãããããæ¿èªã¯å¿
èŠãããŸãããAuthenticatorã空ã®å®è£
ã«çœ®ãæããŸãã
Authenticator.javapublic 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