3.数据同步机制之SyncManager
同步策略
3.1SyncManager的理解
3.1.1SyncAdapterCache类的分析
找出metadata和attribute为指定类型的xml节点数据,并放到存储文件中
/data/system/registered_services/android.content.SyncAdapter.xml
例如davdroid的manifest文件中有以下内容
/data/system/registered_services/android.content.SyncAdapter.xml的文件内容
SyncAdapterCache继承自RegisteredServicesCache
并将需要解析和存储的类型传递给了RegisteredServicesCache
public class SyncAdaptersCache extends RegisteredServicesCache {
private static final String TAG = "Account";
private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
private static final String ATTRIBUTES_NAME = "sync-adapter";
private static final MySerializer sSerializer = new MySerializer();
public SyncAdaptersCache(Context context) {
super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
}
public SyncAdapterType parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs) {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.SyncAdapter);
try {
final String authority =
sa.getString(com.android.internal.R.styleable.SyncAdapter_contentAuthority);
final String accountType =
sa.getString(com.android.internal.R.styleable.SyncAdapter_accountType);
if (authority == null || accountType == null) {
return null;
}
final boolean userVisible =
sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_userVisible, true);
final boolean supportsUploading =
sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_supportsUploading,
true);
final boolean isAlwaysSyncable =
sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_isAlwaysSyncable,
false);
final boolean allowParallelSyncs =
sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_allowParallelSyncs,
false);
final String settingsActivity =
sa.getString(com.android.internal.R.styleable
.SyncAdapter_settingsActivity);
return new SyncAdapterType(authority, accountType, userVisible, supportsUploading,
isAlwaysSyncable, allowParallelSyncs, settingsActivity);
} finally {
sa.recycle();
}
}
static class MySerializer implements XmlSerializerAndParser {
public void writeAsXml(SyncAdapterType item, XmlSerializer out) throws IOException {
out.attribute(null, "authority", item.authority);
out.attribute(null, "accountType", item.accountType);
}
public SyncAdapterType createFromXml(XmlPullParser parser)
throws IOException, XmlPullParserException {
final String authority = parser.getAttributeValue(null, "authority");
final String accountType = parser.getAttributeValue(null, "accountType");
return SyncAdapterType.newKey(authority, accountType);
}
}
}
3.1.2SyncQueue的分析
private final HashMap mOperationsMap = Maps.newHashMap();
public SyncQueue(SyncStorageEngine syncStorageEngine, final SyncAdaptersCache syncAdapters) {
mSyncStorageEngine = syncStorageEngine;
mSyncAdapters = syncAdapters;
}
public void addPendingOperations(int userId) {
//mSyncStorageEngine取出上次没有完成的同步信息,这些信息由PendingOperation 表示
for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
if (op.userId != userId) continue;
//从mSyncStorageEngine中取出该同步操作的backoff信息
final Pair backoff = mSyncStorageEngine.getBackoff(
op.account, op.userId, op.authority);
//从SyncAdapterCache中取出该同步操作对应的Service信息
//如果服务不存在则无需执行之后的流程
final ServiceInfo syncAdapterInfo = mSyncAdapters.getServiceInfo(
SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
if (syncAdapterInfo == null) {
continue;
}
//构造一个SyncOperation对象
SyncOperation syncOperation = new SyncOperation(
op.account, op.userId, op.syncSource, op.authority, op.extras, 0 /* delay */,
backoff != null ? backoff.first : 0,
mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
syncAdapterInfo.type.allowParallelSyncs());
syncOperation.expedited = op.expedited;
syncOperation.pendingOperation = op;
add(syncOperation, op);//添加对象
}
}
3.1.3StorageSyncEngine
初始化的时候创建了一个目录并在该目录下新增了四个文件
/data/system/sync
/data/system/sync/accounts.xml账户信息
/data/system/sync/pending.bin处于pending状态的同步请求
/data/system/sync/status.bin状态信息
/data/system/sync/stats.bin统计信息
其中accounts.xml文件的内容在添加了davdroid后如下所示
SyncStorageEngine的代码分析
private SyncStorageEngine(Context context, File dataDir) {
mContext = context;
sSyncStorageEngine = this;
mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
File systemDir = new File(dataDir, "system");
File syncDir = new File(systemDir, "sync");
syncDir.mkdirs();
mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
//AtomicFile其实内部包括两个文件,其中一个用于数据备份
readAccountInfoLocked();
readStatusLocked();
readPendingOperationsLocked();
readStatisticsLocked();
readAndDeleteLegacyAccountInfoLocked();
writeAccountInfoLocked();
writeStatusLocked();
writePendingOperationsLocked();
writeStatisticsLocked();
}
public static SyncStorageEngine newTestInstance(Context context) {
return new SyncStorageEngine(context, context.getFilesDir());
}
public static void init(Context context) {
if (sSyncStorageEngine != null) {
return;
}
// This call will return the correct directory whether Encrypted File Systems is
// enabled or not.
File dataDir = Environment.getSecureDataDirectory();
sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
}
3.1.4SyncManager的分析
SyncManger的几位成员的作用
- SyncAdaptersCache用于管理系统中SyncService的信息,在SyncManager中,SyncService的信息用SyncAdapterType类表示
- SyncOperation表示一次正在执行或者等待执行的同步操作,而SyncQueue通过mOperationsMap保存系统中存在的SyncOperation
- SyncStorageEngine保存同步操作的信息,PendingOperation表示保存在本地文件中还没执行完的同步操作,SyncStorageEngine还会对同步操作做统计,如耗电量统计等。
public SyncManager(Context context, boolean factoryTest) {
mContext = context;
SyncStorageEngine.init(context);
mSyncStorageEngine = SyncStorageEngine.getSingleton();
mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
public void onSyncRequest(Account account, int userId, String authority,
Bundle extras) {
scheduleSync(account, userId, authority, extras, 0, false);
}
});
mSyncAdapters = new SyncAdaptersCache(mContext);
mSyncQueue = new SyncQueue(mSyncStorageEngine, mSyncAdapters);
HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
Process.THREAD_PRIORITY_BACKGROUND);
syncThread.start();
mSyncHandler = new SyncHandler(syncThread.getLooper());
mSyncAdapters.setListener(new RegisteredServicesCacheListener() {
@Override
public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
if (!removed) {
scheduleSync(null, UserHandle.USER_ALL, type.authority, null, 0 /* no delay */,
false /* onlyThoseWithUnkownSyncableState */);
}
}
}, mSyncHandler);
//创建一个intent,用于和AlarmManagerService交互
mSyncAlarmIntent = PendingIntent.getBroadcast(
mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
//注册CONNECTIVITY_ACTION广播监听,同步操作需要使用网络传数据,所以此处需要监听广播
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
if (!factoryTest) {//监听重启广播
intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
context.registerReceiver(mBootCompletedReceiver, intentFilter);
}
intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
//监听存储空间广播,保存同步时一些信息存储到设备中
intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
intentFilter.setPriority(100);
context.registerReceiver(mShutdownIntentReceiver, intentFilter);
intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_USER_STARTING);
intentFilter.addAction(Intent.ACTION_USER_STOPPING);
mContext.registerReceiverAsUser(
mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
if (!factoryTest) {
mNotificationMgr = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
context.registerReceiver(new SyncAlarmIntentReceiver(),
new IntentFilter(ACTION_SYNC_ALARM));
} else {
mNotificationMgr = null;
}
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
//创建wakelock,防止同步过程中掉电
mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
HANDLE_SYNC_ALARM_WAKE_LOCK);
mHandleAlarmWakeLock.setReferenceCounted(false);
// This WakeLock is used to ensure that we stay awake while running the sync loop
// message handler. Normally we will hold a sync adapter wake lock while it is being
// synced but during the execution of the sync loop it might finish a sync for
// one sync adapter before starting the sync for the other sync adapter and we
// don't want the device to go to sleep during that window.
mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
SYNC_LOOP_WAKE_LOCK);
mSyncManagerWakeLock.setReferenceCounted(false);
//监听SyncStorageEngine状态变化
mSyncStorageEngine.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
public void onStatusChanged(int which) {
// force the sync loop to run if the settings change
sendCheckAlarmsMessage();
}
});
if (!factoryTest) {//监听账户变化
// Register for account list updates for all users
mContext.registerReceiverAsUser(mAccountsUpdatedReceiver,
UserHandle.ALL,
new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
null, null);
}
// Pick a random second in a day to seed all periodic syncs
mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
}
3.2 ConentResolver的requestSync分析
** ConentResolver的requestSync方法用来发起一次同步请求**
如发起邮件同步请求
Account emailAccount = new Account("[email protected]","com.android.yutong");
String emailAuthority = "com.android.email.provider";
Bundle emialBundle = new Bundle();
...
//发起email同步请求
ContentResolver.requestSync(emailAccount,emailAuthority,emialBundle);
1.ContentResolver的requestSync方法
public static void requestSync(Account account, String authority, Bundle extras) {
if (extras == null) {
throw new IllegalArgumentException("Must specify extras.");
}
SyncRequest request =
new SyncRequest.Builder()
.setSyncAdapter(account, authority)
.setExtras(extras)
.syncOnce()
.build();
requestSync(request);
}
public static void requestSync(SyncRequest request) {
try {
getContentService().sync(request);
} catch(RemoteException e) {
// Shouldn't happen.
}
}
2.ContentService的requestSync方法i调用syncManager.scheduleSync
public void requestSync(Account account, String authority, Bundle extras) {
ContentResolver.validateSyncExtrasBundle(extras);
int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
syncManager.scheduleSync(account, userId, authority, extras, 0 /* no delay */,
false /* onlyThoseWithUnkownSyncableState */);
}
} finally {
restoreCallingIdentity(identityToken);
}
}
3.syncManager.scheduleSync
/**
* @param requestedAccount 要进行同步操作的账户,如果为空,将同步所有账户
* @param requestedAuthority 要同步的数据项,如果为空,表示全部同步
* @param extras 同步操作中的参数信息
* @param delay 延迟多少
* @param onlyThoseWithUnkownSyncableState决定是否只是同步那些处于unknown状态的数据
*/
public void scheduleSync(Account requestedAccount, int userId, String requestedAuthority,
Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
//判断是否允许后台传输
final boolean backgroundDataUsageAllowed = !mBootCompleted ||
getConnectivityManager().getBackgroundDataSetting();
if (extras == null) extras = new Bundle();
//同步服务中的参数信息
//SYNC_EXTRAS_EXPEDITED参数表示是否立即执行,如果设置了该参数,则delay不起作用
Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
if (expedited) {
delay = -1; // this means schedule at the front of the queue
}
AccountAndUser[] accounts;
if (requestedAccount != null && userId != UserHandle.USER_ALL) {
accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
} else {
ccounts;
if (accounts.length == 0) {
return;
}
}
//SYNC_EXTRAS_UPLOAD表示本次同步不是否上传,从本地同步到服务端是upload
//反之为downlaod
final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
//手动同步,如果是手动同步就忽略SYNC_EXTRAS_IGNORE_BACKOFF和SYNC_EXTRAS_IGNORE_SETTINGS
final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
if (manualSync) {
extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
}
final boolean ignoreSettings =
extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
//本次同步操作的触发源头,触发源的目的是为了方便进行统计工作
int source;
if (uploadOnly) {
source = SyncStorageEngine.SOURCE_LOCAL;
} else if (manualSync) {
source = SyncStorageEngine.SOURCE_USER;
} else if (requestedAuthority == null) {
source = SyncStorageEngine.SOURCE_POLL;
} else {
// this isn't strictly server, since arbitrary callers can (and do) request
// a non-forced two-way sync on a specific url
source = SyncStorageEngine.SOURCE_SERVER;
}
for (AccountAndUser account : accounts) {
//从SyncAdapterCache中取出所有的SyncService信息
final HashSet syncableAuthorities = new HashSet();
for (RegisteredServicesCache.ServiceInfo syncAdapter :
mSyncAdapters.getAllServices(account.userId)) {
syncableAuthorities.add(syncAdapter.type.authority);
}
//如果指定了本次同步的authority,就从同步服务中找出满足此authority的服务
if (requestedAuthority != null) {
final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
syncableAuthorities.clear();
if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
}
//取出syncable属性值,1为true,-1为unknown
for (String authority : syncableAuthorities) {
int isSyncable = mSyncStorageEngine.getIsSyncable(account.account, account.userId,
authority);
if (isSyncable == 0) {
continue;
}
final RegisteredServicesCache.ServiceInfo syncAdapterInfo;
syncAdapterInfo = mSyncAdapters.getServiceInfo(
SyncAdapterType.newKey(authority, account.account.type), account.userId);
if (syncAdapterInfo == null) {
continue;
}
final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
//如果是unknown切永远是可同步的就设置为true
if (isSyncable < 0 && isAlwaysSyncable) {
mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
isSyncable = 1;
}
//如果只能操作unkonwn但本次不是unknown,就不允许后续的操作
if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
continue;
}
//如果同步服务不支持上传,但本次服务又需要上传,则不允许后续操作
if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
continue;
}
// always allow if the isSyncable state is unknown
boolean syncAllowed =
(isSyncable < 0)
|| ignoreSettings
|| (backgroundDataUsageAllowed
&& mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
&& mSyncStorageEngine.getSyncAutomatically(account.account,
account.userId, authority));
if (!syncAllowed) {
continue;
}
//取出backoff(backoff是指上次没有同步完成的多久之后延迟多少再次同步)
Pair backoff = mSyncStorageEngine
.getBackoff(account.account, account.userId, authority);
long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
account.userId, authority);
final long backoffTime = backoff != null ? backoff.first : 0;
if (isSyncable < 0) {
Bundle newExtras = new Bundle();
newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
scheduleSyncOperation(
new SyncOperation(account.account, account.userId, source, authority,
newExtras, 0, backoffTime, delayUntil, allowParallelSyncs));
}
if (!onlyThoseWithUnkownSyncableState) {
scheduleSyncOperation(
new SyncOperation(account.account, account.userId, source, authority,
extras, delay, backoffTime, delayUntil, allowParallelSyncs));
}
}
}
}
4.scheduleSyncOperation函数
该方法内部将一个SyncOperation对象保存到SyncQueue中,然后发送MESSAGE_CHECK_ALARMS消息
**5. MESSAGE_CHECK_ALARMS的消息处理流程 **
public void handleMessage(Message msg) {
long earliestFuturePollTime = Long.MAX_VALUE;
long nextPendingSyncTime = Long.MAX_VALUE;
try {
waitUntilReadyToRun();
mDataConnectionIsConnected = readDataConnectionState();
mSyncManagerWakeLock.acquire(); //防止过程中掉电
//周期同步操作
earliestFuturePollTime = scheduleReadyPeriodicSyncs();
switch (msg.what) {
......
case SyncHandler.MESSAGE_CHECK_ALARMS:
//返回一个时间
nextPendingSyncTime = maybeStartNextSyncLocked();
break;
}
} finally {
manageSyncNotificationLocked();
manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
mSyncTimeTracker.update();
mSyncManagerWakeLock.release();
}
}
6.maybeStartNextSyncLocked
- 检查SyncQueue中SyncOperation,判断他们对应的同步服务的状态是否为false,是false就不再继续
- 检查ConnectivityManagerService以判断同步目标是否使用了网络,如果没有使用网络,就不允许执行同步操作
- 判断执行时间是否已到,如果未到,则不允许执行
- 与当前正在执行的同步操作对象对比
7.dispatchSyncOperation
构建一个ActiveSyncContext对象,并将该对象绑定到同步服务上
TU 8-16
- 通过bindservice启动同步服务,在onServiceConnected函数中得到用于和SyncService交互的接口对象,也就是Binder通信的ISyncAdapter 的Bp端
- ActiveSyncContext是ISyncContext解扣子Binder通信的Bn端,它调用ISyncAdapter的startSync时,会把自己同步给服务对象,同步服务得到的是ISyncContext的Bp端,当同步服务完成之后就调用ISyncContext的Bp端对象的onFinished函数通知ActiveSyncContext同步操作的结果
8. ActiveSyncContext派发请求
boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
Intent intent = new Intent();
intent.setAction("android.content.SyncAdapter");
设置目标服务的componentname
intent.setComponent(info.componentName);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.sync_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0,
null, new UserHandle(userId)));
mBound = true;
bindService启动目标服务
final boolean bindResult = mContext.bindService(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
| Context.BIND_ALLOW_OOM_MANAGEMENT,
mSyncOperation.userId);
if (!bindResult) {
mBound = false;
}
return bindResult;
}
当目标服务的onBind返回之后,ActiveSyncContext的onServiceConnected将被调用
public void onServiceConnected(ComponentName name, IBinder service) {
Message msg = mSyncHandler.obtainMessage();
msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
mSyncHandler.sendMessage(msg);
}
消息处理函数中调用runBoundToSyncAdapter
case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
// check that this isn't an old message
if (isSyncStillActive(msgData.activeSyncContext)) {
runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
}
break;
}
runBoundToSyncAdapter会调用目标服务的startSync函数
private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
ISyncAdapter syncAdapter) {
activeSyncContext.mSyncAdapter = syncAdapter;
final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
try {
activeSyncContext.mIsLinkedToDeath = true;
syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);
syncAdapter.startSync(activeSyncContext, syncOperation.authority,
syncOperation.account, syncOperation.extras);
} catch (RemoteException remoteExc) {
closeActiveSyncContext(activeSyncContext);
increaseBackoffSetting(syncOperation);
scheduleSyncOperation(new SyncOperation(syncOperation));
} catch (RuntimeException exc) {
closeActiveSyncContext(activeSyncContext);
}
}
3.3数据同步管理SyncManager总结
TU 8-18
recyclerview
http://www.jianshu.com/p/2f2996ef2c75
http://www.jianshu.com/p/6c78a5a07db5
http://www.gcssloop.com/customview/matrix-3d-camera
http://www.gcssloop.com/customview/CustomViewIndex
http://blog.devwiki.net/index.php/2016/06/13/RecyclerView-Scroll-Listener.html
http://mp.weixin.qq.com/s?__biz=MzI4MzE2MTQ5Mw==&mid=2649752111&idx=1&sn=414c62ff6c1485c9a7cd90dd3fc8f6e6#rd
http://www.jianshu.com/p/411ab861034f
http://blog.csdn.net/luoyanglizi/article/details/51519686
http://blog.csdn.net/mynameishuangshuai/article/details/51153978
http://blog.csdn.net/huachao1001/article/details/51594004
http://www.jianshu.com/p/c82cebc4e798
http://www.jianshu.com/p/b1ad50633732
http://www.jianshu.com/p/d993ad653293
http://www.jianshu.com/p/87a49f732724
http://mouxuejie.com/blog/2016-03-06/recyclerview-analysis/
http://mijack.github.io/2016/01/07/%E8%AE%A9%20Toolbar%20%E9%9A%8F%E7%9D%80%20RecyclerView%20%E7%9A%84%E6%BB%9A%E5%8A%A8%E8%80%8C%E6%98%BE%E7%A4%BA%E9%9A%90%E8%97%8F/
http://kymjs.com/code/2015/11/26/01/
http://android.jobbole.com/81947/
https://github.com/nuptboyzhb/TreeRecyclerView
http://www.jianshu.com/p/a92955be0a3e
http://blog.csdn.net/u014099894/article/details/51855129
https://github.com/zuiwuyuan/RecycleView_PullToRefresh_LoadMore
http://blog.csdn.net/yanzhenjie1003/article/details/51935982
https://github.com/songhanghang/Smart-HeaderFooter-RecyclerView
http://blog.devwiki.net/index.php/2016/07/24/three-ways-click-recyclerview-item.html
http://www.jianshu.com/p/a3388e716a93
http://kymjs.com/code/2016/07/10/01
https://github.com/oubowu/PinnedSectionItemDecoration/blob/master/README-CN.md
http://blog.devwiki.net/index.php/2016/07/17/Recycler-View-Adapter-ViewHolder-optimized.html
http://www.jianshu.com/p/12ec590f6c76
https://github.com/dinuscxj/RecyclerItemDecoration
https://segmentfault.com/a/1190000006147436
https://github.com/dinuscxj/PullZoomRecyclerView
http://loshine.me/2016/08/25/a-universal-solution-of-recyclerview-adapter-notify/
http://blog.csdn.net/feiyangbahu1/article/details/52305769
http://www.jianshu.com/p/f592f3715ae2
http://blog.csdn.net/yanzhenjie1003/article/details/52115566
http://www.jianshu.com/p/9bfed6e127cc
https://github.com/CymChad/CymChad.github.io
https://github.com/lufficc/LightAdapter
https://github.com/markzhai/DataBindingAdapter
http://www.jianshu.com/p/66c065874848
http://blog.csdn.net/zxt0601/article/details/52420706
http://blog.csdn.net/qifengdeqingchen/article/details/52388467
https://github.com/kHRYSTAL/CircleRecyclerView
http://hanhailong.com/2015/12/28/Android%E8%BF%9B%E9%98%B6%E4%B9%8BProGuard%E4%BB%A3%E7%A0%81%E6%B7%B7%E6%B7%86/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
http://blog.csdn.net/lmj623565791/article/details/46858663
https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-18/%E6%8B%96%E6%8B%BDRecyclerView.md
http://androidone.io/info_10265.html
https://github.com/cymcsg/UltimateRecyclerView
http://www.itlanbao.com/code/20150812/10050/100317.html
http://www.itlanbao.com/code/20150812/10050/100316.html
http://www.itlanbao.com/code/20150812/10050/100322.html
http://www.itlanbao.com/code/20150812/10050/100323.html
http://www.itlanbao.com/code/20151111/10000/100639.html
http://www.jianshu.com/p/a6f158d1a9c9