Contacts 7.1.1源码解读(一)
参考链接https://blog.csdn.net/dddxxxx/article/details/78731064
一、简介
写这篇文章是公司需求定制通讯录,在官方源码中改,Contacts源码是从Android7.1.1中抽离出来的,并转为as项目,具体如何抽离,后面重新写一篇介绍,本文主要浅谈Contacts主要界面以及功能实现。
联系人模块主要是记录用户联系人记录,增删改查
二、软件架构
联系人Contacts应用主要包括3个部分:
1. Contacts主要响应用户的请求和交互,数据显示。
2. ContactsProvider继承自Android四大组件之一的ContentProvider组件,封装了对底层数据库contact2.db的添删改查。
3. SQLite在底层物理性地存储了联系人数据。
主要交互流程图
三、各个模块分析
3.1 联系人数据显示
入口PeopleActivity是首页,负责联系人列表显示维护了一个Toobar(包括了搜索,多选数量栏),一个自定义Tab,一个Fragment(收藏与联系人列表),一个floatButton(新建联系人/多选删除),ActionBarAdapter类是负责管理Toolbar,搜索,多选状态显示
数据加载 每个fragment中的CursorLoader负责加载数据,实现LoaderManager接口负责管理这些CursorLoader。
PeopleActivity维护两个Fragment
1MultiSelectContactsListFragment(全部)–适配器类MultiSelectEntryContactListAdapter
–数据加载ProfileAndContactsLoader
ContactTileListFragment (收藏)) --适配器类ContactTileAdapter
–数据加载使用Loader?
- Loaders确保所有的cursor操作是异步的,从而排除了UI线程中堵塞的可能性。
- 当通过LoaderManager来管理,Loaders还可以在activity实例中保持当前的cursor数据,也就是不需要重新查询(比如,当因为横竖屏切换需要重新启动activity时)。
- 当数据改变时,Loaders可以自动检测底层数据的更新和重新检索。
3.1.1 全部MultiSelectContactsListFragment(需求标题改为联系人)
MultiSelectContactsListFragment维护了全部联系人列表(搜索列表,多选列表都是同一个,以状态区分并显示). 列表是ListView,通过重写createListAdapter() 方法初始化
进入MultiSelectContactsListFragment中它的祖父类是 ContactEntryListFragment,维护了一个ListView,T mAdapter。并且这个基类实现了LoadManager的接口LoaderCallbacks。
发现此基类实现了LoadManager接口,实现了该接口3个重要的抽象方法:
LoaderManager.LoaderCallbacks<D>
public Loader<D> onCreateLoader(int id, Bundle args);//创建Loader
public void onLoadFinished(Loader<D> loader, D data); //数据加载完毕后的回调方法
public void onLoaderReset(Loader<D> loader);//数据重新加
同时该类也提供了重要的抽象方法
protected abstract T createListAdapter();
//创建适配器Adapter类。意味子类可以按需求创建自己的适配器,完成各个自界面的数据显示,当前联系人自定义类MultiSelectEntryContactListAdapter适配器。
MultiSelectContactsListFragment.class
@Override
protected ContactListAdapter createListAdapter() {
DefaultContactListAdapter adapter = new MultiSelectEntryContactListAdapter(getContext());
adapter.setSectionHeaderDisplayEnabled(true);
adapter.setDisplayPhotos(true);
adapter.setPhotoPosition(
ContactListItemView.getDefaultPhotoPosition(/* opposite = */ false));
return adapter;
}
这里说下LoaderManager与Loades的基本概念
1.LoaderManager
LoaderManager用来负责管理与Activity或者Fragment联系起来的一个或多个Loaders对象.
每个Activity或者Fragment都有唯一的一个LoaderManager实例(通过getLoaderManager()方法获得),用来启动,停止,保持,重启,关闭它的Loaders,这些功能可通过调用initLoader()/restartLoader()/destroyLoader()方法来实现.
LoaderManager并不知道数据如何装载以及何时需要装载.相反,它只需要控制它的Loaders们开始,停止,重置他们的Load行为,在配置变换或数据变化时保持loaders们的状态,并使用接口来返回load的结果.
2.Loader Loades负责在一个单独线程中执行查询,监控数据源改变,当探测到改变时将查询到的结果集发送到注册的监听器上.Loader是一个强大的工具,具有如下特点
(1)它封装了实际的数据载入.ßß
Activity或Fragment不再需要知道如何载入数据.它们将该任务委托给了Loader,Loader在后台执行查询要求并且将结果返回给Activity或Fragment.
(2)客户端不需要知道查询如何执行.Activity或Fragment不需要担心查询如何在独立的线程中执行,Loder会自动执行这些查询操作.
这种方式不仅减少了代码复杂度同事也消除了线程相关bug的潜在可能. (3)它是一种安全的事件驱动方式.
Loader检测底层数据,当检测到改变时,自动执行并载入最新数据.
这使得使用Loader变得容易,客户端可以相信Loader将会自己自动更新它的数据.
Activity或Fragment所需要做的就是初始化Loader,并且对任何反馈回来的数据进行响应.除此之外,所有其他的事情都由Loader来解决
详情链接https://www.cnblogs.com/diysoul/p/5124886.html
回到ContactEntryListFragment中,在执行onCreateView前,在onAttach()中会执行setLayoutManager(),设置 LayouManager的实现类,然后回到MultiSelectContactsListFragment的祖父类DefaultContactBrowseListFragment中,在createCursorLoader()可以看到创建的是new ProfileAndContactsLoader(context),用与数据加载DefaultContactBrowseListFragment.class
@Override
public CursorLoader createCursorLoader(Context context) {
return new ProfileAndContactsLoader(context);
}
之后执行onCreate() 在中初始化调用createListAdapter() 此时的Adpater就是上面提到的MultiSelectEntryContactListAdapter(),并赋值给mAdpater,这里就是子类可以自定义适配器回来的时候,同时初始化ContactsPreferences对象
ContactEntryListFragment<T extends ContactEntryListAdapter>.class
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
restoreSavedState(savedState);
mAdapter = createListAdapter();
mContactsPrefs = new ContactsPreferences(mContext);
}
在onCreateView()中初始化布局,找到ListView,初始化参数,设置适配器mAdapter.此时数据还是空的,直到onStart()方法开始才会正式开始加载数据的过程。
ContactEntryListFragment.class
@Override
public void onStart() {
super.onStart();
//注册ContentObserve子类监听数据变化
mContactsPrefs.registerChangeListener(mPreferencesChangeListener);
...
//设置状态 。 表示状态未加载
mDirectoryListStatus = STATUS_NOT_LOADED;
//仅加载优先目录
mLoadPriorityDirectoriesOnly = true;
//目测开始加载数据
startLoading();
}
到startLoading()方法中
ContactEntryListFragment.class
protected void startLoading() {
...
//Partition这个类持有一个Cursor对象,用来存储数据。
//Adapter持有的Partition,Partition类代表了当前需要加载的Directory,
//可以理解为一个联系人集合,比如说本地联系人、Google联系人……这里我们假设只加载本地联系人数据,所以partitionCount=1。
int partitionCount = mAdapter.getPartitionCount();
for (int i = 0; i < partitionCount; i++) {
Partition partition = mAdapter.getPartition(i);
//字母意思 如果分区是目录分区,目前查都执行到目录分区
if (partition instanceof DirectoryPartition) {
DirectoryPartition directoryPartition = (DirectoryPartition)partition;
//表示状态未加载,这个status在PeopleActivity中的onAttact()初始化后调用几个方法后保存状态
if (directoryPartition.getStatus() == DirectoryPartition.STATUS_NOT_LOADED) {
...
//最后执行到这个方法
startLoadingDirectoryPartition(i);
...
}
}...
}
// 下次调用这个方法时,我们应该开始加载非优先目录
mLoadPriorityDirectoriesOnly = false;
}
private void startLoadingDirectoryPartition(int partitionIndex) {
...
//这是指默认只有本地联系人,
if (directoryId == Directory.DEFAULT) {
//执行开始加载目录分区数据
loadDirectoryPartition(partitionIndex, partition);
} else {
loadDirectoryPartitionDelayed(partitionIndex, partition);
}
...
}
/**
* 开始加载目录分区数据
*/
protected void loadDirectoryPartition(int partitionIndex, DirectoryPartition partition) {
...
//获取LoaderManagerImpl实现类开始执行 。 这里是this就是ContactEntryListFragment实现回调方法LoaderManager.LoaderCallbacks<D>
//加载情况回通过回调返回
getLoaderManager().restartLoader(partitionIndex, args, this);
}
restartLoader()方法说明 这个方法是LoaderManager实现类的方法,参照文档解释:
这个方法会新建/重启一个当前LoaderManager中的Loader,将回调方法注册给他,并开始加载数据。也就是说会回调LoaderManager的onCreateLoader()方法。
Starts a new or restarts an existing android.content.Loader in this
manager, registers the callbacks to it, and (if the activity/fragment
is currently started) starts loading it
进入LoaderManagerImpl实现类的
LoaderManagerImpl.class
public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
LoaderInfo info = mLoaders.get(id);
...
//最后调用这个方法
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
return (Loader<D>)info.mLoader;
}
//进入createAndInstallLoader
private LoaderInfo createAndInstallLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
...
LoaderInfo info = createLoader(id, args, callback);
installLoader(info);
...
}
private LoaderInfo createLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
...
//开始调用回调的第一个方法
Loader<Object> loader = callback.onCreateLoader(id, args);
...
return info;
}
void installLoader(LoaderInfo info) {
//关键方法出现了!LoadManager接口的抽象方法的onCreateLoader方法被回调了!
//然后installLoader方法启动了这个Loader!
// 该活动将在其 onStart() 中启动所有现有加载器,
// 所以只有当我们超过活动的那个点时才从这里开始它们
// 生命周期
info.start();
}
回到ContactEntryListFragment的onCreateLoader()中开始创建Loader
ContactEntryListFragment.class
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
...
//也就是一开始在DefaultContactBrowseListFragment创建的ProfileAndContactsLoader()数据加载器
CursorLoader loader = createCursorLoader(mContext);//创建Loader
//同时这个方法也被重写了DefaultContactListAdapter重写了 查询参数配置
mAdapter.configureLoader(loader, directoryId);//配置Loader
...
}
那么ProfileAndContactsLoader是如何加载数据的呢?
进入其中查看发现
public class ProfileAndContactsLoader extends CursorLoader {
//AsyncTaskLoader的抽象方法
@Override
public Cursor loadInBackground() {}
} //ProfileAndContactsLoader.class
//ProfileAndContactsLoader继承
public class CursorLoader extends AsyncTaskLoader<Cursor> {
//AsyncTaskLoader的抽象方法
@Override
public Cursor loadInBackground() {}
}// CursorLoader.class
//CursorLoader继承
public abstract class AsyncTaskLoader<D> extends Loader<D> {
//当LoaderManagerImpl的installLoader()中调用 info.start();start()方法中会启动 (AsyncTask的使用方法 这个就不深讲了))就会启动这个Task
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
...
//自身的方法 最终子类ProfileAndContactsLoader的loadInBackground()方法就会调用 执行开始前查询
D data = AsyncTaskLoader.this.onLoadInBackground();
...
}
protected D onLoadInBackground() {
return loadInBackground();
}
public abstract D loadInBackground();
} //AsyncTaskLoader.class
回到ProfileAndContactsLoader的loadInBackground()方法
ProfileAndContactsLoader.class
@Override
public Cursor loadInBackground() {
//如果启用,首先加载配置文件。
List<Cursor> cursors = Lists.newArrayList();
if (mLoadProfile) {
//参数配置 查询语句等 下面
cursors.add(loadProfile());
}
if (canLoadExtraContacts() && !mMergeExtraContactsAfterPrimary) {
cursors.add(loadExtraContacts());
}
// ContactsCursor.loadInBackground() 可以返回 null; 合并光标
// 正确处理空游标。
Cursor cursor = null;
...
//指向父类CursorLoader方法 添加查询参数
cursor = super.loadInBackground();
...
final Cursor contactsCursor = cursor;
cursors.add(contactsCursor);
...
return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
@Override
public Bundle getExtras() {
// Need to get the extras from the contacts cursor.
return contactsCursor == null ? new Bundle() : contactsCursor.getExtras();
}
};
}ProfileAndContactsLoader.class
到CursorLoader的loadInBackground()方法查看
CursorLoader.class
@Override
public Cursor loadInBackground() {
...
//异步调用了ContentResolver的query方法,通过这个Query方法,实现了对联系人数据的查询,返回Cursor数据。并绑定了数据监听器。
//这些参数是从哪里配置的:在DefaultContactListAdapter的configureLoader()配置了这些参数
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal);
...
//数据监听器
cursor.registerContentObserver(mObserver);
...
return cursor;
...
}CursorLoader.class
Loader.class 数据监听 中的
//ContentObserver 的一个实现,负责将其连接到 Loader,以便在观察者被告知数据已更改时让加载器重新加载其数据。
// 您通常不需要自己使用它; CursorLoader 使用它来在游标的支持数据更改时执行更新。
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
//数据改变
onContentChanged();
}
}
DefaultContactListAdapter.class . 在ContactEntryListFragment收到onCreateLoader会调用这个方法
@Override
public void configureLoader(CursorLoader loader, long directoryId) {
...
configureUri(loader, directoryId, filter);
loader.setProjection(getProjection(false));
configureSelection(loader, directoryId, filter);
loader.setSortOrder(sortOrder);
...
}
最终查询ContactsProvider2的uri是:
Uri:content://com.android.contacts/contactsaddress_book_index_extras=true&directory=0
查询完毕后,回调LoaderManager的onLoadFinished方法,完成对Ui界面的更新:
onPartitionLoaded(loaderId, data); ContactEntryListFragment.class
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
...
onPartitionLoaded(loaderId, data);
...
}
protected void onPartitionLoaded(int partitionIndex, Cursor data) {
...最终调用Adapter的changeCursor
mAdapter.changeCursor(partitionIndex, data);
...
}
到mAdapter的祖父类CompositeCursorAdapter中
public void changeCursor(int partition, Cursor cursor) {
...
//通知更新
invalidate();
notifyDataSetChanged();
...
}
到这里联系人列表数据分析到此
其他如收藏列表的数据填充基本没什么差别 各自的Adapter,CursorLoader,CursorLoader配置参数不同
3.1.2 联系人列表布局改造
需求是改造列表成图3.1.2-1 查看源码中是通过createView 一个一个创建的,所以先从每个Item改这个Item是ContactListItemView是个自定义View 需要改从里面改造布局
如何找到这个呢
1.最简单的就是通过AS的Layout Inspector工具,启动模拟器 打开应用,可以通过查看View树发现这个Item
2.源码查看
3.1.1我们已经知道这个列表的适配器是MultiSelectEntryContactListAdapter了 ,到它的祖父类ContactListAdapter中可以找到newView方法,当适配器通知更新后,遍历列表数据就会调用到这个方法
ContactListAdapter.class
可以看这个方法是重写父类,并且没有调用 说明是它的父类调用 可以往上找
@Override
protected ContactListItemView newView(
Context context, int partition, Cursor cursor, int position, ViewGroup parent) {
ContactListItemView view = super.newView(context, partition, cursor, position, parent);
...
return view;
}
最终在CompositeCursorAdapter中的getView()中找到
回到ContactListItemView中
这里是通过在onMeasure onLayout方法中重新判断设置位置布局,最终改造
3.1.3 头像图片工具ContactPhotoManager
头像这一块说下,官方是圆形的,需求是需要方形带圆觉,默认图片带首字母或文字是LetterTileDrawable对象图片最终放上去
列表的头像,详情界面,编辑界面都是这个类通过设置mCircularPhotos这个参数决定图片圆形还是圆角.这里说明下mCircularPhotos这个参数:文字意思图片是否为圆形,官方默认列表是圆形true,我改后设置默认false
并且源码是false是方向无原角,改造后有原角 后面在LetterTileDrawable中用到 ,所以可以知道决定头像圆形是在这个类中的
图片这一块统一都是用了ContactPhotoManager这个类,是个抽象类,实际是ContactPhotoManagerImpl实现类
可以从ContactListItemView中发现头像最终是 mPhotoView这个对象,ContactListItemView 方法并没加载图片,所以就看谁调用了mPhotoView
ContactListItemView.class
//外部是通过这个方法找到头像这个对象
public ImageView getPhotoView() {
if (mPhotoView == null) {
mPhotoView = new ImageView(getContext());
...
}
return mPhotoView;
}
查询到了几个类在调用,看到熟悉的一个类ContactListAdapter这个是MultiSelectEntryContactListAdapter的祖父类,可以猜测图片就是在这个类加载的
ContactListAdapter.class
//这个方法有两处调用了view.getPhotoView()
protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
...
// 设置照片(如果存在)
long photoId = 0;
//查询是否存在图片 赋值photoId
if (!cursor.isNull(ContactQuery.CONTACT_PHOTO_ID)) {
photoId = cursor.getLong(ContactQuery.CONTACT_PHOTO_ID);
}
// 如果photoId不等与0 加载个人图片
if (photoId != 0) {
//1.当存在个人照片是调用
getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false,
getCircularPhotos(), null);
} else {
final String photoUriString = cursor.getString(ContactQuery.CONTACT_PHOTO_URI);
final Uri photoUri = photoUriString == null ? null : Uri.parse(photoUriString);
DefaultImageRequest request = null;
if (photoUri == null) {
//这个方法getDefaultImageRequestFromCursor()在父类ContactEntryListAdapter中
//这里是获取用户名和查找键 (表示符:通过LetterTileDrawable.pickColor()算法确认颜色值)
request = getDefaultImageRequestFromCursor(cursor,
ContactQuery.CONTACT_DISPLAY_NAME,
ContactQuery.CONTACT_LOOKUP_KEY);
}
//2.当不存在个人图片,使用默认首字母文字图片
getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
getCircularPhotos(), request);
}
}
ContactEntryListAdapter.class
public DefaultImageRequest getDefaultImageRequestFromCursor(Cursor cursor,
int displayNameColumn, int lookupKeyColumn) {
//用户名 会用户头像首字母
final String displayName = cursor.getString(displayNameColumn);
//查找键 会用于头像背景颜色
final String lookupKey = cursor.getString(lookupKeyColumn);
//mCircularPhotos 改造后默认不为圆形 为方形圆角
return new DefaultImageRequest(displayName, lookupKey, mCircularPhotos);
}
图片加载1就是通过photoId 从数据库找到本地图片
最终放上去 先说2情况
可以从上面看 通过getPhotoLoader() 方法 拿到ContactPhotoManagerImpl对象 这个是在ContactEntryListFragment的setContect()方法中创建
ContactEntryListFragment.class
public void setContext(Context context) {
mContext = context;
configurePhotoLoader();
}
protected void configurePhotoLoader() {
if (mPhotoManager == null) {
//最终创建ContactPhotoManagerImpl对象 getInstacen() ->createContactPhotoManager(){return new ContactPhotoManagerImpl(context);}
mPhotoManager = ContactPhotoManager.getInstance(mContext);
}
}
回到ContactListAdapter中 加载默认图片处
//2.当不存在个人图片,使用默认首字母文字图片
getPhotoLoader().loadDirectoryPhoto(view.getPhotoView(), photoUri, false,
getCircularPhotos(), request);
进入loadDirectoryPhoto(),在ContactPhotoManagerImpl实现类中找到这个方法
ContactPhotoManager.class
//默认DEFAULT_AVATAR
public static DefaultImageProvider DEFAULT_AVATAR = new LetterTileDefaultImageProvider();
public final void loadDirectoryPhoto(ImageView view, Uri photoUri, boolean darkTheme,
boolean isCircular, DefaultImageRequest defaultImageRequest) {
//调用ContactPhotoManagerImpl的loadPhoto 这里注意DEFAULT_AVATAR这个参数,默认的
loadPhoto(view, photoUri, -1, darkTheme, isCircular, defaultImageRequest, DEFAULT_AVATAR);
}
ContactPhotoManagerImpl.class
@Override
public void loadPhoto(ImageView view, Uri photoUri, int requestedExtent, boolean darkTheme,
boolean isCircular, DefaultImageRequest defaultImageRequest,
DefaultImageProvider defaultProvider) {
if (photoUri == null) {
// No photo is needed
//这里的defaultProvider默认 LetterTileDefaultImageProvider();
defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme,
defaultImageRequest);
mPendingRequests.remove(view);
} else {
...
}
} ContactPhotoManagerImpl.class
LetterTileDefaultImageProvider.class
private static class LetterTileDefaultImageProvider extends DefaultImageProvider {
@Override
public void applyDefaultImage(ImageView view, int extent, boolean darkTheme,
DefaultImageRequest defaultImageRequest) {
//在这里配置并获取默认图片 返回的是LetterTileDrawable
final Drawable drawable = getDefaultImageForContact(view.getResources(),
defaultImageRequest);
//加载图片
view.setImageDrawable(drawable);
}
public static Drawable getDefaultImageForContact(Resources resources,
DefaultImageRequest defaultImageRequest) {
final LetterTileDrawable drawable = new LetterTileDrawable(resources);
if (defaultImageRequest != null) {
// 如果联系人标识符就是一开始的lookupKey为 null 或为空,则回退到
// 显示名称。 在这种情况下,请使用 {@code null} 作为联系人的
// 显示名称,以便使用默认位图而不是
// 信
if (TextUtils.isEmpty(defaultImageRequest.identifier)) {
drawable.setLetterAndColorFromContactDetails(null,
defaultImageRequest.displayName);
} else {
//设置设置首字母(源码是只获取首字母,改造在里面添加查找中文首字母方法)和背景颜色(通过标识符)
drawable.setLetterAndColorFromContactDetails(defaultImageRequest.displayName,
defaultImageRequest.identifier);
}
//设置联系人类型
drawable.setContactType(defaultImageRequest.contactType);
//设置比例
drawable.setScale(defaultImageRequest.scale);
//设置头像首字母偏移量 调节首字母位置
drawable.setOffset(defaultImageRequest.offset);
//设置是否为圆形
drawable.setIsCircular(defaultImageRequest.isCircular);
}
return drawable;
}
}
进入LetterTileDrawable中
LetterTileDrawable.class
@Override
public void draw(final Canvas canvas) {
...
//绘制图片
drawLetterTile(canvas);
}
private void drawLetterTile(final Canvas canvas) {
...
//改造后
//判断是否为圆形 做出绘制
if (mIsCircle) {
canvas.drawRect(bounds, sPaint);
Bitmap b = getRoundBitmapByShader(DEFAULT_PERSON_AVATAR, bounds.width(), bounds.height(), 20, 0);
final Rect rectSrc = new Rect(0, 0, b.getWidth(), b.getHeight());
final Rect rectDest = new Rect(0, 0, bounds.width(), bounds.height());
canvas.drawBitmap(b, rectSrc, rectDest, mPaint);
sPaint.setTextSize(0.5f * sLetterToTileRatio * minDimension);
offsetY = (float) (bounds.height() * 0.08);
// canvas.drawCircle(bounds.centerX(), bounds.centerY(), minDimension / 2, sPaint);
} else {
int radius = UiUtils.dip2px(6);
//TODO:需求圆角
canvas.drawRoundRect(new RectF(bounds), radius, radius, sPaint);
sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension);
}
...
}
默认头像的话就到这里。
新建/编辑联系人 下一篇出