Charlesでレンダリングブロックを調査してみた

2018年06月22日
区分
tool
報告者:
ohata

こんにちは ohataです。

2018/3/1に入社して、早3ヶ月経ちました。

わずか3ヶ月ですが、オフィス移転、15周年記念など会社として インパクトの大きい行事に参加させてもらいました。
(オフィスめちゃ広くなった!)

さてオフィス移転と同時にホームページがリニューアルされたのですが 表示に時間がかかっていたので、ちょっと調べて見ました。

遅い原因

サイトが重い場合はリソースの容量が関係するパターンが多いですが、 開発ツールのTimelineを見た感じ容量による影響ではなく、どこかの処理で レンダリングブロックが発生しているようでした。

しかも本番でしか発生していない、WEBあるある状態です。

調査1

大抵はjs絡みが多いのですが、念のため切り分けを行ってみます。

  1. Safari - [開発ツール]-[javascriptを無効にする]
    • レンダリング遅いまま
  2. Safari - [開発ツール]-[スタイルを無効にする]
    • レンダリング速くなった

今回はCSSが原因のようでした

調査2

対象css全部をwebコンソールで調べていくのも、ちょっと辛かったので、 前にネイティブアプリ開発をやっていた際に利用していた [Charles]を使って調べて見ました。

Charlesとは

CharlesはDebugging Proxyツールです。
※ WindowsにはFreeで、Fiddlerという素敵なツールがありますね

Proxy設定

Charles をProxyとして動作させます

  1. Charlesを立ち上げる
  2. [help] - [local Ip address] を開く
  3. en0 に表示されているIP Addressを macのproxyに設定する
  4. [Proxy]-[Record setting]-[include]に調査したいURLを設定する

ここまでがProxy設定です

cssのレスポンスを書き換える

今回はレンダリングブロックしているCSSを特定するのが目的なので、cssの処理を一度全部無効化して、 1個ずつ有効化していく事にしました。

  1. レンダリング対象を特定するために処理を無効化するように、
    空のファイルを作成します
    touch empty.css
    
  2. [Tools]-[Map local]に対象のリソースと書き換え先のファイル[empty.css]を指定します ※ XMLでimportできるので今回はimportして見ました
<?xml version='1.0' encoding='UTF-8' ?>
<?charles serialisation-version='2.0' ?>
<mapLocal>
  <toolEnabled>true</toolEnabled>
  <mappings>
    <mapLocalMapping>
      <sourceLocation>
        <path>/wp-content/themes/agent_tcd033/style.css?ver=4.9.6</path>
      </sourceLocation>
      <dest>/Users/ohata.kenji/empty.css</dest>
      <enabled>false</enabled>
      <caseSensitive>true</caseSensitive>
    </mapLocalMapping>

map_local_settings
スクリーンショットのように

  • [location] に書き換えたいリソース
  • [local path]に書き換え後のリソース
    が反映されます

  1. この状態でサイトにアクセスすると cssのレスポンスを empty.cssの内容をブラウザに返してくれます
  2. チェックボックスで書き換えする/しないがすぐ適用できるので、1つづつ有効にして 重くなっているcssを特定していきます。
  3. 重くなっている cssを特定できたら、そのcssをダウンロードしてきて、書き換え先のファイルに適用します
    (スクリーンショットの[update.css])
  4. あとは[update.css]をコメントアウトしたり、書き換えたりすれば、原因と改修方法が本番で確認できます

    最終的には css内で呼び出しているリソースが重くレンダリングが ブロックされていたという事がわかりました。
    css内の呼び出しリソースが開発ツールのTimelineに表示されなかったので、 現状が把握できなかった場合は一つの手だと思います。
    特にデバッグする際に開発環境を作らなくてもいいのが楽です

思った事

今回いろいろな調査をしていく中で、デザイナーさんと一緒にガガッと勧められたのがよかったです。 会社が大きくないというのもあるかもしれませんが、職種の責任範囲ではなくプロダクト目線で一緒に 動けるのは改めてやりやすいと思いました。

textlint に乗り換えました

