Android Tips #9 Android Lint の設定を理解する (2)

2012.02.27

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

変更履歴
2012/04/10 ADT17に対応しました。

はじめに

前回 「Android Tips #8 Android Lint の設定を理解する (1)」 に引き続き、 Android Lint でチェックできる項目を確認していきたいと思います。
今回は Typography、Performance、Usability、Icons、Accessibility、Internationalization の各チェック項目を解説していきます。

Usability:Typography

TypographyDashes

重大度 : Warning
優先度 : 5/10
概要 : en ダッシュ( – )または em ダッシュ( — )に変更すべきStringリソースがないかチェックします。
自動修正 : あり
解説 :
en ダッシュ( – )は数値などの範囲指定に、em ダッシュ( — )は文と文の間、字句と字句の間に使われる記号です。
文字コードがASCIIだとどちらも存在しないので en ダッシュは( - )(ハイフン1つ)、 em ダッシュは( -- )(ハイフン2つ)で代用しますが、
Androidではテキストの読みやすさの点から数値の範囲指定は en ダッシュ、文と文の間は em ダッシュ の記号を使うことが推奨されています。
それぞれの表示を比較してみました。

Androidアプリで数値の範囲や文と文の間を表現する記号を使用する場合、en ダッシュ、em ダッシュの記号を使うようにしましょう。

TypographyQuotes

重大度 : Ignore
優先度 : 5/10
概要 : 曲線引用符のシングルクオート( ‘ ’ )またはダブルクオート( “ ” )に変更すべきStringリソースがないかチェックします。
自動修正 : あり
解説 :
シングルクオートとダブルクオートは、引用する文章を囲むときに使用する記号です。
日本語では鍵括弧( 「 」 )で囲みますが、欧文ではシングルクオートとダブルクオートで囲みます。
ちなみに、シングルクオートは引用している文中の引用文を囲うときに使います。
また、欧文の引用符には直線引用符と曲線引用符の2つがあります。
Androidではテキストの読みやすさの点から文章を引用するときの記号は曲線引用符が推奨されています。
直線引用符・曲線引用符の表示を比較してみました。

このチェック項目のデフォルトは Ignore となっているので、チェックしたい場合は設定を変更しましょう。

TypographyFractions

重大度 : Warning
優先度 : 5/10
概要 : 分数を表す記号に置き換えられるStringリソースがあるかチェックします。
自動修正 : あり
解説 :
一般的なフォントでは、下記のような「分数の特殊記号文字」があります。

½, ¼, ¾

Androidではテキストの読みやすさの点から分数の表記は特殊記号文字を使うことが推奨されています。
このチェックでは、特殊記号文字にあるもので、分数の表記に記号を使っていないものがないか確認してくれます。
※ 例えば、「½」を「1/2」のように表記すると警告されます。

TypographyEllipsis

重大度 : Warning
優先度 : 5/10
概要 : 省略符号に置き換えられるStringリソースがないかチェックします。
自動修正 : あり
解説 :
省略符号はある文章の一部のみ記述する場合、その後の文章を省略していることを表す記号として用いられます。
日本語では「三点リーダ」、欧文では「Ellipsis」と呼ばれています。以下のような記号です。

省略符号を使わずに、ピリオド( . )を3つ繋げて表現することもできますが、
Androidではテキストの読みやすさの点から分数の表記は特殊記号文字を使うことが推奨されています。

TypographyOther

重大度 : Warning
優先度 : 3/10
概要 : その他の記号に関する問題がないかチェックします。
自動修正 : あり
解説 :
上記のTypographyDashes、TypographyQuotes、TypographyFractions、TypographyEllipsis 以外の記号に関する問題がないかチェックします。
例えば著作権表示 ⓒ ですが、代用で (c) のように記述すると警告されます。
そのように、Androidではテキストの読みやすさの点から代用文字は使わずに特殊記号文字を使うことが推奨されています。
現在のADTのバージョンでは著作権表示の書きかたのみがチェックされるようです。

Usability:Icons

GifUsage

重大度 : Warning
優先度 : 5/10
概要 : GIF形式の画像ファイルがないかチェックします。
自動修正 : なし
解説 :
Androidアプリでは、GIF形式の画像は非推奨になっています。
PNG形式またはJPEG形式が推奨されていますので、GIF形式のファイルがある場合変換してから使うようにしましょう。

IconDensities

