tsurutanのつぶやき

これが、破壊的イノベーションだっっっ!!

【Android】 getApplication() をCustomApplicationにCastできる仕組み

Android開発をしたことがある方は、Activity, FragmentなどでgetApplication を使いApplicationを呼び出しCustomApplicationにCastしたkことがあるのではないかと思います。

そのときなんでApplicationをCustomApplicationにCastしたときjava.lang.ClassCastException が発生しないんだろう?どういう仕組なんだろう?と疑問に感じたことは無いでしょうか?

今回はその仕組について説明したいと思います。

疑問

そもそもCustomApplicationは

public class CustomApplication extends Application {
    @Override
    public final void onCreate() {
        super.onCreate();
    }
}

の様にApplicationのサブクラスになるため、直接生成したApplicationをCustomApplicationにキャストしようとすると java.lang.ClassCastExceptionが発生するはずです。

また Application

public class Application extends ContextWrapper implements ComponentCallbacks2 {
....
}
public class ContextWrapper extends Context {
...
}

となっているため、同様に直接生成したContextをCustomApplicationにCastするとエラーが発生するはずです。

薄々気づいてるかもしれませんが、ダウンキャスト時にエラーが発生しないということは内部でCustomApplication->Application->CustomApplication又はCustomApplication->Context->CustomApplicationの順でキャストがなされているはずです。

どのように行われているか見ていきます。

仕組み

Activity内でgetApplicationをした時

Activity.java

    /** Return the application that owns this activity. */
    public final Application getApplication() {
        return mApplication;
    }
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }

全体コード(http://tools.oesf.biz/android-7.1.1_r1.0/xref/frameworks/base/core/java/android/app/Activity.java)

Activityを継承したクラスでgetApplicationをするとActivity内のメンバ変数mApplicationが返されていることがわかります。 またmApplicationattachメソッドが呼び出された時に初期化されています。

またActivity.attachActivityThread.performLaunchActivityで呼び出されており

ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
   2518         // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
   2519 
   2520         ActivityInfo aInfo = r.activityInfo;
   2521         if (r.packageInfo == null) {
   2522             r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
   2523                     Context.CONTEXT_INCLUDE_CODE);
   2524         }
   2525 
   2526         ComponentName component = r.intent.getComponent();
   2527         if (component == null) {
   2528             component = r.intent.resolveActivity(
   2529                 mInitialApplication.getPackageManager());
   2530             r.intent.setComponent(component);
   2531         }
   2532 
   2533         if (r.activityInfo.targetActivity != null) {
   2534             component = new ComponentName(r.activityInfo.packageName,
   2535                     r.activityInfo.targetActivity);
   2536         }
   2537 
   2538         Activity activity = null;
   2539         try {
   2540             java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
   2541             activity = mInstrumentation.newActivity(
   2542                     cl, component.getClassName(), r.intent);
   2543             StrictMode.incrementExpectedActivityCount(activity.getClass());
   2544             r.intent.setExtrasClassLoader(cl);
   2545             r.intent.prepareToEnterProcess();
   2546             if (r.state != null) {
   2547                 r.state.setClassLoader(cl);
   2548             }
   2549         } catch (Exception e) {
   2550             if (!mInstrumentation.onException(activity, e)) {
   2551                 throw new RuntimeException(
   2552                     "Unable to instantiate activity " + component
   2553                     + ": " + e.toString(), e);
   2554             }
   2555         }
   2556 
   2557         try {
   2558             Application app = r.packageInfo.makeApplication(false, mInstrumentation);
   2559 
   2560             if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
   2561             if (localLOGV) Slog.v(
   2562                     TAG, r + ": app=" + app
   2563                     + ", appName=" + app.getPackageName()
   2564                     + ", pkg=" + r.packageInfo.getPackageName()
   2565                     + ", comp=" + r.intent.getComponent().toShortString()
   2566                     + ", dir=" + r.packageInfo.getAppDir());
   2567 
   2568             if (activity != null) {
   2569                 Context appContext = createBaseContextForActivity(r, activity);
   2570                 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
   2571                 Configuration config = new Configuration(mCompatConfiguration);
   2572                 if (r.overrideConfig != null) {
   2573                     config.updateFrom(r.overrideConfig);
   2574                 }
   2575                 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
   2576                         + r.activityInfo.name + " with config " + config);
   2577                 Window window = null;
   2578                 if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
   2579                     window = r.mPendingRemoveWindow;
   2580                     r.mPendingRemoveWindow = null;
   2581                     r.mPendingRemoveWindowManager = null;
   2582                 }
   2583                 activity.attach(appContext, this, getInstrumentation(), r.token,
   2584                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
   2585                         r.embeddedID, r.lastNonConfigurationInstances, config,
   2586                         r.referrer, r.voiceInteractor, window);
   2587 
   2588                 if (customIntent != null) {
   2589                     activity.mIntent = customIntent;
   2590                 }
   2591                 r.lastNonConfigurationInstances = null;
   2592                 activity.mStartedActivity = false;
   2593                 int theme = r.activityInfo.getThemeResource();
   2594                 if (theme != 0) {
   2595                     activity.setTheme(theme);
   2596                 }
   2597 
   2598                 activity.mCalled = false;
   2599                 if (r.isPersistable()) {
   2600                     mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
   2601                 } else {
   2602                     mInstrumentation.callActivityOnCreate(activity, r.state);
   2603                 }
   2604                 if (!activity.mCalled) {
   2605                     throw new SuperNotCalledException(
   2606                         "Activity " + r.intent.getComponent().toShortString() +
   2607                         " did not call through to super.onCreate()");
   2608                 }
   2609                 r.activity = activity;
   2610                 r.stopped = true;
   2611                 if (!r.activity.mFinished) {
   2612                     activity.performStart();
   2613                     r.stopped = false;
   2614                 }
   2615                 if (!r.activity.mFinished) {
   2616                     if (r.isPersistable()) {
   2617                         if (r.state != null || r.persistentState != null) {
   2618                             mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
   2619                                     r.persistentState);
   2620                         }
   2621                     } else if (r.state != null) {
   2622                         mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
   2623                     }
   2624                 }
   2625                 if (!r.activity.mFinished) {
   2626                     activity.mCalled = false;
   2627                     if (r.isPersistable()) {
   2628                         mInstrumentation.callActivityOnPostCreate(activity, r.state,
   2629                                 r.persistentState);
   2630                     } else {
   2631                         mInstrumentation.callActivityOnPostCreate(activity, r.state);
   2632                     }
   2633                     if (!activity.mCalled) {
   2634                         throw new SuperNotCalledException(
   2635                             "Activity " + r.intent.getComponent().toShortString() +
   2636                             " did not call through to super.onPostCreate()");
   2637                     }
   2638                 }
   2639             }
   2640             r.paused = true;
   2641 
   2642             mActivities.put(r.token, r);
   2643 
   2644         } catch (SuperNotCalledException e) {
   2645             throw e;
   2646 
   2647         } catch (Exception e) {
   2648             if (!mInstrumentation.onException(activity, e)) {
   2649                 throw new RuntimeException(
   2650                     "Unable to start activity " + component
   2651                     + ": " + e.toString(), e);
   2652             }
   2653         }
   2654 
   2655         return activity;
   2656     }

