Linkifyのリンクからパラメーターを渡してActivityを呼び出す処理が必要になり、ググッたらContentProviderの理解が必要だとわかりました。 しかし、ContentProviderの情報は色々と分散していて理解しにくかったので、まとめます。
・ContentProviderとは?
ContentProviderを利用すると、異なるアプリケーション間でデータを共有できる。
COntentProviderを利用することで、特定のデータタイプを他のアプリケーションが利用できる。
例えば、Androidで利用されている正式なContentProviderには、電話帳がある。
電話帳には、名前、住所、電話番号がリストで表示されている。
content://contacts/people/
という特定のURIを利用することで、どのアプリケーションであってもこのデータにアクセスすることができる。
androidURIの構成は以下の通り。
スキーム | content:// |
オーソリティ | contacts |
パス | people |
WEBのURIとほぼ同じですね。オーソリティがホストにあたります。ということで、実装して試してみます。
開発用os(jar)は現在メインで開発している2.1、エミュレータは2.3で確認
1、まずはエミュレーターを立ち上げて電話帳にデータを投入
2、では、実装していきます。
2-1、
AndroidManifest.xmlに
<uses-permission android:name="android.permission.READ_CONTACTS" />
を記述。
2-2、
Activityクラスを作成します。
以下ソースコードです。
public class ProviderTest extends ListActivity {
public static final String TAG = "ProviderTest";
private ArrayList<ContactPojo> list;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.provider);
}
@Override
public void onStart() {
super.onStart();
Log.d(TAG, "onStart start ");
ContentResolver resolver = this.getContentResolver();
Cursor c = resolver.query(Contacts.CONTENT_URI, new String [] {Contacts.DISPLAY_NAME} , null, null, null);
this.list = new ArrayList<ContactPojo>();
while(c.moveToNext()) {
ContactPojo contactPojo = new ContactPojo();
contactPojo.display_name = c.getString(0);
this.list.add(contactPojo);
}
if (c != null) {
c.close();
}
Log.d(TAG, "onStart end ");
}
@Override
public void onResume() {
super.onResume();
setListAdapter(new ProviderTestList(this, R.layout.provider_list, this.list));
}
class ProviderTestList extends ArrayAdapter<ArrayList<ContactPojo>> {
private Context mContext;
private ArrayList<ContactPojo> list;
public ProviderTestList(Context context, int textViewResourceId, ArrayList<ContactPojo> list) {
super(context, textViewResourceId);
this.mContext = context;
this.list = list;
}
@Override
public int getCount() {
return this.list.size();
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
ViewWapper viewWapper = null;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.provider_list, parent, false);
viewWapper = new ViewWapper(row);
row.setTag(viewWapper);
} else {
viewWapper = (ViewWapper)row.getTag();
}
ContactPojo contactPojo = (ContactPojo)this.list.get(position);
viewWapper.getDispName().setText(contactPojo.display_name);
contactPojo = null; // release
return row;
}
}
/**
* ホルダーパターンView
*/
class ViewWapper {
View base;
TextView disp_name;
ViewWapper (View base) {
this.base = base;
}
TextView getDispName() {
if (disp_name == null) {
disp_name = (TextView)base.findViewById(R.id.disp_name);
}
return disp_name;
}
}
}
一覧を表示するように、ListActivityで作成してます。
特に重要なのは
- getContentResolverでContentResolverを取得していること。
- Cursorオブジェクトを取得していること。
です。
続いて表示xmlのprovider.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@id/android:empty"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="データはありません" />
</RelativeLayout>
加えて、リストxmlのprovider_list.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/disp_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</RelativeLayout>
上記のソースをビルドして実行します。
上記のような感じで出力されます。
ContentResolverのqueryで指定してる「Contacts.CONTENT_URI」が電話帳のデータにアクセスする為のURIです。Contacts.CONTENT_URIのURIの形式を取得してみましょう。以下が結果。
- URIスキーム(Contacts.CONTENT_URI.getScheme()):content
- ホスト名(Contacts.CONTENT_URI.getHost()):com.android.contacts
- パス(Contacts.CONTENT_URI.getPath()):/contacts
となります。…というか、URI違うじゃん。調査したところ、androidのversionによって違うみたいです。
確認のために、自分でURIを作成してアクセスしてみます。
getContentResolverの後ろのコードに以下の処理を追加します。
ContentResolver resolver = this.getContentResolver();
Uri.Builder builder = new Uri.Builder();
builder.scheme("content");
builder.authority("com.android.contacts");
builder.path("contacts");
//Cursor c = resolver.query(Contacts.CONTENT_URI, new String [] {Contacts.DISPLAY_NAME} , null, null, null);
Cursor c = resolver.query(builder.build(), new String [] {Contacts.DISPLAY_NAME} , null, null, null);
ビルドして実行します。
同じ結果になりましたね。
以上の処理から理解できるのは、URIで電話帳にアクセスすることができ、結果をCursorオブジェクトで取得していることですね。
気になるのは、ContentProviderがCursorを返す仕組みになっていることです。
データベースを通さない処理の場合はどうするのでしょうか…。また、intentを利用する仕組みとどう関連があるのか…。
とにかく、ContentProviderは理解したのでLinkifyとintentを関連付けるサンプルを作成していきます。
3、Linkfyサンプルを十分に読み込んで実装していきます。
参照するのは
http://android-developers.blogspot.com/2008/03/wikinotes-for-android-routing-intents.html
です。
まずは文字列リンクを作成します。
getViewメソッドのContactPojoオブジェクトを取り出している下に、以下のコード処理を追加します。
ContactPojo contactPojo = (ContactPojo)this.list.get(position);
// viewWapper.getDispName().setText(contactPojo.display_name);
Pattern matcher = Pattern.compile("テスト");
viewWapper.getDispName().setText(contactPojo.display_name);
String viewURL = "content://jp.co.sample.android.muzukashii/name/";
Linkify.addLinks(viewWapper.getDispName(), matcher, viewURL);
ビルドして実行します。
おお、リンクが作成されましたね。
次はAndroidManifest.xmlに
<provider android:name="jp.co.sample.LinkTestProvider" android:authorities="jp.co.sample.android.muzukashii" />
を追記します。
- android:authoritiesは、URIのホストと同じ意味です。URIのROOTとして扱われます。
- android:nameはjavaクラスのフルパスを指定します。
では、呼び出されるLinkTestProviderクラスを作成していきましょう。このクラスでは、ContentProviderクラスを継承します。
注意すべき点は、updateやinsertをOverrideする必要があるということです。なんかずさんな抽象化ですね。DB使わない人もいるでしょうに。
気持ち悪いけどここは無視して作成します。で、作成したのが以下のコードです。
public class LinkTestProvider extends ContentProvider {
public static final String TAG = "LinkTestProvider";
public static final String AUTHORITY =
"jp.co.sample.android.muzukashii";
private static final int NOTE_NAME = 2;
private static final UriMatcher URI_MATCHER;
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
switch (URI_MATCHER.match(uri)) {
case NOTE_NAME:
return "hatoyama.kan.owawa/minsyu.kiero";
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
static {
URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
URI_MATCHER.addURI(AUTHORITY, "name/*",
NOTE_NAME);
}
}
上記のコードで重要なのは、staticとgetTypeメソッドです。
- staticでUriMatcherにURIを作成しています。
- 投げるURIに応じたUriMatcherを全て作成します。
URIはProviderTestのgetViewメソッドに追加した以下のソースと対応しています。
Pattern matcher = Pattern.compile("テスト");
viewWapper.getDispName().setText(contactPojo.display_name);
String viewURL = "content://jp.co.sample.android.muzukashii/name/";
Linkify.addLinks(viewWapper.getDispName(), matcher, viewURL);
getTypeメソッドで、Uriを振り分ける処理を行っています。
色々なパターンで分岐可能なので、以下の公式サイトのソースを参考にしてみてください。
http://developer.android.com/reference/android/content/UriMatcher.html
上記のソースではcase NOTE_NAME:に処理が入り、「hatoyama.kan.owawa/minsyu.kiero」文字列が返却されます。
この文字列はMIMEタイプを表しています。なので、このMIMEタイプによって呼び出されるActivityクラスを作成するようにします。
作成したのが以下のクラスです。
public class GoalActivity extends Activity {
private TextView sendparam;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.goal);
this.sendparam = (TextView)findViewById(R.id.sendparam);
Intent intent = getIntent();
Uri uri = intent.getData();
if (uri != null) {
this.sendparam.setText(uri.getPath());
}
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onResume() {
super.onResume();
}
}
上記の処理では、URIのパスを取得して画面に表示しています。
続いて表示xmlのgoal.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/sendparam"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
最後にandroidManifest.xmlにGoalActivityのintentを設定します。
<activity android:name=".GoalActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="hatoyama.kan.owawa/minsyu.kiero"/>
</intent-filter>
</activity>
intent-filterを設定します。android:mimeTypeは、LinkTestProviderクラスのgetTypeメソッドで返すMIMEタイプ文字列に対応します。
では、上記のソースをビルドして実行します。
続いて、リンク文字をタップします。
おお。見事にActvityが呼び出されて画面遷移できましたね。
URIのpathもきちんと取得できています。
上記の処理から理解できるのは、
- 文字リンクのパターンを利用して特定のURIを作成することで、activityの呼び出しを制御できる。
- Providerクラスで受けとったURIをgetTypeメソッドで分岐し、任意のactivityを呼び出すことが出来る。様々なパラメーターも付加出来る。
これまでやってきたことを応用することで、WEBのような処理を行うUIが色々作れそうですね。
かなり長くなってしまいましたが、これで終了です。お疲れ様でした。
android開発にそれなりに通じていないと、内容的に少し難しかったかもしれませんが、すごく役に立つパターンだと思うので、是非勉強してみてください。
みなさんが素晴らしいアプリを開発して、僕を唸らせてくれることを期待しています。
でわ。
追伸
URIの概念がよくわからないという人は以下の書籍がオススメです。WEB開発に携わるエンジニアは是非是非読んでもらいたい名著です。
この記事がお役にたちましたらシェアをお願いします