重大度 : Warning
優先度 : 4/10
概要 : 画面解像度別のdrawableフォルダにアイコン画像ファイルがあるかチェックします。
自動修正 : なし
解説 :
アプリアイコン(ランチャーアイコン)画像ファイルは、端末の解像度ごとに用意する必要がありますが、
それぞれの解像度ごとのフォルダ(drawable-mdpiやdrawable-hdpiなど)にアイコン画像ファイルが置かれているかチェックしてくれます。
mdpi、hdpi、xhdpi用のアイコンは必ず用意するようにしましょう。
※ drawable-ldpi(120dpiの端末用のdrawable)はあまり多く利用されていないため、このチェックは無視されます。

IconMissingDensityFolder

重大度 : Warning
優先度 : 3/10
概要 : 画面解像度別のdrawableフォルダがあるかチェックします。
自動修正 : なし
解説 :
画面解像度別のdrawableフォルダがないとアイコンが適切なサイズで表示されません。
drawable-mdpiフォルダ、drawable-hdpiフォルダ、drawable-xhdpiフォルダは必ず用意するようにしましょう。
※ drawable-ldpi(120dpiの端末用のdrawable)はあまり多く利用されていないため、このチェックは無視されます。

IconDipSize

重大度 : Warning
優先度 : 5/10
概要 : 画面解像度別のフォルダに置かれた画像ファイルのサイズが同じ密度かチェックします。
自動修正 : なし
解説 :
画面解像度別に用意した画像ファイルのサイズを計算し、同じ密度(dip)になるかチェックしてくれます。
一つのdrawableリソースとなる画像ファイルが画面解像度間で同じ密度にならない場合、
ImageViewなどで表示したときにレイアウトが変わってしまいます。
drawableリソースは同じファイル名でなければならないので、ファイルが間違って置かれている場合もあります。
この警告が出た場合、まずはファイルが正しいdrawableフォルダに置かれているか確認し、
それでも密度が異なっている場合は画像ファイルの作り直しを検討しましょう。

IconExpectedSize

重大度 : Ignore
優先度 : 5/10
概要 : ランチャーアイコン、ノーティフィケーションアイコンなどが適切なサイズかチェックします。
自動修正 : なし
解説 :
ランチャーアイコンとノーティフィケーションアイコンは、以下のように画面解像度ごとに適切なサイズがあります。

Density 画像サイズ
ldpi 36 x 36 px
mdpi 48 x 48 px
hdpi 72 x 72 px
xhdpi 96 x 96 px

また、リソース上は必要ありませんが、アプリをマーケットに登録するときに
高解像度(512 x 512 px)のアプリアイコン画像を設定する必要があるので、一緒に作成すると良いと思います。
このチェックのデフォルトの重大度は Ignore になっているので、チェックしたい場合は設定を変更するようにしましょう。

IconLocation

重大度 : Warning
優先度 : 5/10
概要 : ビットマップのdrawableリソースが画面解像度別のdrawableフォルダに置かれているかチェックします。
自動修正 : なし
解説 :
drawableリソースはビットマップファイル(PNG形式、JPEG形式)とXMLファイルに大きく分類できます。
ビットマップファイルは画面解像度別に用意しなければならないので、
drawable-mdpiフォルダなどの画面解像度別のdrawableフォルダに置く必要があります。
一方XMLファイルは画面解像度別に用意する必要がないため、共通で使用されるdrawableフォルダに置きます。
この置く場所が異なっていると(例えばビットマップファイルを共通で使用されるdrawableフォルダに配置するなど)警告されます。
ビットマップファイルは画面解像度別のフォルダに置くようにしましょう。

IconDuplicates

重大度 : Warning
優先度 : 3/10
概要 : 同一画像ファイルが別なファイル名で複数存在していないかチェックします。
自動修正 : なし
解説 :
リソースフォルダ内で、同一の画像ファイルが別なファイル名で存在していないかチェックします。
この警告が出たらそれぞれの画像ファイルを確認し、同じ画像ファイルであればいずれかを削除して一つのリソースを使うように変更しましょう。

IconDuplicatesConfig

重大度 : Warning
優先度 : 5/10
概要 : 設定別のdrawableフォルダ間で同一の画像ファイルが存在していないかチェックします。
自動修正 : なし
解説 :
drawableフォルダは画面解像度別のdrawableフォルダなどのように、端末の条件に合わせた設定用drawableフォルダを定義できます。
画面解像度別だけではなく、以下のように言語別や方向(縦画面・横画面)別などにも定義することができます。

例1(英語圏用)
drawable-en
例2(横画面用)
drawable-land
例3(英語圏・mdpi用)
drawable-en-mdpi
例4(APIレベル6用)
drawable-v6

このようにdrawableを条件によって分けることができますが、
このチェックでは設定別のdrawableフォルダ間で同一の画像ファイルがないかチェックしてくれます。
この警告が出たらそれぞれの画像ファイルを確認し、同じ画像ファイルであればいずれかを削除して一つのリソースを使うように変更しましょう。

