好的,坏的和丑陋的三种方法来在您的android应用程序中加载联系人

In this article we are going to take a deep dive into loading phone contacts in your Android application — we will explore different approaches to achieve this and analyze the pros and cons of each of these approaches.

在本文中,我们将深入研究如何在Android应用程序中加载电话联系人-我们将探索实现此目的的不同方法,并分析每种方法的优缺点。

NOTE: All code excerpts require permission to read contacts and the first two need to be executed on a background thread in order to work. If you are not familiar with the concept of runtime permissions or background thread execution, follow the links.

注意:所有代码摘录均需要读取联系人的权限,并且前两个代码必须在后台线程上执行才能工作。 如果您不熟悉 运行时权限 后台线程执行 的概念,请 单击 链接

坏人 (The Bad)

Recently I have been refactoring an application which, beside other functionalities and features, loads and displays phone contacts. This application was loading contacts using one of the most common approaches (if you check StackOverflow for this matter), and sadly, one of the worst approaches a developer can take to load phone contacts in Android. The code looked something like this (which is actually an accepted answer on StackOverflow with over 180 upvotes at the moment I am writing this post):

最近,我一直在重构一个应用程序,除了其他功能和特性之外,该应用程序还可以加载并显示电话联系人。 此应用程序使用的是最常用的方法之一(如果您检查了StackOverflow)来加载联系人,而可悲的是,开发人员可以采用的最差的方法之一来在Android中加载电话联系人。 代码看起来像这样(实际上,这是StackOverflow上可接受的答案 ,在撰写本文时,它的投票超过180次):

private void getContactList() {
ContentResolver cr = getContentResolver();
Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,
null, null, null, null);
if ((cur != null ? cur.getCount() : 0) > 0) {
while (cur != null && cur.moveToNext()) {
String id = cur.getString(
cur.getColumnIndex(ContactsContract.Contacts._ID));
String name = cur.getString(cur.getColumnIndex(
ContactsContract.Contacts.DISPLAY_NAME));
if (cur.getInt(
cur.getColumnIndex(
ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
Cursor pCur = cr.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID
+ " = ?",
new String[]{id}, null);
while (pCur.moveToNext()) {
String phoneNo = pCur.getString(pCur.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.i(TAG, "Name: " + name);
Log.i(TAG, "Phone Number: " + phoneNo);
}
pCur.close();
}
}
}
if(cur!=null){
cur.close();
}
}

Looks familiar? If you run this code, it will work and it will give you results, but at what cost? If you take a better look at the code above, you will notice it queries the database once to get the contact ID for each contact, and then for each contact that has a phone number, it queries the database yet again to get contact details. Let’s say you have 1000 contacts in your phone and all of them have phone numbers. Running this code means you will query the database 1001 times (once to get all contacts and then once for each of the 1000 contacts). Seems like an overkill for fetching contacts from the phone, no?

看起来很熟悉? 如果您运行此代码,它将可以正常工作,并且可以为您带来结果,但是费用是多少? 如果您对上面的代码有更好的了解,您会发现它一次查询数据库以获取每个联系人的联系人ID,然后对于每个具有电话号码的联系人,它再次查询数据库以获取联系人详细信息。 假设您的手机中有1000个联系人,并且所有人都有电话号码。 运行此代码意味着您将查询数据库1001次(一次获取所有联系人,然后每1000个联系人一次)。 似乎从电话中获取联系人太过刻意了,不是吗?

Now, let’s up the ante and say your application is used by a busy business person that has 30000 contacts in his or her phone and your application runs the code above. It will take the application several minutes, depending on phone performance, to load the contacts from the phone and present it to your user. Yes, you read that right, I said minutes, not seconds. If you find a user that patient, I would like to meet him, or her.

现在,让我们举个例子,假设您的应用程序被一个繁忙的业务人员使用,他的电话中有30000个联系人,并且您的应用程序运行上面的代码。 根据手机的性能,该应用程序将花费几分钟的时间从手机中加载联系人并将其显示给用户。 是的,您没看错,我说的是分钟 ,而不是秒。 如果您找到该患者的用户,我想见见他或她。

Before, I said we were going to discuss pros and cons of each of these approaches. Well, there are no pros to this approach. The cons? Really slow, unnecessary complicated and wasteful, resource-wise. Managing a background thread seems insignificant among the other problems, but it is a problem

之前,我说过我们将讨论每种方法的利弊。 好吧,这种方法没有优点。 缺点 ? 真的很慢,不必要的复杂和浪费,在资源方面。 在其他问题中,管理后台线程似乎无关紧要,但这是一个问题

丑陋的 (The Ugly)

I know I am not following the order from the title, but I like building up from the bad and work our way to the good. Now, let’s look at the “ugly” approach. Although this may not be the most beautiful way to do the job, this approach has its qualities. Yes, this one isn’t all bad.

我知道我没有遵循标题的顺序,但是我喜欢从坏处逐步发展,并朝着好的方向努力。 现在,让我们看一下“丑陋”的方法。 尽管这可能不是完成工作的最漂亮方法,但是这种方法具有其特质。 是的,这还不是全部。

This approach removes the big problems of the previous one. We will not be querying the database too much, in fact, we will reduce the 1001 query to just two and still get all the information we need. “Why ugly”, you might ask? Well, because this approach’s qualities are on the inside, not the outside. But, don’t judge a book by its cover. Let’s take a look:

这种方法消除了前一个方法的大问题。 我们将不会查询太多数据库,实际上,我们会将1001查询减少到只有两个,并且仍会获得所需的所有信息。 您可能会问:“为什么难看”? 好吧,因为这种方法的特质在于内部,而不是外部。 但是,不要凭封面来判断一本书。 让我们来看看:

private void getContactsList() {
ContentResolver resolver = getContentResolver();
Map<Long, List<String>> phones = new HashMap<>();
Cursor getContactsCursor = resolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
new String[]{
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.NUMBER},
null, null, null);
if (getContactsCursor != null) {
while (getContactsCursor.moveToNext()) {
long contactId = getContactsCursor.getLong(0);
String phone = getContactsCursor.getString(1);
List<String> list;
if (phones.containsKey(contactId)) {
list = phones.get(contactId);
} else {
list = new ArrayList<>();
phones.put(contactId, list);
}
list.add(phone);
}
getContactsCursor.close();
} getContactsCursor = resolver.query(
ContactsContract.Contacts.CONTENT_URI,
new String[]{
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.PHOTO_URI},
null, null, null);
while (getContactsCursor != null &&
getContactsCursor.moveToNext()) {
long contactId = getContactsCursor.getLong(0);
String name = getContactsCursor.getString(1);
String photo = getContactsCursor.getString(2);
List<String> contactPhones = phones.get(contactId);
if (contactPhones != null) {
for (String phone :
contactPhones) {
addContact(contactId, name, phone, photo);
}
}
}
}

