2015/05/02

Android Studio で Warning や Error の一覧を表示する

Android SDK を更新したら Warning が増えてしまいました。
問題になりそうなところを教えてくれるのは良いのですが、修正するには一覧で確認したいもの。
というわけで、一覧表示する方法を調べました。

Analyze → Inspect Code...

対象の範囲を選びます。

Analyze が終了すると、左下に Inspection というウィンドウが作成され、一覧が表示されます。

左のボタンで表示を切り替え可能なので、使いやすいようにカスタマイズします。
お勧めは、赤と黄色のボタン、"Group Inspections By Severity" です。
以下のように Error, Warning, Typo で分類してくれます。

2015/04/12

AndroidStudioで Java Library を追加したら UNEXPECTED TOP-LEVEL EXCEPTION でコンパイルが通らなかった場合の対処方法

現象

UNEXPECTED TOP-LEVEL EXCEPTION もいろいろなケースがあるので、一概に言えないのですが、以下のように ParseException: bad class file magic or version の場合、コンパイルに使った Java のバージョンが合っていない可能性が高いです。

...省略
:app:preDexDebug
AGPBI: {"kind":"SIMPLE","text":"UNEXPECTED TOP-LEVEL EXCEPTION:","position":{},"original":"UNEXPECTED TOP-LEVEL EXCEPTION:"}
AGPBI: {"kind":"SIMPLE","text":"com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)","position":{},"original":"com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)","position":{},"original":"\tat com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)","position":{},"original":"\tat com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)","position":{},"original":"\tat com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)","position":{},"original":"\tat com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.processClass(Main.java:704)","position":{},"original":"\tat com.android.dx.command.dexer.Main.processClass(Main.java:704)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.processFileBytes(Main.java:673)","position":{},"original":"\tat com.android.dx.command.dexer.Main.processFileBytes(Main.java:673)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.access$300(Main.java:83)","position":{},"original":"\tat com.android.dx.command.dexer.Main.access$300(Main.java:83)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602)","position":{},"original":"\tat com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)","position":{},"original":"\tat com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)","position":{},"original":"\tat com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)","position":{},"original":"\tat com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.processOne(Main.java:632)","position":{},"original":"\tat com.android.dx.command.dexer.Main.processOne(Main.java:632)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.processAllFiles(Main.java:510)","position":{},"original":"\tat com.android.dx.command.dexer.Main.processAllFiles(Main.java:510)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.runMonoDex(Main.java:280)","position":{},"original":"\tat com.android.dx.command.dexer.Main.runMonoDex(Main.java:280)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.run(Main.java:246)","position":{},"original":"\tat com.android.dx.command.dexer.Main.run(Main.java:246)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.dexer.Main.main(Main.java:215)","position":{},"original":"\tat com.android.dx.command.dexer.Main.main(Main.java:215)"}
AGPBI: {"kind":"SIMPLE","text":"\tat com.android.dx.command.Main.main(Main.java:106)","position":{},"original":"\tat com.android.dx.command.Main.main(Main.java:106)"}
AGPBI: {"kind":"SIMPLE","text":"...while parsing com/kokufu/lib/Test.class","position":{},"original":"...while parsing com/kokufu/lib/Test.class"}
AGPBI: {"kind":"SIMPLE","text":"1 error; aborting","position":{},"original":"1 error; aborting"}

私の環境で Java のバージョンを調べてみると、以下のように 1.8 でした。

$ java -version
java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

解決方法

2015/4/12 現在、Android Studio は Java 1.7 系でコンパイルするようですので、以下のように Java Library のコンパイルバージョンを下げます。

lib/build.gradle
apply plugin: 'java'

sourceCompatibility = '1.7'

Android Application のコンパイルバージョンを変更する

おまけですが、Android Application の方のコンパイルバージョンも変更することが出来ます。
以下のように Source Compatibility を選択すればオーケー。
現時点では 1.8 が選択肢にないです。

File → Project Structure
2015/03/19

DialogFragment の中に Fragment を表示する方法

ちょっと複雑な Dialog を表示しようと思った場合、DialogFragment の中に Fragment を表示したいということがあるかと思います。
しかし、XML を使った静的な Fragment は入れ子構造に出来ないようです。