IconNoDpi

重大度 : Warning
優先度 : 7/10
概要 : 画面解像度別のdrawableフォルダとdrawable-nodpiフォルダに同じファイル名の画像ファイルがないかチェックします。
自動修正 : なし
解説 :
drawable-nodpiフォルダは、画面解像度別にスケールさせたくない画像を定義したい場合に使います。
このフォルダに置かれたDrawableリソースはその画像のpxサイズ通りに確実に表示されるようになります。
しかしこのフォルダに画面解像度別に用意してあるDrawableリソースと同じファイル名のファイルがあると、
drawable-nodpiフォルダが優先されるためスケールされずに表示されてしまいます。

drawable-nodpiフォルダに置くファイルは、画面解像度別に用意した画像ファイルとは別なファイル名で置くようにしましょう。

Usability

TextFields

重大度 : Warning
優先度 : 5/10
概要 : android:inputType や android:hint 属性がない テキスト入力コンポーネント がないかチェックします。
自動修正 : あり
解説 :
EditTextなどのテキスト入力コンポーネントでは、以下のようなテキスト入力をサポートするような属性が定義できます。

android:inputType 入力する文字の種別を設定します。
android:hint 入力内容が空のときに表示する文言を設定します。

特に android:inputType ですが、この指定を行うと適切な入力モードでIMEが立ち上がります。
例えば number を指定すると、数値入力モードのIMEが立ち上がります。
これらの定義があるとユーザビリティを向上させることができますのでぜひ検討してみましょう。

ButtonOrder

重大度 : Warning
優先度 : 8/10
概要 : OK / Cancel ボタンのレイアウトがAndroid4.0のデザイン規約に沿っているかどうかチェックします。
自動修正 : なし
解説 :
ユーザに対して確認を促すUIである、 OK / Cancel ボタンに関するチェックです。
OK / Cancel ボタンが表示されるのは一般的に AlertDialog などですが、
これらのボタンを表示するには以下のように実装します。

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("確認");
builder.setMessage("確認してもよろしいですか?");
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();

「OK」のような肯定的なアクションボタンは setPositiveButton() に、
「Cancel」のような否定的なアクションボタンは setNegativeButton() にそれぞれセットします。
この2つのボタンの位置はAndroid4.0のデザイン規約に定義され、
肯定的なアクションボタンは右側に、否定的なアクションボタンは左側に置くように統一されました。
AlertDialog を使用する場合は setPositiveButton() と setNegativeButton() を使うことで
自動的に位置が決められるため、意識する必要はありません。
問題はカスタムレイアウトで OK / Cancel ボタンのようなレイアウトを作成した場合です。
Android4.0より前のバージョンでは AlertDialog のボタン配置は逆になっているので、
そのレイアウトに倣って配置してしまうと、画面によって OK / Cancel の位置が変わってしまいます。
結果、使いづらいアプリとなってしまう可能性があります。

このチェックでは、テキストが "OK" "Cancel"となっている2つのボタンを水平にレイアウトしているとき、
"OK"ボタンが右側、"Cancel"ボタンが左側に置かれていない場合に警告されます。
この警告が出たら、 OK / Cancel の位置を変更するようにしましょう。

ButtonCase

重大度 : Warning
優先度 : 2/10
概要 : OK / Cancel の文字列が標準的な大文字・小文字の区別になっているかチェックします。
自動修正 : なし
解説 :
Androidでは、主にDialogなどで使用される OK / Cancel ボタンの文字列の表記は
"OK" "Cancel" を標準的な表記としています。
このチェックでは、 "ok" や "Ok"、また "cancel" などのStringリソースがないか調べます。
AndroidSDKには @android:string/ok と @android:string/cancel というStringリソースがあるので、
特殊な場合を除いては OK / Cancel ボタンの文字列はそちらを使用するようにしましょう。

BackButton

重大度 : Ignore
優先度 : 6/10
概要 : Androidプラットフォーム上で一般的ではないBackボタンがリソース上にないかチェックします。
自動修正 : なし
解説 :
Androidアプリのデザインガイドラインでは、画面遷移をナビゲートする "Backボタン" は
アクションバーの機能を使うようにすることが定義されています。
アクションバーにBackボタンを配置するには以下のように実装します。

getActionBar().setDisplayHomeAsUpEnabled(true);

このチェックでは、 "Back" というStringリソースがラベルに指定されているButtonが
レイアウトリソース上で定義されている場合に警告してくれます。

