Android開発のちょっとしたお話
こんにちは。新卒入社で今年から働き始めました、横幕です。現在は、mixiのAndroid(TM)版公式クライアントアプリを開発しています。
Android開発を始めてから数か月になりますが、今回は、開発に携わる中で知ったことをご紹介したいと思います。
レイアウトの複雑さで発生するStackOverFlowError
Androidでは、見た目(UI)のデザインやレイアウトをXMLで記述することができます。XMLを書くときには、UIのパーツ(ウィジェット:ボタンやチェックボックスなど)のほか、ウィジェットの配置を決めるためのコンテナ(LinearLayoutやFrameLayoutなど)を用います。そして、それらを入れ子にしながら画面を設計していきます。
たとえば、以下のような感じに。
* main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ボタン"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="テキスト"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.3"
android:text="ホゲホゲ"/>
<Button
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:text="フガフガ"/>
</LinearLayout>
</LinearLayout>
また、XMLで画面を作っていると、あるパーツは他の画面でも使いたいので、その部分だけ切り出したいということがよくあります。
そのような場合には、切り出したい部分だけを記述したXMLを用意し、使いたい場所でインクルードするという方法が利用できます。
* main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="ボタン"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="テキスト1"/>
<include layout="@layout/hogehoge"/>
</LinearLayout>
* hogehoge.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.3"
android:text="テキスト2"/>
<Button
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="0.7"
android:text="テキスト3"/>
</LinearLayout>
入れ子の深さにご用心
さて、このようにしながら画面を作っていると、コンテナの入れ子が深くなったり、複雑になったりしてきます。
特に、コンテナの入れ子の深さには注意が必要です。
というのも、あまりにコンテナの入れ子が深くなりすぎると、StackOverFlowErrorが発生して強制終了の原因になるからです。
パーツごとに切り出してXMLを書いている場合には、特に注意が必要です。
Android 1.5の場合、Androidが持っているスタックの大きさは8KBになっています。
このため、TabHostを使って画面を作った場合に、12回程度の入れ子を作るとStackOverFlowErrorになる例もあったようです。
検証してみる
では、Android 2.xになった現在、具体的にどの程度の深さでStackOverFlowが発生するのか、手持ちのGalaxy S2(Android2.3.3)で検証してみました。
まず始めに、LinearLayoutの入れ子を単純に増やし続けてみます。
* main.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">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
...
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="こんにちは"/>
</LinearLayout>
...
</LinearLayout>
</LinearLayout>
すると、25回の入れ子を作ったところでアプリがStackOverFlowErrorで強制終了しました。
次に、ListViewを使って、Listの各要素のレイアウトに入れ子構造を作った場合にどうなるか試してみました。
* MainActivity
package com.test.app02;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class MainActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String[] data = new String[]{"hoge", "fuga"};
ListView list = (ListView)findViewById(R.id.ListView);
list.setAdapter(new MyAdapter(this, R.layout.list_item, data));
}
private class MyAdapter extends ArrayAdapter {
private String[] data;
private LayoutInflater inflater;
public MyAdapter(Context context, int resourceId, String[] data) {
super(context, resourceId, data);
this.data = data;
this.inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
v = inflater.inflate(R.layout.list_item, null);
}
TextView text = (TextView)v.findViewById(R.id.string);
text.setText(data[position]);
return v;
}
}
}
* main.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">
<ListView
android:id="@+id/ListView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
* list_item.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">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
...
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="こんにちは"/>
</LinearLayout>
...
</LinearLayout>
</LinearLayout>
今度は、list_item.xmlの中のLinearLayoutを22回入れ子にしたところでStackOverFlowErrorとなりました。
最後に、TabHostを使って試してみました。
* MainActivity
package com.test.app03;
import android.app.TabActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
public class MainActivity extends TabActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TabHost tabHost = getTabHost();
LayoutInflater.from(this).inflate(R.layout.main, tabHost.getTabContentView(), true);
TabSpec tab1 = tabHost.newTabSpec("Tab1");
tab1.setIndicator("1");
tab1.setContent(R.id.tab1);
TabSpec tab2 = tabHost.newTabSpec("Tab2");
tab2.setIndicator("2");
tab2.setContent(R.id.tab2);
tabHost.addTab(tab1);
tabHost.addTab(tab2);
tabHost.setCurrentTab(0);
}
}
* main.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">
<TabHost
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include
layout="@layout/tab_content"
android:id="@+id/tab1"/>
<include
layout="@layout/tab_content"
android:id="@+id/tab2"/>
</FrameLayout>
</TabHost>
</LinearLayout>
* tab_content.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">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
...
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="こんにちは"/>
</LinearLayout>
...
</LinearLayout>
</LinearLayout>
TextViewが入れ子の中に1つあるだけの単純な構造ですが、20回の入れ子を作った所でStackOverFlowErrorとなりました。
今回は、単純にコンテナの入れ子を増やすだけで検証してみましたが、実際には、もっと複雑な作りになるはずですし、レイアウトをプログラムで動的に制御したいというような場面も出てくると思います。
そのようなときに、StackOverFlowErrorに出くわしたら、レイアウトの入れ子が深くなりすぎていないか、
Hierarchy Viewerなどを使って探ってみると良いかもしれません。
(ちなみに、Android4.0のエミュレータ上で実行したところ、どのコードもすんなり実行することができました。)
終わりに
今回は、かなりこまごまとしたお話になりましたが、いかがでしたでしょうか。
この記事が何かのお役に立つことが出来れば幸いです。