Doesn’t look the best, but it does the job very well. In short, we first query the database for list of all phone contacts and add them to a map. The key for the map is the contact id and the value is a list of the phone numbers the contact has (one contact can have more than one phone number, as we already know). Then we query the database once again, asking for details for the contacts, such as display name and photo uri in this case, and the contact id. Then we get the phone numbers for the contact id from the map and we that is it, we have all the details for the contact we need, by using only two queries.

看起来不是最好的,但是做得很好。 简而言之,我们首先在数据库中查询所有电话联系人的列表,然后将其添加到地图中。 映射的键是联系人ID,值是该联系人拥有的电话号码的列表(众所周知,一个联系人可以拥有多个电话号码)。 然后,我们再次查询数据库,询问联系人的详细信息,例如在这种情况下的显示名称照片uri ,以及联系人ID。 然后,我们从地图上获取联系人ID的电话号码,就是这样,仅使用两个查询 ,我们便有了所需联系人的所有详细信息。

If we run this code on the phone of that business person with 30000 contacts, it will execute within few seconds. I believe you will agree that’s quite an improvement.

如果我们在具有30000个联系人的业务人员的电话上运行此代码,它将在几秒钟内执行。 我相信您会同意这是一个很大的进步。

Pros: Fetches contacts in relatively short time, doesn’t waste resources.

优点:在相对较短的时间内获取联系人,而不会浪费资源。

Cons: Manual work — handling background threads. Slower than “the good” approach.

缺点:手动工作-处理后台线程。 比“好的”方法慢。

善良 (The Good)

Finally, let’s take a look at the last (and definitely not the least important) approach on our our list — the good one. This approach utilizes the Loader API which allows loading data from a content provider. This Loaders are executed on a background thread, so you don’t have to worry about managing threads, so that’s a good start.

最后,让我们看一下最后一个( 绝对不是 我们对我们的名单上最重要的)方法- 好一个 。 这种方法利用了Loader API ,该API允许从内容提供商加载数据。 该加载程序在后台线程上执行,因此您不必担心管理线程,因此这是一个不错的开始。