例えばiOSアプリでは戻る遷移のナビゲートに、ラベルを付けたBackボタンを採用していますが、
Androidではそのようなデザインは採用されていません。
アプリのUIデザインはプラットフォームごとのガイドラインに従うようにしましょう。

※ 詳細な情報は以下に記載されています。参考にしてください。

ViewConstructor

重大度 : Warning
優先度 : 3/10
概要 : カスタムViewで、ある前提として思われるコンストラクタが定義されているかチェックします。
自動修正 : なし
解説 :
いくつかのレイアウトエディタ(Eclipseプラグインなど)では、レイアウトXMLで定義するViewに
次のうち1つのシグネチャを持つコンストラクタを必要としています。

  • View(Context context)
  • View(Context context, AttributeSet attrs)
  • View(Context context, AttributeSet attrs, int defStyle)

第二引数の attrs はレイアウトXML上で定義された属性のセット、
第三引数の defStyle はデフォルトのStyleのリソースIDになります。
このチェックでは、上記コンストラクタがカスタムViewに定義されていない場合に警告してくれます。
開発環境がEclipseの場合、コンストラクタが定義されていないとコンパイルエラーが発生するため
あまり遭遇しない警告かと思います。
また余談になりますが、View#isInEditMode() は実行時にはfalseを返し、
レイアウトエディタによる編集中にはtrueを返す便利なメソッドです。
このメソッドにより実行時と編集時で処理を分けることができます。

AlwaysShowAction

重大度 : Warning
優先度 : 3/10
概要 : showAsAction="always" が showAsAction="ifRoom" が代わりに使用できないかチェックします。
自動修正 : なし
解説 :
オプションメニューなどのメニューの定義には menu.xml ファイルで記述します。
3.0(Honeycomb)以降のバージョンではアクションバーに表示されますが、
MenuItemには、表示方法を決めるために使われる "showAsAction" 属性があります。
showAsAction属性には、以下のいずれかの値を指定します。

ifRoom 表示領域に余裕があれば表示し、余裕がなければオーバーフローメニューに表示します。
never 常にオーバーフローメニューに表示します。
withText "android:title" で指定された文字列と一緒に表示します。
always 常にアクションバーに表示します。
collapseActionView "android:actionViewClass" または "android:actionLayout" で指定されたViewを表示します。

この中の "always" を指定することでアクションバー上に常に表示させることができますが、
"always" を複数のMenuItemに指定してしまうと、端末によっては表示しきれなくなってしまいます。
そのため、MenuItemの表示数によってオーバーフローメニューに格納する "ifRoom" を使うことが推奨されています。
このチェックでは、一つの menu.xml ファイルで "always" が複数指定されている場合、
またはJavaソース上で MenuItem#setShowAsActionFlags() に MenuItem.SHOW_AS_ACTION_IF_ROOM を
指定している場合に警告してくれます。MenuItemが確実に操作できるように "ifRoom" を指定するようにしましょう。

Performance

FloatMath

重大度 : Warning

優先度 : 9/10

概要 : java.lang.Math を android.util.FloatMath に置き換えられるかチェックします。

自動修正 : なし

解説 :

float を使用してサインやコサイン、平方根の計算をしたい場合、 java.lang.Math の代わりに
android.util.FloatMath を使用したほうがベターです。

java.lang.Math の sin() や cos() などの計算にfloatを使用すると、doubleへの変換が走ってしまうので、
floatにしたいのであれば android.util.FloatMath のメソッドを使用するようにしましょう。

android.util.FloatMath のメソッドは以下のとおりです。
これらは java.lang.Math の代わりに使用することができます。

  • ceil()
  • cos()
  • floor()
  • sin()
  • sqrt()

FieldGetter

重大度 : Ignore

優先度 : 4/10

概要 : クラス内のフィールドのアクセスに getter が使用されていないかチェックします。

自動修正 : なし

解説 :

クラス内で getter が定義されているフィールドを参照する場合、

private変数を直接参照したほうが3倍高速です。

例えば以下のようにシンプルな getter の場合、フィールドの参照に getter を使用する必要は特にありません。

package jp.classmethod.sample;

import android.content.Context;
import android.graphics.Canvas;
import android.view.View;

public class SampleView extends View {
    
    private int mColor;
    
    public int getColor() {
        return mColor;
    }
    public void setColor(int color) {
        this.mColor = color;
    }
    public SampleView(Context context) {
        super(context);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // クラス内で getter を使用している
        canvas.drawColor(getColor());
    }
}

クラス内のフィールドの参照はprivate変数を直接参照するようにしましょう。

InefficientWeight