では、どうしたら良いのかというと、動的に Fragment を追加してやれば良いです。
以下のように、onViewCreated() で Fragment を追加します。この時、getChildFragmentManager() を使用していることに注意です。

TestDialog.java
public class TestDialog extends DialogFragment {
    public TestDialog() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.dialog_test, container, false);
        return v;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Fragment innerFragment = new InnerFragment();
        getChildFragmentManager().beginTransaction().add(R.id.fragment, innerFragment).commit();
    }
}

dialog_test.xml
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
2015/02/09

Gradle で Configuration with name 'default' not found. が出てしまった時の対処方法

Gradle で app と lib のように 2つの依存関係のあるモジュールを開発していると、以下のようなエラーが出てしまうことがあります。

A problem occurred configuring project ':app'.
> Cannot evaluate module lib : Configuration with name 'default' not found.

解決策1: app から対象の Configuration を明示的に指定する

Configuration は plugin を適用すると大抵は自動的に設定されます。 java とか com.android.application だと default という名前の Configuration があるので問題ないのですが、その他のものの場合は default がない場合があります。その場合は、対処の Configuration を以下のように明示的に指定することで回避することができます。
project の引数に path: が追加されているのに注意してください。

app/build.gradle
dependencies {
    compile project(path: ':lib', configuration: 'compile')
}

解決策2: lib に default という名前の Configuration を作る

lib にプラグインを適用してない等、Configuration がそもそもない場合は、以下のように自分で作成してしまうのも手です。

lib/build.gradle
configurations.create('default')
2015/01/27

ActionBarActivity#setSupportActionBar() で NoClassDefFoundError が発生する端末がある

私が Google Play で公開しているアプリのクラッシュ報告に、以下のような Stack Trace が送られてきました。
java.lang.NoClassDefFoundError: android.support.v7.internal.view.menu.MenuBuilder
at android.support.v7.widget.ActionMenuView.getMenu(ProGuard:620)
at android.support.v7.widget.Toolbar.ensureMenu(ProGuard:825)
at android.support.v7.widget.Toolbar.getMenu(ProGuard:817)
at android.support.v7.internal.app.ToolbarActionBar.getMenu(ProGuard:554)
at android.support.v7.internal.app.ToolbarActionBar.setListMenuPresenter(ProGuard:558)
at android.support.v7.app.ActionBarActivityDelegateBase.setSupportActionBar(ProGuard:178)
at android.support.v7.app.ActionBarActivity.setSupportActionBar(ProGuard:92)
at com.kokufu.android.apps.divelogbook.MainActivity.onCreate(ProGuard:84)
at android.app.Activity.performCreate(Activity.java:5122)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1150)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2315)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2403)
at android.app.ActivityThread.access$600(ActivityThread.java:165)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1373)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5391)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
at dalvik.system.NativeStart.main(Native Method)

原因

調べてみると、Samsung やその他一部の端末で発生するバグらしい。

対策

これ、appcompat-v7 のバグではなく、OS のバグだと思われます。 OS に古い appcompat-v7 が組み込まれているのが問題ではないかと。
つまり、アプリ側ではどうしようもない。 …と諦めかけてたのですが、ProGuard を使って回避するナイスなアイデアが!

難読化をしない場合、以下の一行を ProGuard の設定ファイルに記述すればオッケーです。
-keep class !android.support.v7.internal.view.menu.**,** {*;}

他にも難読化したい場合には、以下の方が無難でしょう。
-keep class !android.support.v7.internal.view.menu.**,android.support.v7.** { *; }
-keep interface !android.support.v7.internal.view.menu.**,android.support.v7.** { *; }

出力された mapping ファイルを確認し、MenuBuilder の名前が変わっていることを確認しておきます。

build/outputs/mapping/release/mapping.txt
android.support.v7.internal.view.menu.MenuBuilder -> android.support.v7.internal.view.menu.i:

私の手元には、この問題が起こる端末が無いため、動作確認出来ていません。
名前変えただけで、class path の問題を回避できるんだか、ちょっと不安です。
もし、確認された方がいらっしゃいましたら、是非教えてください。