It is good to mention that this approach is preferred by Google (for now) and is explained in detail in the official Android documentation. The instructions encourage the developers to use CursorAdapter, and you can do that, but in this example we will follow the same pattern as in the two previous examples, for the sake of consistency. The following code is a part of the sample application you can find at the end of this article.

值得一提的是,这种方法已被Google首选(目前),并在Android官方文档中进行了详细说明 。 这些说明鼓励开发人员使用CursorAdapter,您可以这样做,但是在本示例中,为了保持一致性,我们将遵循与前两个示例相同的模式。 以下代码是本文末尾的示例应用程序的一部分。

This is how loading contacts looks like when using Loaders to do the job for you:

这是使用加载程序为您完成工作时加载联系人的样子:

public class TheGoodFragment extends TheNeutralFragment implements LoaderManager.LoaderCallbacks<Cursor> {     public String[] PROJECTION_NUMBERS = new String[]   
{ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.NUMBER}; public String[] PROJECTION_DETAILS = new String[]
{ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.PHOTO_URI}; public static TheGoodFragment newInstance() {
return new TheGoodFragment();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LoaderManager.getInstance(this).initLoader(0, null, this);
}
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id,
@Nullable Bundle args) {
startLoading();
switch (id) {
case 0:
return new CursorLoader(
getActivity(),
ContactsContract.CommonDataKinds.
Phone.CONTENT_URI,PROJECTION_NUMBERS,
null,
null,
null
);
default:
return new CursorLoader(
getActivity(),
ContactsContract.Contacts.CONTENT_URI,PROJECTION_DETAILS,
null,
null,
null
);
}
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader,
Cursor data) {
switch (loader.getId()) {
case 0:
phones = new HashMap<>();
if (data != null) {
while (!data.isClosed() && data.moveToNext()) {
long contactId = data.getLong(0);
String phone = data.getString(1);
List<String> list;
if (phones.containsKey(contactId)) {
list = phones.get(contactId);
} else {
list = new ArrayList<>();
phones.put(contactId, list);
}
list.add(phone);
}
data.close();
}
LoaderManager.getInstance(TheGoodFragment.this)
.initLoader(1,null,this);
break;
case 1:
if (data!=null) {
while (!data.isClosed() && data.moveToNext()) {
long id = data.getLong(0);
String name = data.getString(1);
String photo = data.getString(2);
List<String> contactPhones =
phones.get(contactId);
if (contactPhones != null) {
for (String phone :
contactPhones) {
addContact(id, name, phone, photo);
}
}
}
data.close();
loadAdapter();
}
}
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
}
}

As you can see, the code is relatively well-structured (it definitely can be improved) and utilizes different callbacks. fWe are using two loaders, one to get the phone numbers from the database, and one to get the details for each contact, similar to what we did before. Each of the loaders has its id, so we can identify which loader has finished and which needs to be executed. Once the first loader finishes, we add the contacts in a HashMap and execute the second loader and repeat the process explained in the previous approach. And if you don’t want to deal with cursors, you can simply use a CursorAdapter, as I have previously mentioned.

如您所见,代码结构相对良好(肯定可以改进),并利用了不同的回调。 f我们正在使用两个加载程序,一个加载程序从数据库中获取电话号码,一个加载程序获取每个联系人的详细信息,类似于我们之前所做的。 每个加载器都有其ID,因此我们可以确定哪个加载器已完成以及哪个需要执行。 第一个加载程序完成后,我们将联系人添加到HashMap中,然后执行第二个加载程序,并重复前面方法中说明的过程。 而且,如果您不想处理游标,则可以使用CursorAdapter ,就像我之前提到的那样。

Pros: The fastest of the three approaches, no need for manual background thread handling, doesn’t waste resources and is a well-structured approach.

优点 :三种方法中最快的一种,不需要手动后台线程处理,不会浪费资源,并且是结构合理的方法。

Cons: Although it is currently the recommended way to load contacts from the phone, Google have declared Loaders deprecated as of Android P (API 28). In the future, we should be looking to utilize LiveData & ViewModels.

缺点:尽管目前建议使用此方法从手机加载联系人,但Google宣布从Android P(API 28)开始不推荐使用Loaders。 将来,我们应该寻求利用LiveDataViewModels

好人获胜 (The Good Guy Wins)