重大度 : Warning
優先度 : 3/10
概要 : LinearLayoutの子コンポーネントに layout_weight 指定している場合、無駄がないかチェックします。
自動修正 : あり
解説 :
LinearLayoutを使ってコンポーネントを構成する場合、 layout_weight プロパティを使用して
子コンポーネントのサイズは親の全体のサイズから見た子コンポーネントの割合によって定義することができます。
例えば以下の例では、LinearLayout内に2つのボタンを同じ割合で(50%ずつ)レイアウトしています。

<?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">

    <Button android:text="Button1"
            android:layout_width="0dip"
            android:layout_height="100dp"
            android:layout_weight="1"
            />
    <Button android:text="Button2"
            android:layout_width="0dip"
            android:layout_height="100dp"
            android:layout_weight="1"
            />

</LinearLayout>

子コンポーネントが一つしかない状態で layout_weight プロパティを設定している場合、
LinearLayoutの orientation が horizontal の場合は layout_width を 「fill_parent」 ではなく 「0dip」 に、
vertical の場合は layout_height を 「fill_parent」 ではなく 「0dip」 にしたほうがパフォーマンスが向上します。

layout_weight を指定しているコンポーネントへの幅(または高さ)に fill_parent を指定してしまうと、
layout_weight 指定によるサイズの計算だけではなく fill_parent のサイズ計算が無意味に走ってしまいます。
layout_width と layout_height は必須プロパティなので、 layout_weight を指定している場合は
0dip を指定するようにしましょう。

NestedWeights

重大度 : Warning
優先度 : 3/10
概要 : layout_weight が指定されたコンポーネント内に layout_weight が指定された子コンポーネントがネストされていないかチェックします。
自動修正 : なし
解説 :
layout_weight が指定された Layoutコンポーネント(LinearLayoutなど)の中の子コンポーネントで
さらに layout_weight でレイアウト指定すると、レイアウト計算が二回走ってしまいます。
例えば以下のような構造です。

<?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">

    <LinearLayout android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
        <Button android:text="Button1"
                android:layout_width="0dip"
                android:layout_height="fill_parent"
                android:layout_weight="1"
                />
        <Button android:text="Button2"
                android:layout_width="100dip"
                android:layout_height="fill_parent"
                />
    </LinearLayout>

    <Button android:text="Button3"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            />
    <Button android:text="Button4"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            />

</LinearLayout>

レイアウトの計算コストが多くかかってしまうので、親のレイアウトに layout_weight を指定している場合、
ネストした子コンポーネントの layout_weight 指定は控えたほうがよいです。
4.0からはGridLayoutという選択肢もあるので、出来る限りコストを抑えたレイアウトを目指しましょう。

DisableBaselineAlignment

重大度 : Warning
優先度 : 3/10
概要 : android:baselineAligned を false に指定する必要があるLinearLayoutがないかチェックします。
自動修正 : なし
解説 :
android:baselineAligned はベースラインにレイアウトするかどうかを指定する属性です。
例えば以下のように、複数のコンポーネントを文字のベースラインを合わせてレイアウトしてくれます。

<?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="horizontal"
        android:gravity="center"
        >
    <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/sample"
        />
    <Button 
        android:layout_width="100dip"
        android:layout_height="50dip"
        android:text="@string/sample"
        />
    <Button 
        android:layout_width="100dip"
        android:layout_height="100dip"
        android:text="@string/sample"
        />
    <EditText 
        android:layout_width="100dip"
        android:layout_height="140dip"
        android:hint="@string/sample"
        />
</LinearLayout>

この android:baselineAligned 属性は使いどころによっては非常に便利なのですが、
レイアウトの計算コストがあるため false に指定するとレイアウトの計算を高速化できます。
この属性はデフォルトでtrueになっているので、指定の必要がないレイアウトはfalseを指定し、必要なところだけ有効化しましょう。

ObsoleteLayoutParam

重大度 : Warning
優先度 : 6/10
概要 : レイアウトコンポーネント内の子コンポーネントに無効なレイアウト属性が定義されていないかチェックします。
自動修正 : あり
解説 :
レイアウトコンポーネントにネストしているコンポーネントには、いくつかのレイアウト属性(例えば layout_weight など)がありますが、
レイアウトコンポーネントの種類によって有効/無効なレイアウト属性があります。
例えばRelativeLayoutの子には layout_weight は指定できません。
そもそもAndroidレイアウト・エディターのコードヒントでは有効な属性しか表示されないのでこの問題には出くわさないのですが、
例えばLinearLayoutで作りはじめ、途中からやっぱりRelativeLayoutに変更、などといったときに出くわす可能性があります。
この警告が出たら自動修正を実行し、不要な属性を削除するようにしましょう。