全体コード(http://tools.oesf.biz/android-7.1.0_r1.0/xref/frameworks/base/core/java/android/app/ActivityThread.java)

   2558             Application app = r.packageInfo.makeApplication(false, mInstrumentation);
   2583                 activity.attach(appContext, this, getInstrumentation(), r.token,
   2584                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
   2585                         r.embeddedID, r.lastNonConfigurationInstances, config,
   2586                         r.referrer, r.voiceInteractor, window);

2588行目で作成したApplication型のインスタンスをactivity.attachの引数で渡していることがわかります。 この時makeApplicationの中身が気になるので調べてみると

LoadedApk.java

    772     public Application makeApplication(boolean forceDefaultAppClass,
    773             Instrumentation instrumentation) {
    774         if (mApplication != null) {
    775             return mApplication;
    776         }
    777 
    778         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
    779 
    780         Application app = null;
    781 
    782         String appClass = mApplicationInfo.className;
    783         if (forceDefaultAppClass || (appClass == null)) {
    784             appClass = "android.app.Application";
    785         }
    786 
    787         try {
    788             java.lang.ClassLoader cl = getClassLoader();
    789             if (!mPackageName.equals("android")) {
    790                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
    791                         "initializeJavaContextClassLoader");
    792                 initializeJavaContextClassLoader();
    793                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    794             }
    795             ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
    796             app = mActivityThread.mInstrumentation.newApplication(
    797                     cl, appClass, appContext);
    798             appContext.setOuterContext(app);
    799         } catch (Exception e) {
    800             if (!mActivityThread.mInstrumentation.onException(app, e)) {
    801                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    802                 throw new RuntimeException(
    803                     "Unable to instantiate application " + appClass
    804                     + ": " + e.toString(), e);
    805             }
    806         }
    807         mActivityThread.mAllApplications.add(app);
    808         mApplication = app;
    809 
    810         if (instrumentation != null) {
    811             try {
    812                 instrumentation.callApplicationOnCreate(app);
    813             } catch (Exception e) {
    814                 if (!instrumentation.onException(app, e)) {
    815                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    816                     throw new RuntimeException(
    817                         "Unable to create application " + app.getClass().getName()
    818                         + ": " + e.toString(), e);
    819                 }
    820             }
    821         }
    822 
    823         // Rewrite the R 'constants' for all library apks.
    824         SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
    825                 .getAssignedPackageIdentifiers();
    826         final int N = packageIdentifiers.size();
    827         for (int i = 0; i < N; i++) {
    828             final int id = packageIdentifiers.keyAt(i);
    829             if (id == 0x01 || id == 0x7f) {
    830                 continue;
    831             }
    832 
    833             rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
    834         }
    835 
    836         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    837 
    838         return app;
    839     }