2017年12月27日
区分
tool
報告者:
morishita

このポストはactindi Advent Calendar 2017で 突発的に穴が開きそうになったとき用の記事として用意していたものです。 無事、使うことなくアドベントカレンダーは終了しましたが、そのままお蔵入りにするのも 何なので私の今年最後のポストにしようと思います。

文章のチェックどうしてますか?

まとまった文章を書く時に、表記のゆらぎやスペルミスをなくすためにチェックツールがほしい。

昔、Microsoft Wordを使っていたときには校正ツールのお世話になっていましたが、 マークダウンでしかほとんど文章を書くことがなくなったので、Wordは使わなくなりました。 でも、ニーズがなくなったわけではないので、そのために最近までRedPenを使っていました。

今後はRedpenをやめてtextlintを使うことにしました。

乗り換えの理由

乗り換えの理由は次のとおりです。

RedPenに比べてチェックが速い

RedPenはJavaで実装されているので、実行時にVMの起動に時間がかかります。 そのオーバヘッドが使っていて辛い。サーバとして起動しっぱなしにすると速いのだろうと思うのですが、それはそれで面倒。 nodeで動作するtextlintではそのようなオーバーヘッドは感じません。

Atomプラグインを導入するとAtomのlinterに表示される

RedPenにもAtomプラグインがあります(redpen - Atom package)。 このプラグインはコマンドパレットから実行するか、 ファイルの保存時にチェックしてくれるのですが、独自のインタフェースで表示されます。 一方、textlint ではAtom標準のlinterに結果が表示され、文中にも結果がわかりやすく表示されます。 どのルールに引っかかっているのかも表示されるので、ルールを緩和したい場合にも便利でした。 このプラグインの使い勝手の良さが一番の決め手でした。

設定がいろいろプラグインとして揃っている

この手のツールは設定項目がたくさんあって、どう設定するかが大変なのですが、 標準的な設定が textlint のプラグインとして多数公開されています。

それらをいくつか導入して私好みのを設定を作ることができました。

インストール

私は文章を書くときにはAtomエディタをよく使うので、Atomエディタのプラグインで使う前提での手順です。textlint 自体はnodeモジュールでCLIも用意されています。

事前準備

次のものをインストールします。詳細は割愛します。

textlint のインストール

今後設定していく場所になるので、適当なところにディレクトリを作って、textlint をインストールします。 私は $HOME/.textlint にインストールしました。 他の場所にインストールする場合、以降、読み替えて下さい。

$ mkdir $HOME/.textlint
$ cd $HOME/.textlint
$ npm init -y
$ npm instsll -S textlint
$ touch .textlintrc

.textlintrc は後に設定します。

Atomプラグインをインストール

プラグインのインストールは次のコマンドでも、Atomの設定画面でもどちらで良いです。

$ apm install linter-textlint

Atom の設定で textlint の設定を開き次を設定します。

  • textlint Rules Dir: $HOME/.textlint/node_modules
  • .textlintrc Path: $HOME/.textlint/.textlintrc

textlint の設定

続いて、textlintのルールの設定です。

プラグインのインストール

先程作った $HOME/.textlint に移動して、プラグインをインストールします。 私は次のプラグインを使っています。

コマンドにすると次の通りです。

$ cd $HOME/.textlint
$ npm install -S textlint-rule-preset-ja-spacing textlint-rule-preset-ja-technical-writing textlint-rule-date-weekday-mismatch textlint-rule-spellcheck-tech-word

.textlintrc の設定

preset系のプラグインはインストールするだけで有効になるようです。 それ以外は設定ファイルで有効にします。

私は次の設定で使っています。 preset系のプラグインで、デフォルト適用される幾つかのルールを false にして緩和しています。

{
  "rules": {
    "preset-ja-spacing": {
      "ja-space-between-half-and-full-width" : false
    },
    "preset-ja-technical-writing": {
      "ja-no-weak-phrase" : false,
      "no-exclamation-question-mark" : false
    },
    "date-weekday-mismatch" : true,
    "spellcheck-tech-word" : true
  }
}

チェック結果