MergeRootFrame

重大度 : Warning
優先度 : 4/10
概要 : FrameLayoutがmergeタグに置き換えられるかどうかチェックします。
自動修正 : あり
解説 :
mergeタグは、ビュー階層を減らす目的で作られた要素です。
例えば以下のようなレイアウトは一見なにも問題なさそうに見えますが、
FrameLayout要素は幅・高さ以外の指定がないため無意味な要素となっています。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|top"
        android:layout_marginTop="20dip"
        android:src="@drawable/sample"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dip"
        android:layout_gravity="center_horizontal|bottom"
        android:padding="12dip"
        android:background="#AA000000"
        android:textColor="#ffffffff"
        android:text="@string/hello"
        />

</FrameLayout>

これを改善するのがmergeタグです。mergeタグをFrameLayoutの代わりに宣言することにより、
このレイアウトXMLより上の親のコンポーネントとマージ(ビュー階層が削減)され、最適化できます。

見た目の変化もないので、このエラーが出たらmergeタグに置き換えるようにしましょう。

UseCompoundDrawables

重大度 : Warning
優先度 : 6/10
概要 : TextViewに置き換えられるLinearLayoutがないかチェックします。
自動修正 : なし
解説 :
ImageViewとTextViewを並べておくようなレイアウトのLinearLayoutを作りたい場合、
LinearLayoutを使わず、TextViewのandroid:drawable属性だけで表現できます。
例えば、以下のようなレイアウトのLinearLayoutの場合を考えましょう。

<?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"
        >
    <ImageView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:src="@drawable/sample"
        />
    <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="@string/sample"
        />
</LinearLayout>

ListViewのアイテムレンダラによく使いそうなレイアウトですね。
これを以下のようにTextViewのみで表現できます。

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="@string/sample"
        android:drawableLeft="@drawable/sample"
        android:gravity="center_vertical"
        >
</TextView>