全体コード(http://tools.oesf.biz/android-7.1.0_r1.0/xref/frameworks/base/core/java/android/app/LoadedApk.java)

    796             app = mActivityThread.mInstrumentation.newApplication(
    797                     cl, appClass, appContext);

となっておりInstrument.newApplicationからApplicationを作成していることがわかります。

また引数として渡しているappClass

    780         Application app = null;
    781 
    782         String appClass = mApplicationInfo.className;
    783         if (forceDefaultAppClass || (appClass == null)) {
    784             appClass = "android.app.Application";
    785         }

となっており、forceDefaultAppClass(makeApplicationの引数)がtrue又はmApplicationInfo.classNamenullの時にandroid.app.Applicationが代入されるのがわかります。 ちなみにこの時のmApplicationInfo.classNameにはAndroidManifest.xmlにかかれている

    <application
        android:name=".CustomApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

android:nameに指定された値が入ってきます。

   989     public Application newApplication(ClassLoader cl, String className, Context context)
    990             throws InstantiationException, IllegalAccessException,
    991             ClassNotFoundException {
    992         return newApplication(cl.loadClass(className), context);
    993     }
    994 
   1004     static public Application newApplication(Class<?> clazz, Context context)
   1005             throws InstantiationException, IllegalAccessException,
   1006             ClassNotFoundException {
   1007         Application app = (Application)clazz.newInstance();
   1008         app.attach(context);
   1009         return app;
   1010     }

全体コード(http://tools.oesf.biz/android-7.1.0_r1.0/xref/frameworks/base/core/java/android/app/Instrumentation.java)

   1007         Application app = (Application)clazz.newInstance();

遂にたどり着きました。ココでappClassを元にCustomApplicationを作成し、それをApplicationにダウンキャストしていることがわかります。

このようにしてgetApplication()で取得するApplicationはCustomApplicationにキャストできるわけですね。

ViewPagerの入れ子構造でFragmentが表示されない時の対処

ViewPagerを使ってフラグメントを表示するときに普通 FragmentPagerAdapter を継承したカスタムのAdapterにFragmentManagerを引数として渡すと思いますが、ViewPagerが入れ子構造をしている場合、子のフラグメントのViewが表示されない問題が発生してしまいます。

この問題の対処として

CustomAdapter adapter = CustomAdapter(getActivity().getSupportFragmentManager())

CustomAdapter adapter = CustomAdapter(getChildFragmentManager())

のように書き換えてください。

getChildFragmentManager()の中身がどのようになっているか見てみると

final public FragmentManager getChildFragmentManager() {
        if (mChildFragmentManager == null) {
            instantiateChildFragmentManager();
            if (mState >= RESUMED) {
                mChildFragmentManager.dispatchResume();
            } else if (mState >= STARTED) {
                mChildFragmentManager.dispatchStart();
            } else if (mState >= ACTIVITY_CREATED) {
                mChildFragmentManager.dispatchActivityCreated();
            } else if (mState >= CREATED) {
                mChildFragmentManager.dispatchCreate();
            }
        }
        return mChildFragmentManager;
    }

とActivityのステイタスによってmChildFragmentManagerにライフサイクルに対応したメソッドを呼び出しています。

getActivity().getSupportFragmentManager()を子のフラグメントで直接呼び出すとライフサイクルを無視し、バックグラウンドに存在するフラグメントが表示されないというバグが発生してしまったことがわかります。

ほんきで学ぶAndroidアプリ開発入門 第2版 Android Studio、Android SDK 7対応

ほんきで学ぶAndroidアプリ開発入門 第2版 Android Studio、Android SDK 7対応

Combining Textual Entailment and Argumentation Theory for Supporting Online Debates Interactions の概要と考察

f:id:tsurutan:20161016154131j:plain

今回は2012年に自然言語のトップカンファレンスであるACLに投稿されたCombining Textual Entailment and Argumentation Theory for Supporting Online Debates Interactionsについての概要を説明し、考察してみようと思います。

ACL | Association for Computational Linguistics

[原文]http://www.aclweb.org/anthology/P12-2041

序章

f:id:tsurutan:20161017154856j:plain

近年、TwitterやDebetepediaといったサービスで様々な話題で多くの議論が交わせれている。

www.debatepedia.org

しかし、そのような話題に途中で参加するには今までの議論を一つ一つ見ていき、議論が収束しているのか、また今後話し合う必要があるところなどを考えなくてはいけなく、それを面倒に感じることが多いのではないかと考えている。

そこでこの論文ではこれらのサービスで繰り広げられている議論をTextual Entailmentを使って、反対・賛成意見を抜き取り収束したのかどうかを評価する手法を提案している。

手法

Textual EntailmentとはDaganが2009年に提唱したもので、text(t)とhypothesis(h)で論理的含意(t→h)を表すものである

ちょっと何を言っているのかわからないので簡単な例を見てみると

text:

アメリカンショートヘアーを飼っている

hypothesis:

猫を飼っている

がある時textが真ならばhypothesisも真になるのがわかると思う。

このような関係がTextual Entailmentである。

数学的にこのような条件を定義する時は厳密なものでなくてはならないが、Textual Entailmentではすごくざっくりとしたものであるため、客観的に正しいと考えられるものはこのように定義できる。

ちなみに、TE(日本語)の評価データについては京大の黒橋・河原研究室のホームページに掲載されています。

Textual Entailment 評価データ - KUROHASHI-KAWAHARA LAB

この論文でも幾つかTextual Entailment の例を出しているので見てみると下記のように書いてある。

text:

Research shows that drivers speaking on a mobile phone have much slower reactions in braking tests than non-users, and are worse even than if they have been drinking.

hypothesis:

The use of cell-phones while driving is a public hazard.

この例文を意訳してみるとtextは「電話で話しながら運転をしていると、電話をしていない人々また飲酒運転をしている人よりもブレーキテストの反応が悪かった」と読め、hypothesisには「運転中の携帯電話の使用は危険である」と書かれており確かにこの場合でもtextが真ならばhypothesisが真であることが分かる。

このようにT-Hのペアを見つけることで、議論の関係性を見ることができ、また相手の発言に対して反論するときにTに対してなのかHに対してなのかを区別し最終的にどこで議論が収束していたのか見ている。

f:id:tsurutan:20161017160255p:plain

A1=hypothesis:

The use of cell-phones while driving is a public hazard.

A2=text:

Research shows that drivers speaking on a mobile phone have much slower reactions in braking tests than non-users, and are worse even than if they have been drinking.

A3=text:

Regulation could negate the safety benefits of having a phone in the car. When you’re stuck in traffic, calling to say you’ll be late can reduce stress and make you less inclined to drive aggressively to make up lost time.

A4=text:

If one is late, there is little difference in apologizing while in their car over a cell phone and apologizing in front of their boss at the office. So, they should have the restraint to drive at the speed limit, arriving late, and being willing to apologize then; an apologetic cell phone call in a car to a boss shouldn’t be the cause of one being able to then relax, slow-down, and drive the speed-limit.

これらの関係を図で表すと上記のようになり。点線の矢印は反論をそれ以外の矢印は賛成となる関係をしめしている。

また、これは実際にDebetepediaで繰り広げられた議論である、二重線で書かれている円の主張が受け入れられていることを表す。

このような関係を取り出すために著者はEDITS systemというオープンソースのTE認識システムを使って既存の手法と提案手法の実験を行い、評価を行った。

データセットには下記のような100個のtarin data, test dataを用いている。

f:id:tsurutan:20161017161506p:plain

結果を見てみると既存の手法ではtraining setでaccuracyが0.69,test setで0.67であったが提案手法では0.75と高くなった。

考察

議論の賛成、反対などの意見をTEに着目して分析したのは素晴らしい考えだと思う。

しかし、この論文では詳しい分類の実装方法などが書かれておらず実際に自分の手で実験できないのは残念である。

またACLといったトップカンファレンスはこういった比較的新しい手法に対してAcceptが寛容的になるのではないかと感じた。(それだけTEは今後流行ると期待されているのか)

今後もAugmentation関連の論文を読んで知見を集めていこうと思う。

入門 自然言語処理

入門 自然言語処理

  • 作者: Steven Bird,Ewan Klein,Edward Loper,萩原正人,中山敬広,水野貴明
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2010/11/11
  • メディア: 大型本
  • 購入: 20人 クリック: 639回
  • この商品を含むブログ (44件) を見る

言語処理のための機械学習入門 (自然言語処理シリーズ)

言語処理のための機械学習入門 (自然言語処理シリーズ)

自然言語処理の基礎

自然言語処理の基礎

Predicting Quality Flaws in User-generated Content: The Case of Wikipedia の概要と考察

f:id:tsurutan:20161016154131j:plain

2012年にACMというカンファレンスに出された「Predicting Quality Flaws in User-generated Content: The Case of Wikipedia」という論文を読み考察をしました。

www.acm.org

[原文] http://www.uni-weimar.de/medien/webis/publications/papers/stein_2012i.pdf

この論文ではWikipediaについているcleanup tagをもとに記事の質の悪さを予測する手法を提案している。

序章

現在誰もが知っているWikipediaには日々膨大な記事が作成されていおり、その著者に誰もがなれるため様々な質の記事が生み出されている。

本来は記事がリリースされる前に専門家のチェックを理想とするが、記事の量が膨大なため人手で目を通すのは不可能に近い。

そこで、この論文では記事の良し悪しを見るためcleanup tagを用いて、コンピューターが記事の欠落を予測する手法を提案している。

実験

cleanup tagとはWikipediaの記事の欠落を表すタグである。

下記の画像を見ると分かるようにcleanup tagは読者や編集者に記事の問題点を知らせている。

f:id:tsurutan:20161015144829p:plain

そしてこのようなcleanup tagはテンプレートから作成されており、その種類は320000ほど存在する。

そこでcleanup tagと記事の内容をSQLでwikipediaから取得し、最も頻度の高い10個のcleanup tagを用いて予測を行う。

f:id:tsurutan:20161015150846p:plain

分類にはOptimistic SettingPessimistic Settingというモデルを作成し、SVMを使い分類を行っている。

f:id:tsurutan:20161016152546p:plain

横軸は閾値で縦軸は精度と再現率となっているが精度ではOptimisticの方が明らかに高いことが見て分かる。

また、cleanup tagの種類ごとに精度を見てみると、Orphanは常に精度は1で、それ以外は記事の欠落の比率が大きくなるにつれて精度が低くなっているのが分かる。

f:id:tsurutan:20161015153352p:plain

f:id:tsurutan:20161015153415p:plain

Orphanの定義自体リンクの個数といった具体的なもので表されるため、このような高い精度になったと考えられる。

下記はOrphanタグの定義

f:id:tsurutan:20161016153622p:plain

結論

結果を見てみると多くの欠落が記事に含まれているとどういったcleanup tagが現れるのかという精度が低くなってしまうが、逆に欠落の少ない記事であれば高い精度がでている。

とくにリンクの数が少ないことを表すOrphanというタグに至っては、記事の欠落の比率にかかわらず常に精度は1である。

それぞれのタグをif else文のみで完結に定義しているのにこのような高い精度を出しているのは感嘆する。

この論文をもとに今後、悪質な記事が減ることを期待する。

記事の質をcleanup tagを用いて解析したのは新規性のある素晴らしいアイデアだと思うが、このようなtagはwikipediaにしかついていないので応用性が乏しいのではないかと考える。

Android Fabricを使ったクラッシュ分析 テスト配布

f:id:tsurutan:20161014113752p:plain

今回は今買収されそうで話題なTwitter社のFabricを紹介したいと思います。

Fabricとは、開発者の方々がより良いアプリケーションをつくるために便利なモジュール形式のモバイルプラットフォームです。

FabricをAndroidに導入することで、Web上のサイトからクラッシュの分析やAndroid Studioから直接apkのテスト配布が行えます。

非常に便利なので、ぜひ使って見ましょう!

まずはアカウント登録

Fabric - Twitter's Mobile Development Platform

Fabricの公式ページに行き

f:id:tsurutan:20161014104320p:plain

Get Started with Fabricのボタンを押します。

すると登録フォームが出てくるので、適当なユーザーネームとアドレス、パスワードを入力してメール認証ボタンを押し、メールが届くので記載されているurlを押し、登録を完了させてください。

f:id:tsurutan:20161014104704p:plain

Android Studioにプラグインを入れる

さて、アカウントが登録できたところで次にAndroid StudioにFabricのプラグインを入れましょう。

メニューからAndroid Studioを選択し、表示されたプルダウンメニューの中にあるPreferencesをクリックします。

f:id:tsurutan:20161014105013p:plain

するとPreferenceのダイアログが表示されるので、そのサイドバーにあるPluginをクリックし、下の中央にあるBrowse repositoriesというボタンを押します。

f:id:tsurutan:20161014105240p:plain

f:id:tsurutan:20161014105350p:plain

そして表示されたダイアログの検索欄でFabricと入力するとFabric for Android Studioと出てくるのでインストールをし、Android Studioを再起動しましょう。

f:id:tsurutan:20161014105821p:plain

再起動して、メニューバーにこのようなFabricのアイコンが表示されれば正常にインストールが完了しております。

f:id:tsurutan:20161014110241p:plain

Fabric SDKを導入しよう。

Fabricのプラグインがインストールできましたら、あとは簡単です。

Fabricのアイコンをクリックすると右側にFabricのUIが表示されるので、先ほど作成したアカウント情報を入力しログインします。

すると、このようにFabricに搭載されている様々なツールが表示されるので今回はClashriticsを選択し、インストールしましょう。

f:id:tsurutan:20161014110645p:plain

f:id:tsurutan:20161014110738p:plain

f:id:tsurutan:20161014110807p:plain

なんとFabric プラグインが自動的にコードをプロジェクト(build.gradleApplication, Acitivty)に書き加えてくれるので、これだけでSDKの導入は終わりです。

そしてWebからFabricにアクセスすると登録したアプリのクラッシュ情報を見ることができます。

f:id:tsurutan:20161014113405p:plain

テスト配布しよう

Fabricにはテスト配布をする機能も搭載されております。 まずFabricのホーム画面まで戻ると登録したアプリのリストが表示されるので、配布したいアプリを選択します。 すると下のような画面が表示されるので左上の配布ボタンをクリックします。

f:id:tsurutan:20161014111634p:plain

そしてこの画面に選択したアプリのapkをドラッグ&ドロップすると

f:id:tsurutan:20161014111735p:plain

このように配布したいユーザーのメールアドレスを登録する画面に遷移するので、メールアドレスを登録しましょう。

f:id:tsurutan:20161014111840p:plain

そして、このapkの情報を記入してDistuributeボタンを押すと配布が完了します。

f:id:tsurutan:20161014113031j:plain

登録したメールアドレスにこのようなメールが来ているはずです。

とても簡単ですので、是非みなさんもFabricを導入してみましょう!!!

オススメの記事

ほんきで学ぶAndroidアプリ開発入門 第2版 Android SDK 7/Android Studio 2.X対応

ほんきで学ぶAndroidアプリ開発入門 第2版 Android SDK 7/Android Studio 2.X対応

黒帯エンジニアが教えるプロの技術 Android開発の教科書 (ヤフー黒帯シリーズ)

黒帯エンジニアが教えるプロの技術 Android開発の教科書 (ヤフー黒帯シリーズ)

  • 作者: 筒井俊祐,里山南人,松田承一,笹城戸裕記,毛受崇洋
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2016/06/18
  • メディア: 単行本
  • この商品を含むブログを見る

Picasso を使った画像表示

f:id:tsurutan:20161011104324p:plain

今回は神Jake Wharton 氏が作成したPicassoという画像のダウンロードやらキャッシュをやってくれるライブラリーについて紹介したいと思います。

使い方

使い方は至って簡単!!

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

loadに画像が置かれているurlを私intoに画像を表示したいImageViewを渡すだけです。

また、画像の加工もでき

Picasso.with(context)
  .load(url)
  .resize(50, 50)
  .centerCrop()
  .into(imageView)

このようにすれば中央を中心に50*50でクロップすることができ、また角丸の画像を作りたいならば

public class CropSquareTransformation implements Transformation {
  @Override public Bitmap transform(Bitmap source) {
    int size = Math.min(source.getWidth(), source.getHeight());
    int x = (source.getWidth() - size) / 2;
    int y = (source.getHeight() - size) / 2;
    Bitmap result = Bitmap.createBitmap(source, x, y, size, size);
    if (result != source) {
      source.recycle();
    }
    return result;
  }

  @Override public String key() { return "square()"; }
}

というTransformationを作成し

Picasso.with(context)
  .load(url)
  .transform(new CropSquareTransformation())
  .into(imageView)

といった感じで渡してください。

また、自分でTransformationを作るのが面倒くさいという方はwasabeefさんが様々なTransformationを作成してくださり、ライブラリーとして配布されているのでそちらを使うのがオススメです。

github.com

Picassoでは画像の読み込み中やエラーが出たときに表示する画像を指定することができます。

Picasso.with(context)
    .load(url)
    .placeholder(R.drawable.user_placeholder)
    .error(R.drawable.user_placeholder_error)
    .into(imageView);

また、ネットに上がっている画像のみではなくローカルに保存された画像もpathを指定することで表示することができます。

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);

キャッシュのサイズを制限することもできます。

int maxSize = MAX_CACHE_SIZE;
Picasso picasso = new Picasso.Builder(context)
                              .memoryCache(new LruCache(maxSize))
                              .build();

このmaxSizeはバイトを表します。

またデバッグでイメージがネット、キャッシュ、Diskのどれから呼び出されているのかsetIndicatorsEnabled(true)を使い可視化することができます。

f:id:tsurutan:20161011104952p:plain

どうやって使うの?

おなじみアプリケーションレベルのbuild.gradle

compile 'com.squareup.picasso:picasso:2.5.2'

と記述するだけです!最高ですね!!

オススメの記事

www.tsurutan.com

www.tsurutan.com

www.tsurutan.com

ほんきで学ぶAndroidアプリ開発入門 第2版 Android SDK 7/Android Studio 2.X対応

ほんきで学ぶAndroidアプリ開発入門 第2版 Android SDK 7/Android Studio 2.X対応

黒帯エンジニアが教えるプロの技術 Android開発の教科書 (ヤフー黒帯シリーズ)

黒帯エンジニアが教えるプロの技術 Android開発の教科書 (ヤフー黒帯シリーズ)

  • 作者: 筒井俊祐,里山南人,松田承一,笹城戸裕記,毛受崇洋
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2016/06/18
  • メディア: 単行本
  • この商品を含むブログを見る

Android ローカライゼーションでテキスト幅を揃える

f:id:tsurutan:20161010111900p:plain

Androidは多くの言語が搭載されており、開発者にとってアプリケーションの多言語化は切っても切れない関係です。

単言語化で最も気をつけなければならないことは、あるフレーズを別の言語に置き換えたときに、テキストの長さが変わってしまいデザインが崩れてしまうことです。

今回は、このようなケースに備えて、自動的に文字サイズを変えて一定の幅にテキストを抑えてくれるAutofitHelperというライブラリーについて紹介したいと思います。

使い方

使い方は至ってシンプルです。

文字の大きさを自動で変えたいTextView

AutofitHelper.create(textView);

このようにAutfitHelper.createの引数に渡すだけです。

ただし、渡すTextViewにはmaxLinesなどを指定し、行数を制限してください。

またxmlからも指定することができ

<me.grantland.widget.AutofitLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        />
</me.grantland.widget.AutofitLayout>

このように、変化させたいViewをAutofitLayoutでラップするか

<me.grantland.widget.AutofitTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:singleLine="true"
    android:maxLines="2"
    android:textSize="40sp"
    autofit:minTextSize="16sp"
    />

AutofitTextViewAutofitButtonというウィジェットを使うことができます。

特にCustomしたTextViewを使ってないのであれば、提供されているウィジェットを使うことをおすすめします。

f:id:tsurutan:20161010111509g:plain

また、English(XA)かロシア語が他の言語と比べてフレーズが長いと言われているので多言語化をテストするときはこれらを使うのがおすすめです。

ちなみにEnglish(XA)とは、標準的な英語を三割ほど文字数を増やして長くしたものです。

どうやって使うの?

おなじみアプリケーションレベルのbuild.gradle

dependencies {
    compile 'me.grantland:autofittextview:0.2.+'
}

と記述するだけです。簡単ですね!

問題点

AutofitHelperには現在のバージョンでは問題点があり、TextViewに.setCompoundDrawablesWithIntrinsicBounds(R.drawable.icon, 0, 0, 0)を指定して、drawableを描画すると文字の幅を見誤ってしまいうまく調整することができません。

この対策として、Libraryをローカルに引っ張ってきてAutofitHelper.javaの98行目を

int targetWidth = view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();

から

int targetWidth = view.getWidth() - view.getCompoundPaddingLeft() - view.getCompoundPaddingRight();

に変更しましょう。

それとも、現在修正されたプルリクが出されているのでそれをjitpackで使うか、マージされるか待った方が良いかもしれませんね。

オススメの記事

www.tsurutan.com

www.tsurutan.com

www.tsurutan.com

ほんきで学ぶAndroidアプリ開発入門 第2版 Android SDK 7/Android Studio 2.X対応

ほんきで学ぶAndroidアプリ開発入門 第2版 Android SDK 7/Android Studio 2.X対応

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)