atom-textlint 上のスクリーンショットのようにエラーがあればlinterにリストアップされます。 エラーの箇所にも印が付き、マウスオーバーするとどのチェックでエラーなのかが表示されます。

最後に

アクトインディでは、エンジニアを募集 しています。少しでもご興味があれば、いちど話だけでもしてみませんか。

HTTP/HTTPS判定をnginx側で判定するようにしました

2017年12月25日
区分
Rails
報告者:
endo

こんにちは、endoです。

いこーよのHTTP/HTTPS判定をアプリケーション側から、nginxで行うようにしました。

rails5へのアップデート作業の途中経過報告に書いていたことを実現しました。

理由としては、下記の通りです。

  • アプリケーション側で行うより、ALB/nginx側で行う方がコンピュータに優しい
  • rails5のupdate作業中にssl_requirementのメンテナンスをなくしたかった

対応としては、下記のことに注意しないといけません。

  • HTTPSリダイレクト
    • 一部HTTPでもアクセス可能
  • ALBのhealth check対応

HTTPSリダイレクト

いこーよはALBでHTTPS判定を行っているので、その前提でお話しします。

ALBは暗号化処理を終わらせた状態で後続のリクエストを流してくれます。

user    →    ALB    →    nginx    →    rails
      暗号化       暗号解除(平文)

ALBは暗号化処理を解除した証にヘッダーにX-Forwarded-Protoをhttpsという値をつけてくれています。

nginxは$http_x_forwarded_protohttpsかどうかを判定すれよくなります。

httpアクセスの場合は、ALBはヘッダーにX-Forwarded-Protoをつけてこないので、それを利用します。

ALBはヘッダーにX-Forwarded-Protoをつけてこないので、それを利用します。

location / {
  ...

  if ($http_x_forwarded_proto != https) {
    return 301 https://$host$request_uri;
  }

  ...
}

これでHTTPSのリダイレクトは可能になりました。

一部HTTPでもアクセス可能

こちらは特定のURLの場合の設定をnginx側に書きます。


location /foo {
  try_files $uri @app;
}

try_filesでそのまま通常の設定に送ります。

ALBのhealth check対応

health checkをHTTPS通信で行うと暗号を解除しないといけなくなるのでHTTP通信で行います。

ここで気をつけないといけないことです

  • health checkはHTTP
  • health check以外はHTTPS
location /bar {
  # ヘルスチェックはHTTP通信を許可する
  if ($http_user_agent ~ "ELB-HealthChecker") {
      proxy_pass http://app_server;
      break;
  }

  # HTTP通信の場合は、HTTPSにリダイレクトする
  if ($http_x_forwarded_proto != https) {
      return 301 https://$host$request_uri;
  }
  try_files $uri @app;
}

User AgentでALBかどうかを判定します。

リリース時の対応

nginxの設定を反映すれば完了です。

いこーよはマルチAZなので、アクセスを片方に寄せてnginxを起動することもできるのですが、若干めんどくさいですね。

nginxはプロセスで設定を「反映する前」と「反映後」を共存できるので,それを利用して反映します。

sudo /opt/nginx/sbin/nginx -t←設定ファイルを確認する

sudo kill -USR2 `cat /var/run/nginx.pid`←設定後のプロセスを立ち上げてくれて、反映前のプロセスと共存します。
sudo kill -WINCH `cat /var/run/nginx.pid.oldbin`←反映前のworkerプロセスを終了する
sudo kill -QUIT `cat /var/run/nginx.pid.oldbin`←反映前のプロセスを終了する

これでリリースは終了です。

唯一の懸念点は、health checkのUser Agentが変更されたら、health checkでサーバーが落ちていきます。

こんなことがあるのかは、わからないですが、現象さえわかっていれば対応は取れます。

以上です。

補足

ステージングでhealth check対応かどうかを調べていたときの方法です。


curl 'http://staging.net/foo' -H 'User-Agent: ELB-HealthChecker'

curlコマンドのUser Agentを入れて確かめていきました。

参考

無停止でnginxを手動upgradeする

おでかけ施設のTwitterの利用状況