全く同じ結果になりました。
JavaソースからはsetCompoundDrawable()メソッドを使って変更できます。

        // Drawable を取得
        Drawable sample = getResources().getDrawable(R.drawable.sample);
        // サイズを設定する
        sample.setBounds(0, 0, sample.getIntrinsicWidth(), sample.getIntrinsicHeight());
        // TextView を main.xml から取得
        TextView textview = (TextView) getLayoutInflater().inflate(R.layout.main, null);
        textview.setGravity(Gravity.CENTER_VERTICAL);
        // TextView に Drawable をセット
        textview.setCompoundDrawables(sample, null, null, null);
        // Activity に TextView をセット
        setContentView(textview, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

Viewの数を2つも減らすことができるので、この警告が出た場合は置き換えを検討しましょう。

UselessParent

重大度 : Warning
優先度 : 2/10
概要 : 削除可能な親コンポーネントがないかチェックします。
自動修正 : あり
解説 :
必要のないレイアウトコンポーネントがないかチェックします。
例えば以下のようにLinearLayoutの子にLinearLayoutを置き、さらにその中にTextViewを置いたとします。
その場合LinearLayoutは一つ無意味になってしまうので、削除可能な親コンポーネントといえます。

<?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"
        >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            />
    </LinearLayout>

</LinearLayout>

レイアウトを複雑にするとこのような現象を無意識に作ってしまうことがあります。
無駄なコンポーネントは削除し、最適化していきましょう。

UselessLeaf

重大度 : Warning
優先度 : 2/10
概要 : 子コンポーネントがないレイアウトコンポーネントがないかチェックします。
自動修正 : あり
解説 :
このチェックでは、子コンポーネントが一つもないレイアウトコンポーネントが定義されていないかチェックしてくれます。
例えば以下のような場合、無意味なレイアウトコンポーネントに対して警告が出ます。

<?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="wrap_content" 
        android:layout_height="wrap_content"
        />
    
    <!-- 無意味なレイアウトコンポーネント -->
    <LinearLayout 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        />
    
</LinearLayout>

自動修正を実行すると対象の要素を削除してくれます。

TooManyViews

重大度 : Warning
優先度 : 1/10
概要 : Viewの数が多すぎないかチェックします。
自動修正 : なし
解説 :
一つのレイアウトXMLソースに対してViewの数が多すぎると、パフォーマンスが低下してしまいます。
このチェックでは80個以上のViewが定義されていると警告してくれます。
Viewが多すぎる場合は include などを利用してレイアウトファイルを分割するようにしましょう。

TooDeepLayout

重大度 : Warning
優先度 : 1/10
概要 : ネストが深すぎるレイアウトがないかチェックします。
自動修正 : なし
解説 :
ネスト階層が深すぎるレイアウトはパフォーマンスが低下してしまいます。
このチェックでは10階層以上ネストされていると警告してくれます。
ネスト階層が深すぎるレイアウトになってしまった場合は、RelativeLayoutやGridLayoutのような
レイアウトを利用するなど、出来る限りフラットなレイアウトになるように工夫しましょう。

UnusedResources

重大度 : Warning
優先度 : 3/10
概要 : 未使用のリソースがないかチェックします。
自動修正 : なし
解説 :
未使用のリソースはアプリのサイズを無駄に大きくしてしまい、ビルドを遅くしてしまいます。
未使用のリソースがある場合は削減し、無駄のないようにしましょう。

UnusedIds

重大度 : Ignore
優先度 : 1/10
概要 : 未使用のリソースID定義がないかチェックします。
自動修正 : なし
解説 :
リソースIDは、どのソースからも参照されていない場合必要ありません。
このチェックのデフォルトの重大度は Ignore になっているので、チェックしたい場合は設定を変更するようにしましょう。

Overdraw

重大度 : Warning
優先度 : 3/10
概要 : レイアウトXMLのルート要素に背景を指定している場合、Overdrawに成りうるかチェックします。
自動修正 : なし
解説 :
レイアウトXMLに対して背景色や背景画像を指定したい場合、 android:background 要素を使います。
以下では、画面全体に背景画像を描画しています。

<?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:gravity="center"
        android:background="@drawable/background_sample"
        >
    <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/sample"
        />
</LinearLayout>

このような指定をしている場合、テーマに背景が指定されているか気を付けなければいけません。
テーマの背景が指定されている場合、テーマの背景とレイアウトの背景が描画されますので、
余分な描画が発生しパフォーマンス低下につながってしまいます。これを"Overdraw"と呼んでいます。

例えばAndroidManifest.xml の application 要素の android:theme 属性に Theme.Light を指定している場合、
Theme.Lightでは背景が指定されているので、レイアウトに android:background を指定すると Overdrawとなります。
テーマの背景が先に描画されるので一見わからないのですが、余計な描画が含まれています。
この現象を防ぐには、カスタムテーマを作成し、 android:windowBackground 属性を null にします。

styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="CustomTheme" parent="@android:style/Theme.Light">
        <item name="android:windowBackground">@null</item>
    </style>
</resources>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.classmethod.sample.lint"
    android:versionCode="1"
    android:versionName="1.0" >
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/CustomTheme">
        <activity
            android:name=".SampleProjectActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

背景の描画は多くのコストがかかるので、場合によってはかなりのパフォーマンス向上が期待できます。
レイアウトXMLのルート要素に背景を指定する場合、テーマの背景が null になっているか確認するようにしましょう。

DrawAllocation

重大度 : Warning

優先度 : 9/10

概要 : 描画やレイアウトを行うコード上でオブジェクト生成が行われていないかチェックします。

自動修正 : なし

解説 :

View#onDraw メソッドや ViewGroup#onLayout メソッドなどのメソッド内で

オブジェクト生成を行うと、パフォーマンスが悪くなりUIが滑らかに動かなくなってしまいます。

View#onDraw メソッドや ViewGroup#onLayout メソッドは頻繁に呼び出されるため、

呼び出されるたびにオブジェクト生成が行われてしまいます。

これを避けるためには、以下のように予めオブジェクト生成を行うようにすることが一般的です。

package jp.classmethod.sample;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;

public class CustomView extends View {

    private Rect mRect;
    private Paint mPaint;

    public CustomView(Context context) {
        super(context);
        // オブジェクトを予め生成しておく
        mRect = new Rect(0, 0, 100, 100);
        mPaint = new Paint();
        mPaint.setAlpha(100);
        mPaint.setColor(Color.BLACK);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 既に生成しているオブジェクトを使用
        canvas.drawRect(mRect, mPaint);
    }
}

UseValueOf

重大度 : Warning
優先度 : 4/10
概要 : 'valueOf' メソッドに置き換えるべきラッパークラスのインスタンス生成が行われているかチェックします。
自動修正 : なし
解説 :
ラッパークラスのインスタンス生成は、'new Integer(1)' のように
コンストラクタ引数に値を入れて生成すべきではありません。
例えば、 0 と 1 のような一般的な Integer は、'valueOf' メソッドを使う場合、
単一のインスタンスを共有するので、'new' でインスタンス生成するよりもメモリ消費が少なくなります。

UseSparseArrays

重大度 : Warning

優先度 : 4/10

概要 : SparseArrayに置き換えるべきHashMapがないかチェックします。

自動修正 : なし

解説 :

キーがIntegerであるマッピングを行うには、HashMapクラスを使うよりも

SparseArrayクラスを使ったほうがより効率的に動作します。

SparseArrayクラスは、android用にチューニングされたマッピングを行うクラスです。

以下のようにキーがIntegerとなる HashMapクラスが使われていると警告されます。

HashMap<Integer, String> map = new HashMap<Integer, String>();
// 値の追加
map.put(0, "AAA");
map.put(1, "BBB");
map.put(1, "CCC");
// 値の取り出し
String value = map.get(0);

これをSparseArrayクラスに置き換えると以下のようになります。

SparseArray<String> array = new SparseArray<String>();
// 値の追加
array.append(0, "AAA");
array.append(1, "BBB");
array.append(2, "CCC");
// 値の取り出し
String value = array.get(0);

値を追加するメソッドが put() が append() に変わっているだけなので、変更は非常に簡単です。
また、キーと値のどちらもプリミティブ型のintの場合はSparseIntArrayクラスを、
値がプリミティブ型のbooleanの場合はSparseBooleanArrayクラスを使うようにしましょう。
これらのクラスを使うことでオートボクシングを避けることができるので、処理がより効率的になります。
この警告が出たらSparseArrayクラス、SparseIntArrayクラス、SparseBooleanArrayクラスのいずれかに変更するようにしましょう。

Accessibility

ContentDescription

重大度 : Warning
優先度 : 3/10
概要 : 画像コンポーネントに android:contentDescription が定義されているかチェックします。
自動修正 : あり
解説 :
android:contentDescription はUIコンポーネントの説明を指定するときに使います。
この属性を定義することで、以下のような場合にユーザを補助することができます。

  • コンポーネントがレンダリング(表示)されなかったとき、代わりに文字を表示する
  • 端末の音声補助機能が有効化されているとき、説明を音声で読み上げる

視覚のない人にも使えるよう、android:contentDescriptionの指定が推奨されています。
ImageViewやImageButtonなどにはandroid:contentDescriptionを必ず設定するようにしましょう。

Internationalization

HardcodedText

重大度 : Warning
優先度 : 5/10
概要 : ハードコーディングされているStringリソースがないかチェックします。
自動修正 : あり
解説 :
TextViewの android:text などのような文言を指定する属性に文字列がハードコーディングされている場合、
どの言語設定でアプリを使用しても同じ文言が出てしまうので、多言語対応の面でよろしくないです。
また、画面の向きによって別なレイアウトファイルにしているとき、それぞれのファイルでハードコーディングするより
一つのStringリソースを参照したほうがパフォーマンス面でも良いです。
文言の管理もしやすくなるので、文言はすべてstring.xmlに記述ようにしましょう。

このチェックの自動修正を実行すると、以下のようなウインドウが表示されます。

Replace by R.string. 項目にはIDを指定します。
また、下部にあるプレビューボタンを押すと自動修正されるリソースをすべて確認することができます。

EnforceUTF8

重大度 : Warning
優先度 : 2/10
概要 : XMLリソースファイルの文字コードがすべて UTF-8 かどうかチェックします。
自動修正 : なし
解説 :
XMLは文字セットの多種多様な文字コードをサポートしています。
しかし UTF-8 以外の文字コードを使用してしまうと、非ASCII文字を使用しているときに
バグが生じる可能性があるため、 androidアプリでは UTF-8 を使用するとこが推奨されています。
この警告が出た場合は文字コードを UTF-8 に変更するようにしましょう。

補足情報

デフォルトが Ignore のチェックは Warning に設定できない

理由は不明ですが、筆者の環境では デフォルトが Ignore の項目は Warning に設定できませんでした。
Error または Information には設定できるので、チェックしたい場合は注意してください。

※ ADT17でバグが修正され、設定できるようになったようです。

まとめ

2回にわたり、 Android Lint でチェックできる項目をできる限り詳細に解説しました。
ちなみに "Lint" とは、以下の意味があるそうです。

  1. リンネルの片面を起毛して柔らかくした、薬を塗って患部に当てるための布。
  2. 乾燥機の糸くず取り(Lint trap)の略称。

この2つの意味から考えると、 Android Lint は
「アプリのソースから余計な物を取り除き、エラー(傷)を修復するもの」
と捉えると何となく納得(?)できるような気がします。
ぜひ自分のアプリでエラーが出ないかチェックしてみてください。より良いアプリに導いてくれるはずです。
エラーの意味がわからないとき、この記事が参考になれば幸いです。
ADTのアップデートなどでチェックできる項目が増えたらまた解説したいと思います。

参考