So, there we have it. The good, the bad, and the ugly approach to loading contacts in Android. Choosing the best method to load contacts can be vital when a user has a significant number of contacts in their phone. When loading a small number of contacts, you may not be able to tell the difference (after all, it’s a matter of milliseconds), so here’s a comparison between the three methods, when loading 200 contacts on Samsung Galaxy A20:

因此,我们有它。 在Android中加载联系人的好,坏和丑陋的方法。 当用户的手机中有大量联系人时,选择最佳的联系人加载方法至关重要。 当加载少量联系人时,您可能无法分辨出差异(毕竟这是毫秒级的问题),因此,这是在Samsung Galaxy A20上加载200个联系人时这三种方法之间的比较:

The Good: 28ms

优点 28ms

The Bad: 3817ms

糟糕: 3817ms

The Ugly: 114ms

丑陋: 114ms

Even for a relatively small number of contacts, you can easily tell the difference and even visually notice the delay when using ‘The Bad’ approach. Although you probably wouldn’t notice it visually, the ‘good’ approach is 4 times faster than the ‘ugly’ approach.

即使使用相对较少的联系人,使用“不良”方法时,您也可以轻松分辨出差异,甚至可以从视觉上注意到延迟。 尽管您可能不会从视觉上注意到它,但是“好”方法比“丑”方法快4倍。

But, this test was too easy, right? Let’s try something more far-fetched. Let’s load 30000 contacts on an older device, such as Samsung A3. Let’s see the results:

但是,这个测试太容易了,对吧? 让我们尝试一些牵强的东西。 让我们在较旧的设备(例如Samsung A3)上加载30000个联系人。 让我们看看结果:

The Good: 917 ms

好: 917毫秒

The Bad: 334255 ms (about 5 and a half minutes)

错误: 334255毫秒(约5分半钟)

The Ugly: 5930 ms

丑陋: 5930毫秒

We are starting to notice the difference, visually. Now, even the most ignorant user will notice the time difference between the good and the ugly, let alone the bad approach.

我们开始在视觉上注意到差异。 现在,即使是最无知的用户也会注意到好与丑之间的时间差,更不用说不良方法了。

Impressive, isn’t it, how a few lines of code can make a huge difference in performance?

令人印象深刻的是,几行代码如何在性能上产生巨大差异?

Remember, (almost) everyone can copy and paste code from the Internet and put together an application, thus calling him or herself a ‘programmer’. You don’t have to be an expert to do that. The difference between a software engineer and a ‘programmer’ is being able to tell the difference between various approaches (nay, algorithms), evaluate them and adapt them, or even come up with a new approach, so that the application executes quickly and efficiently, even when it comes to something as tedious as loading contacts from a phone.

记住,( 几乎 )每个人都可以从Internet复制和粘贴代码并组合成一个应用程序,因此称自己为“ 程序员 ”。 您不必成为专家即可做到这一点。 软件工程师和“ 程序员 ”之间的区别是能够分辨出各种方法(不适用,算法)之间的差异,对其进行评估并加以调整,甚至可以提出一种新方法,从而使应用程序快速有效地执行。 ,即使涉及到从手机加载联系人这样的乏味工作。

I have put together (not developed) a small application to let you test the three different approaches and see the difference for yourself. You can find it on GitHub.

整理了一个 ( 未开发的 )小应用程序,让您测试这三种不同的方法,并亲自了解它们的不同。 您可以在GitHub上找到它

Disclaimer: The application is just an example, it is not well-structured, nor does it use all the latest trends in Android development (i.e. don’t use AsyncTasks, they are deprecated). Please treat it as such and don’t copy-paste the code in your own application.

免责声明 :该应用程序只是一个例子,它的结构不合理,也不使用Android开发中的所有最新趋势(即不使用AsyncTasks,它们已弃用)。 请这样处理,不要将代码复制粘贴到您自己的应用程序中。

TL;DR: Use Loaders for loading contacts into your Android application. Don’t copy-paste stuff off the Internet just to make things work. Think before you code.

TL; DR:使用加载程序将联系人加载到Android应用程序中。 不要只是为了使事情正常进行而从Internet复制粘贴内容。 在编写代码之前请三思。

Follow me for more similar articles related to software engineering and Android development.

跟随我以获取与软件工程和Android开发相关的更多类似文章。

翻译自: https://medium.com/swlh/the-good-the-bad-and-the-ugly-three-approaches-to-loading-contacts-in-your-android-application-c96eaf03ffaf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值