2017年12月24日
区分
sns
報告者:
morishita

この記事は actindi Advent Calendar 2017 の12月24日の記事です

いこーよはお出かけ先を探す子育てパパ・ママ向けのサービスですが、 掲載させていただいているおでかけ施設の運営者様も大切なお客様です。 施設運営者様がいこーよ上で施設の魅力を知らせる方法としてしては、 施設情報や見どころがあるのですが、それ以外にもイベントやお知らせといった機能を提供しています。

Webのトラフィックがスマホ中心になり、利用ユーザの皆様のニーズも変化してきているのではと思っています。 世の中的に消費の傾向はモノからコトに移行していると言われています。それがいこーよにも起こっているのではないか? お出かけ先を探すときにも楽しそうな場所ではなく、楽しいコトがありそうなところを探そうとしているのではないか?  そのニーズに応えるように施設側も、今日のイベントや今日の見所といった旬の施設の魅力をお知らせしたいと思っているのではないか?

そうなると発信する側も、それを受け取る側もより鮮度の高い情報へのニーズが高まると思われます。

情報を発信するには「知らせたい内容(ネタ)」と「伝えたい気持ち(モチベーション)」が必要です。施設様はそのようなネタとモチベーションをお持ちなのか?

潜在的なニーズを探るためにリアルタイム性の強い情報をやり取りするプラットフォームであるTwitterを施設様がどれくらい使っているのかを調べてみました。

と、ここまでもっともらしい前置きを述べましたが、ちょっと気になった個人的な興味を 満たすためだけのポストですのでご了承ください。

Twitterアカウントを持つ施設は約15%

いこーよに登録されているおでかけ施設すべてを対象にするのはちょっと数が多いので 約1万件を抽出して、Twitterアカウントがあるかどうかを調べました。 なお、各アカウント所持者に問い合わせだわけではないので判定は私の推測です。 施設名とアカウント名が一致しており、それが認証アカウントである場合は確定ですが、それ以外は間違いを含んでいる可能性はあります。

だいたい、1割くらいかなーと思っていたのですが、Twitterアカウントの保有率は約15%と 予想より少し多かったです。 全国的なばらつきはどうなのかなと思ったので、都道府県ごとに集計してみました。 まあ、東京、神奈川は予想通り高いですね。

アカウント保有率マップ アカウント保有率グラフ

アカウント年齢、ツイート数とフォロワー数

アカウントが作成されてからの経過年数(アカウント年齢)は次の様になります。

アカウント経過年数グラフ

直近1年の間に作られた比較的新しいアカウントが割りと多いですね。 近年、Twitterで発信したい熱が高まっているのでしょうか。 一方で、すでに5年以上経過しているアカウントも約4割と結構多いことがわかります。 ちなみに、Twitterは2008年に日本語版ができて、2009年ごろから日本で本格的に普及し始めたようです。早くから取り組まれている施設様が予想以上に多い印象です。

次にツイート数別のアカウント数の分布は次のようになっています。 ツイート数分布グラフ

ツイート数500未満のアカウントが40%程度と多いですが、一方で、5000ツイート以上発信している活発なアカウントも1割ほど存在します。

ツイート数の多少はアカウントを作成してからの経過時間にもよると思われるので アカウント年齢✕ツイート数の分布を求めてみました。

アカウント経過年数グラフ

若いアカウントでツイート数が少ない傾向はまあ、予想通り鮮明です。 割合の大きなところの傾向を見ると、1日に1ツイートするアカウントが多のかなぁと思われます。それより活発なアカウントでは年間1000ツイート程度かなといったところです。

フォロワー数

Twitterアカウントの運用にとって、フォロアー数というのは重要なKPIです。 ツイートはフォロアーのタイムラインに表示され、フォロアーが多いほどツイートが人の目に入る確率が高まるからです。

フォロワー数の分布をグラフにしたのが次のグラフです。

フォロワー数分布グラフ

1000〜5000フォロアーを獲得しているアカウントが多そうです。 それ以上となると一気に減るので5000フォロアー獲得するのは難しいとわかります。 ちなみに最もフォロアーが多かったのは某夢の国リゾートで227万超。さすが。

