読者です 読者をやめる 読者になる 読者になる

なるようになるかも

力は多くの場合、その人の思いを超えない。

スマートフォンの連絡帳の話。

純正の連絡帳で別に満足してるって人もいれば、オリジナルの連絡帳を入れてる人もいますし、そもそも連絡帳に何も登録されてない人もいるでしょう。

それはさておき、iOSAndroid の OS 標準の連絡帳はユーザー視点で見るか、開発者視点で見るかで貧弱とも言えるし、高性能とも言えて面白いです。

連絡帳の基本思想

iOS / Android に共通して言えることとして、実用的ではないです。グルーピング表示すらできません。

その理由は、OS 標準の連絡帳は、端末内の利用出来る情報を活用しつつ、様々なアプリへ連絡帳情報を提供するためのデータベースとなるよう、割り切った実装になっているからです。

連絡帳の元データは、多種多様です。

Android であれば Google アカウント、iOS であれば iCloud アカウントに紐付いていることが多いと思います。

その他にも SIM カードやソーシャルアカウントなど複数のデータソースを持つ場合があります。連絡帳はそれらの情報を収集し、統合した連絡先を表示しています。

例えば適当な数字ですが、Skype のフレンドが5000人いて、Facebook の友達が5000人いて、Google アカウントに5000人登録されていたとします。連絡帳はそれらのアカウント情報の断片から名寄せを行い、統合された7500人のデータを表示します。「自分」が3人に分裂したりすることは、きっとありません。

このような処理はリアルタイムではできません。このため、事前にデータベースに蓄積しています。高速に取得するために、インデックスを貼り、非同期で行われる更新をトリガーに、情報の紐付けを更新しています。これだけ考えるとものすごい高性能です。

そしてグルーピング機能が貧弱なのも説明できます。API 的には iOS でも Android でもグループを設定することはできるのです。ただしデータソース元がグルーピング機能を持っているとは限りませんし、また統合された連絡先をグルーピングすると、異なるデータソース間をまとめてしまう可能性があるので GUI としての提供は限定的なのです。

テーブルから理解する連絡帳

データは SQLite で保持されています。なので SQL の民であれば、ER 図を見れば余裕で理解できるはずです。

Android

いっぱいテーブルがあるんですけれど、だいたい下の3つがわかってれば問題ないという噂。

f:id:quesera2:20160622233507p:plain

contactテーブルに統合された連絡先データがあります。

統合人格は名寄せされた複数のraw_contactテーブルと関連付けられています。

実際の各データはdataテーブルに入っています。このdata1data15という汎用的で素敵なレコード名で察せられるかと思いますが、名前の場合、「data1に表示名、data7に名前のフリガナ」、電話番号だったら「data1に電話番号、data2に種別(携帯か固定電話か)」みたいな感じです。

連絡帳のContent Providerへ問い合わせをするときには、このテーブル構造を覚えておくと少しだけ便利です。ほとんどの場合、単体のテーブルではなく、JOINしたテーブルに対する問い合わせになります。SQL を意識すれば無駄を省くことができます。

以下、いくつかポイントです。

lookup

contactのプライマリキーは_id列ですが、これはただの Android の作法で、実際の一意キーはlookupです。これの使い方にも癖がありますけど、リファレンス読めば大丈夫。

sort_keysort_key_alt

日本のロケールで見たとき、連絡帳は「山田太郎」さんと「Jhon Doe」さんのどちらが上であるのが自然でしょうか?

単純な文字列のソートでは、数字、英語、ひらがな、漢字になります。sort_keyは読み仮名などを考慮した上で、それぞれの国のロケールに合わせたソート順で連絡帳データを並び替えるためのものです。連絡帳データは件数が予期できないので、複雑な条件でソートをするよりも事前にソート用の値を持っておいたほうが楽なわけですね。

sort_key_altロケールに依存しないソートをしたい場合に利用します。

name_lookupphone_lookup

名前や電話番号は表記ブレがおきます。080-3123-2931と書いたり、(080)31232931と書いたり。

名前や電話番号を正規化して保持しているルックアップテーブルが存在します。氏名や電話番号から検索する場合などは、contactテーブルから辿るよりも、こちらを使うほうが適切です。

iOS

iOS の連絡帳の構造はすごく単純です。外部キー制約も一切ないです。

f:id:quesera2:20160622233516p:plain

統合された連絡先情報を、ABPersonテーブルに保持しています。複数のデータを持つ場合(例えば電話番号が自宅と携帯と会社で3つあるなど)は、ABMultiValueでラベルと値を保持しています。

Androidraw_contactに相当するテーブルはなく、統合された結果だけを保持しています。

なので統合された情報が何に由来してきているのかを知る術があまりなさそうで、ABStoreから知ることができる情報も、LocalExchangeCardDAVかみたいな超ざっくりとした情報しか持っていません。

iOS の場合、AddressbookUI/ContactsUI フレームワークが用意されているため、あまり気にすることもないでしょう。

Addressbook フレームワークはほぼ C 言語な感じで、Objective-C からでも使いづらいのに、Swift から使うのはチャレンジャーな感じがありましたが、iOS9 から Contact フレームワークに置き換わって楽になりました。

FirstSortLastSort

この中身は姓名でソートするために事前に計算されたキーです。

A)D=みたいに文字を並び替えていっているみたいですね。FirstSortLanguageIndexLastSortLanguageIndexという列もあって、これを組み合わせてロケール対応しているのでしょうか。

iOS の場合、テーブルへの問い合わせはNSPredicateというブラックボックスを介するので、Android ほど SQL 感はありません。しかしユーザーコード上でソートするよりも、sortOrderを適切に渡した方が何かと無難だとは思います。