本文主要是介绍Contacts 7.1.1源码解读(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
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.classprotected 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.classpublic <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的抽象方法@Overridepublic Cursor loadInBackground() {}
} //ProfileAndContactsLoader.class//ProfileAndContactsLoader继承
public class CursorLoader extends AsyncTaskLoader<Cursor> { //AsyncTaskLoader的抽象方法@Overridepublic Cursor loadInBackground() {} }// CursorLoader.class//CursorLoader继承
public abstract class AsyncTaskLoader<D> extends Loader<D> {//当LoaderManagerImpl的installLoader()中调用 info.start();start()方法中会启动 (AsyncTask的使用方法 这个就不深讲了))就会启动这个Taskfinal 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()])) {@Overridepublic 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());}@Overridepublic boolean deliverSelfNotifications() {return true;}@Overridepublic 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的changeCursormAdapter.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;//查询是否存在图片 赋值photoIdif (!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 {@Overridepublic void applyDefaultImage(ImageView view, int extent, boolean darkTheme,DefaultImageRequest defaultImageRequest) {//在这里配置并获取默认图片 返回的是LetterTileDrawablefinal 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);}...
}
默认头像的话就到这里。
新建/编辑联系人 下一篇出
这篇关于Contacts 7.1.1源码解读(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!