フォロアー数はアカウント年齢やツイート数が大きいほど増える傾向があると予想できますが、実際にはどうか?

アカウント年齢✕フォロワー数の分布を表したのが次のグラフです。

アカウント年齢✕フォロワー数分布グラフ 5年以上続ければ25%のアカウントは数千以上のフォロワーを獲得しているようですね。

続いて、ツイート数✕フォロワー数の分布を表したのが次のグラフです。

ツイート数xフォロワー数分布グラフ

ツイート数が少ないとフォロアーも少ないのは、明らかですが、1000フォロアー以上を獲得するためにツイート数がそれほど大きな要素ではないように感じます。しかし、5000ツイート頑張れば、数千のフォロアーを得られると言えそうです(5000ツイートするまでにアカウント年齢もそれなりに大きくなることもあるのでしょうが)。

始めると継続している

アカウントを作って運用を始めるときにはモチベーションが高いのですが、 それを維持してアカウントの運用を継続するのが難しいのでは? 放置されているアカウントが多いのでは? と思っていました。 しかし、その予想は完全に外れました。

直近1年以内にツイートしているアカウントの割合を1年アクティブ率とここで呼ぶことにして求めてみました。同様に半年アクティブ率、1ヶ月アクティブ率も求めてみました。その結果は次のとおりです。


  • 1年アクティブ率: 88.5 %
  • 半年アクティブ率: 85.6 %
  • 1ヶ月アクティブ率: 77.8 %

なんと、7割以上のアカウントが少なくとも1ヶ月以内にツイートしており、継続して情報発信に利用されています。

Twitterが集客に有効であると認められているのか、やめた時の減少を懸念して続けているのか、あるいは伝えたいという熱が大きく持続するからなのか。 いずれにせよ多くの施設で一度Twitterを始めると情報発信に利用し続けるようです。

まとめ

さて、とりとめない内容になってしまいましたが、ざっくりまとめるとこんな感じでしょうか。

  • Twitterをプロモーションに活用しているのは15%程度。
  • 早いタイミングでTwitterを使い始めた施設も多い。
  • 放置されているアカウントは少なく、積極的に情報発信に利用されているアカウントは7割を超える。

個人的に気になったのは1年以内に作られた新しいアカウントが思ったより多いのだなぁということですね。

最後に

アクトインディでは、おでかけ施設を応援したいエンジニアを募集 しています。少しでもご興味があれば、よろしくお願いします。

Kotlin Android Extensionsを使ってみよーよ

2017年12月15日
区分
Android
報告者:
honda

この記事はactindi Advent Calendar 2017の15日目の記事です。

Androidエンジニアのhondaです。去年のアドベントカレンダーではKotlinのことを書きましたが今年もKotlinについて書きます。よろしくお願いします。

現在、Android版いこーよではKotlinを使っています。100%Kotlinです。 弊社でKotlinアプリを作ってみたい方はこちらとかこちらなどで応募お願いします。お願いします!

前置きはこのくらいにして、Android版いこーよではKotlin Android Extensionsを使っています。 なかなか、プロダクトでのKotlin Android Extensionsの採用を聞かないので、今回はKotlin Android Extensionsの素晴らしさをお伝えしたいと思います。

Kotlin Android Extensionsとはなにか?

Kotlin Android ExtensionsはKotlinの開発元であるjetbrains社が開発したAndroid開発を支援するためのプラグインです。 このプラグインを使うことによって、Androidアプリエンジニアの方ならおなじみの「findViewById」を使わずにコードからUIの要素を参照することが出来るようになります。

Kotlin Android Extensionsを導入してみよう!

Android Studio 3.0以上で新規プロジェクトを作成するとデフォルトで Kotlin Android Extensions使えるようになっています。素晴らしいですね。 既存プロジェクトの場合はbuild.gradle (Module: app)に以下を追加するだけです。

apply plugin: 'kotlin-android-extensions'

簡単ですね。

Kotlin Android Extensionsでコードを書いてみよう!

・Activity

簡単なアプリを作ってみましょう。 TextViewとButtonが配置された画面でTextViewには”Hello World!”と表示されています。 ButtonをタップするとTextViewの”Hello World!”の表示が”ハローワールド!”に切り替わります。 コードは以下の通りです。

Activityのレイアウトファイル
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="sample.samplekotlinandroidextensions.MainActivity">

    <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
              android:text="Hello World!"
              app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent"
              app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />
    <Button android:id="@+id/button" android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:layout_marginTop="8dp"
            android:text="ハローワールド!" app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" />

</android.support.constraint.ConstraintLayout>
Activityのソース
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        this.button.setOnClickListener {
            this.textView.text = "ハローワールド!"
        }
    }
}

これでfindViewByIdを使わずにtextViewに対して文言を設定することが出来ます。 レイアウトファイルでIDに設定した名前でコード上から参照出来ます。

・Fragment

次にFragmentで試してみましょう。 先程のActivityのSample同様にButtonをタップするとTextViewの内容が変わるものを作成します。

Fragmentのレイアウトファイル
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                             xmlns:app="http://schemas.android.com/apk/res-auto"
                                             xmlns:tools="http://schemas.android.com/tools"
                                             android:id="@+id/frameLayout"
                                             android:layout_width="match_parent" android:layout_height="match_parent"
                                             tools:context="sample.samplekotlinandroidextensions.SampleFragment">

    <TextView android:id="@+id/helloFragmentTextView" android:layout_width="wrap_content"
              android:layout_height="wrap_content" android:text="@string/hello_fragment"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
    <Button android:id="@+id/helloFragmentButton" android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp" android:layout_marginTop="8dp"
            android:text="@string/hello_fragment" app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/helloFragmentTextView" />

</android.support.constraint.ConstraintLayout>
Fragmentのソース
class SampleFragment : Fragment() {

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_sample, container, false)
        view.helloFragmentButton.setOnClickListener {
            view.helloFragmentTextView.text = "ハロー フラグメント"
        }
        return view
    }
}

ここで注意しなければならないのがonCreateViewでのUIの要素の参照方法です。 上記のサンプルの用にonCreateViewではinflateしたviewに対してUIの参照を行わないといけません。

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?): View? {
    val view = inflater.inflate(R.layout.fragment_sample, container, false)
    this.helloFragmentButton.setOnClickListener {
        this.helloFragmentTextView.text = "ハロー フラグメント"
    }
    return view
}

上記の様にUIに参照しようとするとNullPointerExceptionが発生します。 onCreateView以外では下記の様にUIに参照出来ます。

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    this.helloFragmentButton.setOnClickListener {
        this.helloFragmentTextView.text = "ハロー フラグメント"
    }
}

・RecyclerView

次にRecyclerViewで試してみましょう

RecyclerViewに表示するItemのレイアウトファイル
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView android:id="@+id/sampleRecyclerItemTextView" android:layout_width="wrap_content"
              android:layout_height="wrap_content" android:layout_marginBottom="8dp"
              android:layout_marginEnd="8dp" android:layout_marginStart="8dp"
              android:layout_marginTop="8dp"
              tools:text="TextView"
              app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
RecycleViewのAdapterコード
class SampleRecyclerViewAdapter(private val itemDataList: List<String>) :
        RecyclerView.Adapter<SampleRecyclerViewAdapter.ViewHolder>() {
    override fun getItemCount(): Int = itemDataList.count()

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder? {
        parent ?: return null
        return ViewHolder(LayoutInflater.from(parent.context).inflate(
                R.layout.view_sample_recycler_item, parent, false))
    }

    override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
        holder?.itemView?.sampleRecyclerItemTextView?.text = itemDataList[position]
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        init {
            itemView.setOnClickListener {
                Toast.makeText(
                        itemView.context, 
                        itemView.sampleRecyclerItemTextView.text, 
                        Toast.LENGTH_SHORT).show()
            }
        }
    }
}

RecycleViewのAdapterがListに入った文言をItemのレイアウトファイルを使ってリスト表示しています。 レイアウトのsampleRecyclerItemTextViewへはViewHolder内でitemView経由で参照します。

Kotlin Android Extensionsの挙動を理解しよう!

さて、とても便利なKotlin Android Extensionsですが、内部でどんなことをしているのでしょうか? Activityのサンプルコードを逆コンパイルしてJavaコードに変換して、どんなことをしているのか理解していきましょう。

public final class MainActivity extends AppCompatActivity
{

    public MainActivity()
    {
    }

    public void _$_clearFindViewByIdCache()
    {
        if(_$_findViewCache != null)
            _$_findViewCache.clear();
    }

    public View _$_findCachedViewById(int i)
    {
        if(_$_findViewCache == null)
            _$_findViewCache = new HashMap();
        View view1 = (View)_$_findViewCache.get(Integer.valueOf(i));
        View view = view1;
        if(view1 == null)
        {
            view = findViewById(i);
            _$_findViewCache.put(Integer.valueOf(i), view);
        }
        return view;
    }

    protected void onCreate(Bundle bundle)
    {
        super.onCreate(bundle);
        setContentView(0x7f09001b);
        ((Button)_$_findCachedViewById(R.id.button)).setOnClickListener((android.view.View.OnClickListener)new android.view.View.OnClickListener(this) {

            public final void onClick(View view)
            {
                view = (TextView)_$_findCachedViewById(R.id.textView);
                Intrinsics.checkExpressionValueIsNotNull(view, "this.textView");
                view.setText((CharSequence)"\u30CF\u30ED\u30FC\u30EF\u30FC\u30EB\u30C9\uFF01");
            }

            final MainActivity this$0;

            
            {
                this$0 = mainactivity;
                super();
            }
        }
);
    }

    private HashMap _$_findViewCache;
}

_$_findViewCache というハッシュマップのプロパティを用意して、Viewを管理しています。 参照時に_$_findViewCacheにViewインスタンスが無ければ、findeViewByIdをしてViewインスタンスを格納して参照し、すでに_$_findViewCacheにViewインスタンスがあれば、それを参照するという処理だということがわかりました。

注意点

Kotlin Android Extensions、とても便利ですが気をつけなければならない部分があります。

・誤って他の画面のUIへの参照するコードが書けてしまう。ビルドも通る。

Kotlin Android ExtensionsはData Bindingのようにコード生成しているのではなく、UIの要素をハッシュマップで管理しています。 誤って他の画面の要素を参照するコードを書いてしまっても、ビルドは正常に通ってしまうので気づけません。 実行時に_$_findCachedViewByIdでNullが返却されるため、NullPointerExceptionでクラッシュして、気づくことになります。 レイアウトファイルの要素のIDは他の画面の要素のIDが被ったり、どこの画面の要素なのかわからなくなることを防ぐために命名規則を設けておくと良いかもしれません。 例えば、「sampleActivityTextView」というような感じです。

・設計と相談

すでにデータバインディングを使っているような既存のプロジェクトではKotlin Android Extensionsを使っていくとなると設計を変えざるを得ないと思います。 使用する場合は設計と相談の上、ご使用ください。

まとめ

  • Kotlin Android Extensionsはjetbrains社製のAndroid開発支援プラグイン。
  • Kotlin Android Extensionsは導入が簡単!
  • Kotlin Android Extensionsを使うとfindViewByIdを使わずに簡潔にUIを参照するコードが書けるようになる!
  • FragmentやRecyclerViewのAdapterなどでも使える!けど、Activityでのやり方とは違うので注意。
  • Kotlin Android ExtensionsはUIの要素をViewインスタンスとしてハッシュマップで管理している。
  • 間違って他の画面のUIを参照するとクラッシュするので命名規則などで回避。
  • 使う時は設計と相談。
 | 

技師部隊からの
お知らせ

【求人】エンジニア募集しています。

本頁の来客数
八十七万千百七十六名以上(計測停止中)

メンバー一覧

アクトインディ技師部隊員名簿

アクトインディ技師部元隊員

アクトインディへ

カテゴリー

アクトインディ

aaaa