20201023のAndroidに関する記事は12件です。

【超初心者向け】Android入門 EditText編

はじめに

前回はButtonを解説したので、今回はEditTextについて解説していきたいと思います。

記事の対象

  • Androidアプリ開発を始めたばかりの人
  • 前回の記事を読んだ方

EditText

EditTextとは、入力可能なテキストフィールドです。

1. EditTextを配置していく

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".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" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="30dp"
        android:layout_marginTop="30dp"
        android:layout_marginEnd="30dp"
        app:layout_constraintTop_toBottomOf="@id/textView" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="Click!"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/editText" />

</androidx.constraintlayout.widget.ConstraintLayout>

前回のTextViewとButtonの間にEditTextを入れていきます。

2. 実行

一旦このまま実行していきましょう。

スクリーンショット 2020-10-23 20.06.04.png

下線が付いているところがEditTextです。このまま入力するだけだとつまらないですよね。

なので今回はボタンを押したら上のTextViewに入力したテキストを反映させるようにしましょう。

3. MainActivity.ktでEditTextを認識させる

MainActivity
package com.example.helloworld

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView

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

        val textView = findViewById<TextView>(R.id.textView)

        val editText = findViewById<EditText>(R.id.editText)

        val button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            textView.text = editText.text
        }

    }
}
val editText = findViewById<EditText>(R.id.editText)

でActivityにEditTextを認識させます。

4.ボタンをクリックした時の動作を記述する

button.setOnClickListener {
    textView.text = editText.text
}

textViewにeditTextの入力値を代入します。

5.実行!

EditTextに何かしら入力してから、ボタンをクリックしてみてください!
そうするとEditTextの入力値がTextViewに反映されてると思います。

6.まとめ

今回はEditTextの使い方、というかEditTextを使ってTextViewに入力を反映させる方法を解説しました。

今更ですが、このシリーズは最終的にTodoアプリを作っていきたいと思っていますので最後までお付き合いいただけたらと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MutableLiveData は双方向データバインディングにも使える

データバインディングを利用して EditText に入力した内容を ViewModel に通知する処理を実装していました。

<EditText
    android:id="@+id/form_user_name_edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{viewmodel.userName}" />
FormViewModel.kt
class FormViewModel : ViewModel() {
    var userName = ObservableField<String>()
}

上記の実装だと、FormViewModelからuserNameを変更すると View に反映されますが、逆に画面からテキストを入力してもFormViewModeluserNameに反映されません。
つまり、ViewModel -> View は対応しているが、View -> ViewModel には対応していません。

双方向データバインディング

上記の問題を解決するためには、「双方向データバインディング」にする必要があります。
名前の通り、ViewModel -> View だけでなく、View -> ViewModel にも対応するための仕組みです。

対応方法

それぞれ以下のように変更します。

<EditText
    android:id="@+id/form_user_name_edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={viewmodel.userName}" />

xml の方は@{の間に=を追加するだけです。

FormViewModel.kt
class FormViewModel : ViewModel() {
    val userName = MutableLiveData<String>()
}

ViewModel の方はFormViewModeluserNameMutableLiveData<String>に変更するだけです。
また、MutableLiveData を使わない、従来型の方法(公式サイト記載)もあります。

FormViewModel.kt
import com.example.simplemvvmapp.BR

class FormViewModel : BaseObservable() {
    @Bindable
    var userName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.userName)
        }
}

ですが MutableLiveData を使う方が簡単に書けますし、ライフサイクルにも考慮してくれるので、MutableLiveData を使うことをお勧めします。

まとめ

MutableLiveData は双方向データバインディングにも使えるということがわかりました。
普通にデータバインディング以外にも使えるのでもっと積極的に使っていきたいです。

参考 URL

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

音声認識

音声認識を取り入れたアプリを開発した時にいろいろ詰まったことがあったので備忘録。

何を使った??

まず音声認識をするのに使ったのはSpeechRecognizer
Androidの音声認識をやるにはこれが基本みたいです。

やりたいこと

常に音声を拾いたい!拾ったら画面に認識した言葉を文字列として表示したい!

詰まったところ

ひとつめ

いざ音声認識をしてみたのですが、一回拾ったらその次の言葉を拾ってくれない。。
ええええ、なんで?となって調べまくりました。
連続して音声を拾うには、音声の取得が終わったところでまたSpeechRecognizer#startListening()を呼べばいいそう!
音声認識した文字列を受け取った先でstartListeningするためにリスナーを用意しました。
エラーの時も処理を継続させたかったので、onError時にもリスナーをよんでいます!

ざっくりですが、こんな感じ。

▼SpeechRecognizerのコールバック用クラス

class SpeechRecognizerListener(private val listener: SpeechResponseListener) : RecognitionListener {

    // 音声結果を返却する
    interface SpeechResponseListener {
        fun onResultsResponse(speechText: String)
    }

    ・・・略・・・
  
  // ネットワークまたは、音声認識エラー
    override fun onError(error: Int) {
        listener.onResultsResponse("")
    }

    // 認識結果
    override fun onResults(bundle: Bundle?) {

     ・・・略・・・

        // 結果を返却する
        if (speechText.isNullOrEmpty()) {
            listener.onResultsResponse("")
        } else {
            listener.onResultsResponse(speechText)
        }
    }
}

▼リスナー先

    override fun onResultsResponse(speechText: String) {
        if (speechRecognizer != null) {
            // SpeechRecognizerをdestroyする
            speechRecognizer!!.destroy()
        }
        setupSpeechRecognizer()
        setupRecognizerIntent()
        speechRecognizer.startListening(intent)
        muteBeepSoundOfRecorder(true, context)
    }

二回目以降のstartListening() を呼び出す前に、destroy()しないとうまくいかないことがあるみたいなので、nullじゃなかったらdestroyするようにしてます。

これで、連続して音声認識してくれることに成功しました。

ふたつめ

音声認識中になんだか音がピコンピコンうるさい。。
startListeningする時にmuteBeepSoundOfRecorderというとこで音ミュートにしたりしてるのになぜ?!と詰まる。

▼実際のイメージ

これじゃぴこぴこうるさいですよね。
でもこれを追加しただけで音が鳴り止んだんです。

am.adjustStreamVolume(AudioManager.STREAM_SYSTEM, AudioManager.ADJUST_MUTE, 0)

AudioManager.STREAM_MUSICに対してだけしか操作してなかったんですけど、AudioManager.STREAM_SYSTEMが必要だったんですねえ。笑

▼STREAM_SYSTEMを入れた後のイメージ

難しいなあ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Navigationでアニメーション付きで画面遷移させる

config.xmlとアニメーション用のxmlを作成する

main/res/values/config.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <integer name="config_transitionAnimTime">400</integer>
</resources>
res/anim/slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="50%p"
        android:toXDelta="0"
        android:duration="@integer/config_transitionAnimTime" />
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:duration="@integer/config_transitionAnimTime" />
</set>
res/anim/slide_out_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="-50%p"
        android:duration="@integer/config_transitionAnimTime" />
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="@integer/config_transitionAnimTime" />
</set>
res/anim/slide_in_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="-50%p"
        android:toXDelta="0"
        android:duration="@integer/config_transitionAnimTime" />
    <alpha
        android:fromAlpha="0.0"
        android:toAlpha="1.0"
        android:duration="@integer/config_transitionAnimTime" />
</set>
res/anim/slide_out_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="50%p"
        android:duration="@integer/config_transitionAnimTime" />
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="@integer/config_transitionAnimTime" />
</set>

navigationファイルでアニメーションを設定する。

res/navigation/nav_graph.xml
        <action
            android:id="@+id/action_hogeFragment_to_fugaFragment"
            app:destination="@id/fugaFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】プロジェクト作成

戻る

新規プロジェクト作成

※著者のAndrid Studioは日本語化されています。

  • Android Studioの以下の画面又は、[メニュー]→[新規]→[新規プロジェクト] image.png

  • とりあえず[空のアクティビティー]を選択し、[次へ] image.png

  • プロジェクト構成
    • 名前 変更は面倒なのでよく考える
    • パッケージ名 所持ドメインの逆順が一般的?
    • 保存ロケーション 保存先どこでも
    • 言語(JAVA or Kotlin) 今回はJAVA
    • 最小SDK 使用する環境に合わせて選択
    • Use legacy~ とりあえずチェック

[完了]を推すとプロジェクトが作られる
image.png
戻る

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】業務用Andoridアプリ開発への挑戦

前提情報

挨拶

乱筆乱文にて失礼いたします
業務改善のためAndroidアプリの開発に挑戦することに
開発手順を検討しつつ進める所存
都合により細部をぼかすことがあるはご容赦いただきたし
内容は随時追加予定

選定経緯

  • 紙に記入→PCへ入力の業務が非常に多く
    データ化されていない情報も多い
  • PC台数が少なく調べものもままならない
  • ネットワーク環境が弱いためオフラインでも使える
    →Androidでアプリ開発が最適では

投稿者スペック

  • システム管理者3年目(2020/10現在)
  • Android使用経験なし
  • Java経験なし

環境確認

  • Windows10 Pro + Java7
  • Android Studio 4.0
  • サーバー + SQL Server + Tomcat7
  • Androidタブレット(Android 9 Pie)

開発手順

要件定義

  • ターゲット補足
  • 理想形の構築
  • 現状の確認
  • すり合わせ

Androidアプリ

WebAPI

  • Tomcat7関連
  • http通信関連
  • 帳票印刷
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【未経験の方必見】アプリエンジニアが狙い目な理由

こんにちは。
この記事では、
「なぜアプリエンジニアが狙い目なのか」
「なぜアプリエンジニアが少ないのか」
にフォーカスを置いて書いていきます。

なぜアプリエンジニアが狙い目なのか

理由は簡単です。
母数が少ないからです。
この理由から、需要に対しての供給が足りていません。
経験者の採用を諦め、未経験者の教育に力を入れている会社もあるようです。

実際、弊社では全ポジションを募集していますが、
アプリエンジニアの面談回数はサーバーサイドやフロントに比べてかなり少ないです。

サーバーサイドやフロントに対してアプリエンジニアは
大体5分の1〜10分の1ほどしか応募がきません。
アプリエンジニアの中でもAndroidエンジニアは特に、iOSエンジニアの10分の1ほどしか
来ないため、Androidエンジニアの希少性はかなり強いと言えます。

なぜアプリエンジニアが少ないのか

Twitterを通していろいろな意見を見たり、実際に勉強会で交流を通して意見を聞いてきた中で、
アプリエンジニアが少ない理由がいくつか見えてきました。

苦手意識

初学者の人はまず、「どの言語をやるか」「どのポジションをやるか」
について選択が迫られます。
この時、「フロントの方がすぐに結果が見えてわかりやすい」
という意見がよく見られました。

また、「プログラミングの勉強を始めるならまずHTML・CSS
という暗黙の何かがあるように思えました。
(「HTML・CSSはプログラミング言語じゃない」という気持ちはわかります)
そこから派生してJavascript・jQuery・Vue.jsなどフロントエンジニアの道を歩む人が
多いのではないでしょうか。

アプリエンジニアを勧める環境が少ない

初学者の言語選択に対して、「PHP×Javascript」「Rails」
を勧める環境が多いように感じられました。
特に「PHP×Javascript」は、「初学者が最短で仕事につくのに最適だ」「一番簡単だ」
と勧める人をよく見かけました。

Railsが多い理由はスクールの影響が大きいかと思います。
確かにわかりやすいですし、レール(rail)にのっとった書き方ができるため初学者に優しいかもしれませんが、
逆に言えば経験者が爆速開発するためのフレームワークで少数精鋭でやるものだと言う声もあり、
初学者に本当に優しいのかと聞かれると
はっきりとYesと言えない言語です。

これらから分かるように、初学者にとってウケがいい言語は「手頃(そう)な言語」で、
またウケを狙った(?)スクールによってRails学習者が増え、
バランスが偏ってしまっているのではないかと推測しました。

iOSエンジニアはMacBookが必要

正確に言えば、MacBookがなくてもOSをぶち込めれば良いのですが、
いきなりの初学者がそんなことを思いつきませんし、できません。
勉強を始めるためにMacBookを買うと言うフィルターがあるために、
アプリエンジニアの頭数が少ないのだと推測しました。

Androidユーザーが少ない

アプリエンジニアの道に進んだとしても、iOSかAndroidかの分岐があります。
ここでは、多くの人がiOSエンジニアを選択することでしょう。
なぜなら日本では、世界でも稀にiPhoneユーザーが多いためです。
Android端末を使ったことがないのにAndroidの勉強をする人はかなり稀です。

結局のところアプリエンジニアは少ない

これらの理由から、エンジニア市場には偏りが見られます。
iOSエンジニアをやるにしても高価なMacbookが必要で、
Androidエンジニアへの道も国内OSシェアからその道に進む人があまりいません。

サーバー・インフラ>>アプリエンジニア(iOS >> Android)

のような構図が見られるため、ただでさえアプリエンジニアは狙い目であるのに
Androidエンジニアとなれば更に母数が減るのです。

これからプログラミングの勉強を始めようと考えている方がいましたら、
ぜひアプリエンジニアを検討してみてはいかがでしょうか。

誤解してはいけない

誤解してはいけないのが、「供給が足りないのだから簡単なんだ!」と言うことです。
あくまでも一定レベルのスキルは必要であり、それなりの勉強時間が必要になります。
はっきり言ってエンジニアになるには大変です。
数ヶ月でなれる人は天才だと思います。

最後まで記事を読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【超初心者向け】Android入門 Button編

はじめに

前回Android入門 TextView編をやったので今回はButtonに関して解説していきたいと思います。

記事の対象

  • Androidアプリ開発初心者
  • Androidをこよなく愛す人
  • 前回の記事を読んだ方

Buttonとは

その名の通りボタンです。
押して何かしらのアクションを起こすものですね。

1. activity_main.xmlにButtonを追加する。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".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="30dp"
        android:text="Click!"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView" />

</androidx.constraintlayout.widget.ConstraintLayout>

Buttonの中に書いてあることは最初は無視でいいです。
簡単に説明すると、Buttonがどのくらいの大きさで(layout:width, layout_height)、上にどれだけ余白をつけて(layout_marginTop)、ボタンのテキストはどうするか(text)、 どの位置に配置するか(layout_constraintHogetHoge)です。

興味がある方は色々試してみてくださいねー!

2. MainActivity.ktでButtonを定義する

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

        val textView = findViewById<TextView>(R.id.textView)

        textView.text = "こんにちは、世界"

        val button = findViewById<Button>(R.id.button)
    }
}

前回の続きからなので、textViewが定義されたままですね。

その下にボタンでボタンを定義していきましょう

勘がいい方はお気づきかと思いますが、findViewByIdメソッドがxml上で定義したidを元にViewを見つけてくれるんですね!

3.クリックリスナーを定義する

クリックリスナーというのはボタンをクリックした時にどんな動作をさせるかというものです。
今回はボタンをクリックしたらtextViewの文字がClicked!になるようにしましょう。

先ほどのval button = ~~の下に以下の一文を加えてください

button.setOnClickListener {
    textView.text = "Clicked!"
}

4.実行

さぁ実行してみましょう!
どうでしょう?

ボタンをクリックしたらこんにちは、世界がClicked!になりましたね。

5.まとめ

今回はButtonの解説をしていきました。

上記の通りにやっても動かない等あれば気軽にコメントやTwitterのDMに質問ください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【超初心者向け】Android入門 TextView編

はじめに

前回Android入門 Hello World編を書いたので、今回はHello World以外の出力をしてみたいと思います。

記事の対象

  • Android入門者
  • Android開発してみたい人
  • 前回の記事を読んだ方

TextViewとは

TextViewはテキスト表示するためのViewです。

前回の続きから書いていきましょう。

1. activity_main.xmlを確認

スクリーンショット 2020-10-20 20.52.58.png

左側のディレクトリ構成を確認します。
app/res/layoutの中にactivity_main.xmlがあることがわかりますね。
基本的にレイアウトファイルはapp/res/layout下に配置していきます。

2. xmlを見てみる

上記の画像の右上のタブが今はDesignが指定されていますが、これをSplitにします。
スクリーンショット 2020-10-20 20.55.42.png

この画面になっていると思います。

このTextViewの中にIDを付与してきます。

activity_main.xml
<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" />

3. MainActivityを見てみる

app/java/{package_name}を見てみるとMainActivity.ktがあることがわかります。

スクリーンショット 2020-10-20 20.58.37.png

こんな感じですね。
このonCreateメソッドの中で、Viewをいじっていきます。

MainActivity.kt
package com.example.helloworld

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView

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

        val textView = findViewById<TextView>(R.id.textView)

        textView.text = "こんにちは、世界"
    }
}
val textView = findViewById<TextView>(R.id.textView)

この一文でxmlで定義したTextViewをActivity側で認識させます。

textView.text = "こんにちは、世界"

この一文で認識したTextViewに新しい文を追加します。

4. 実行

さぁ、実行してみましょう!
スタートボタンを押すとどうでしょう。
スクリーンショット 2020-10-20 21.04.51.png

Hello Worldがこんにちは、世界に変わってますね。

まとめ

今回はTextViewを使って新しい文を表示させました。

動的に新しい文を表示させたい場合はこんな感じにすればOKですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【超初心者向け】Android入門 Hello World編

はじめに

今回は超初心者向け、AndroidでHello worldを出力するだけの記事です。

記事の対象

  • Androidアプリ開発をしたことがない人
  • Androidに挑戦してみたい人
  • Hello World大好きな人

環境構築

Android Studioをインストールするだけです。

初めてのAndroidアプリ

1. Create New Projectを選択します。

スクリーンショット 2020-10-20 19.33.11.png

2. Empty Activityを選択しNextを押下

スクリーンショット 2020-10-20 19.34.23.png

3. アプリ名を決めます

スクリーンショット 2020-10-20 19.39.44.png

Name: アプリ名(今回はHelloWorldにしました)
Package Name: パッケージ名(無視でいいです)
Save Location: 保存場所
Language: 言語(Kotlin or Java)
Minimum SDK: デフォルトで構いません。

これらが終わったらFinishを押下します。

4. Androidアプリの雛形が完成します

スクリーンショット 2020-10-20 19.47.00.png

5. Android端末を持っている方

端末によって違いますが、設定->デバイス情報といくと下の方にビルド番号というのがあるので、それを5回ほどタップします。
すると、「開発者オプションが有効になりました」という文言と共に、開発者オプションの設定ができるようになります。

開発者オプションを開き、USBデバッグをONにし、PCと繋げると、、、

スクリーンショット 2020-10-20 19.52.38.png

ここのスマホアイコンのところに自分の端末が表示されます。
あとはスタートボタン(再生ボタンみたいなやつ)を押すだけ!!

Screenshot_20201020-195348.png

Hello Worldが表示されます。

6. Android端末を持っていない方

スクリーンショット 2020-10-20 19.55.59.png

右から3番目、AVD Managerを開きます。
Create Virtual Deviceをクリックします。
適当な端末を選び、適当なOSバージョンを選びます。
そうすると、PC上で動作してくれる仮想端末が完成します。

あとはスタートボタン(再生ボタンみたいなやつ)を押すだけ!!

スクリーンショット 2020-10-20 19.58.30.png

Hello Worldが表示されます。

7. 完成

非常に簡単ですね!!
よくわからない環境構築をしなければHello Worldすら表示させてくれないとなると、初心者の方は心が折れそうになります。
ですが、AndroidアプリはAndroid StudioをインストールするだけでHello Worldを表示してくれます。

まとめ

ここまで記事を書きながら10分でHello Worldを表示させることができました。(インストール時間は除く)

この記事通りにやってもできない方はQiitaのコメントか、TwitterのDMに連絡いただければ解決しますので気軽に相談してください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】APKをデコンパイルしてみる

はじめに

最近Android開発を始めて、絶賛Kotlin勉強中の者です。今回ソースコードを難読化してbuildすることがあり、本当に難読化できているのかを確かめてみたかったので、デコンパイルすることにしました。ただ、なかなか思うようにデコンパイルができなかったので、成功した方法を備忘録として残しておきます。

今回は、下図の通り、build前のソースコードをKotlinで書いたので、.ktファイルをbuildして.apkファイルになったAndroidのコードをデコンパイルし、.javaファイルを作成しました。

デコンパイル図.png

環境・バージョン

  • macOS Catalina バージョン 10.15.6

失敗した方法

調べるとよく出てくるdex2jarjadを使う方法です。
この方法が定番なのかなと思ったので、この方法でデコンパイルをやってみましたが、わたしの場合上手くいきませんでした。

dex2jarやファイルを展開する手順を踏むことで、.classファイルが生成されます。
手順詳細はこちらへ。

その後、jadを使って.class.javaにデコンパイルします。

$ jad hoge.class

すると、次のようなエラーが出て、デコンパイルが進まなくなってしまいました。

Bad CPU type in executable

考えられる原因

調べた限り、確実な情報は出てこなかったので、推測です。

  • 現在のMac OSでは64bitのアプリケーションしか動作しないが、jadは32bitのツール?
  • jadは少し前からアップデートが止まっているみたい?

上記みたいなところが原因かなと思います。
きっと他にツールがあるはず、諦めます!

成功した方法

jadxを使う方法を使うと簡単にデコンパイルすることができました。
この方法はJDKが入っていなくても実行できます。

インストール

macだとHomebrewを使って簡単にインストールすることができます。

$ brew install jadx

実行

実行はこのコマンドのみ!
特にPATHを通すこともなく、どこからでも開くことができます。

$ jadx-gui

実行すると、下記のような画面が開くので、デコンパイルしたいapkファイルを開きます。

スクリーンショット 2020-10-19 21.04.34.png

これでデコンパイルは完了です。
非常に簡単ですね!

実行結果

元のコード(.kt)

package com.example.testqiita

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

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

        var message = findViewById<TextView>(R.id.text)
        val buttonEnglish = findViewById<Button>(R.id.button_en)
        val buttonJapanese = findViewById<Button>(R.id.button_jp)


        buttonEnglish.setOnClickListener {
            message.setText("Hello World!")
        }

        buttonJapanese.setOnClickListener {
            message.setText("こんにちは 世界!")
        }
    }

}

デコンパイルしたコード(.java)

ファイルを開くと、下記のようなディレクトリ構成で出力されます。

スクリーンショット 2020-10-12 23.12.00.png

MainActivityは下記のように3つのファイルに分かれていますが、同じ処理が読み取れることがわかります。

MainActivity
package com.example.testqiita;

import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import kotlin.Metadata;
import kotlin.jvm.internal.Ref;

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001a\u00020\u00042\b\u0010\u0005\u001a\u0004\u0018\u00010\u0006H\u0014¨\u0006\u0007"}, d2 = {"Lcom/example/testqiita/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_debug"}, k = 1, mv = {1, 1, 16})
/* compiled from: MainActivity.kt */
public final class MainActivity extends AppCompatActivity {
    /* access modifiers changed from: protected */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_main);
        Ref.ObjectRef message = new Ref.ObjectRef();
        message.element = (TextView) findViewById(R.id.text);
        ((Button) findViewById(R.id.button_en)).setOnClickListener(new MainActivity$onCreate$1(message));
        ((Button) findViewById(R.id.button_jp)).setOnClickListener(new MainActivity$onCreate$2(message));
    }
}
MainActivity\$onCreate\$1
package com.example.testqiita;

import android.view.View;
import android.widget.TextView;
import kotlin.Metadata;
import kotlin.jvm.internal.Ref;

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\u0010\u0000\u001a\u00020\u00012\u000e\u0010\u0002\u001a\n \u0004*\u0004\u0018\u00010\u00030\u0003H\n¢\u0006\u0002\b\u0005"}, d2 = {"<anonymous>", "", "it", "Landroid/view/View;", "kotlin.jvm.PlatformType", "onClick"}, k = 3, mv = {1, 1, 16})
/* compiled from: MainActivity.kt */
final class MainActivity$onCreate$1 implements View.OnClickListener {
    final /* synthetic */ Ref.ObjectRef $message;

    MainActivity$onCreate$1(Ref.ObjectRef objectRef) {
        this.$message = objectRef;
    }

    public final void onClick(View it) {
        ((TextView) this.$message.element).setText("Hello World!");
    }
}
MainActivity\$onCreate\$2
package com.example.testqiita;

import android.view.View;
import android.widget.TextView;
import kotlin.Metadata;
import kotlin.jvm.internal.Ref;

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\u0010\u0000\u001a\u00020\u00012\u000e\u0010\u0002\u001a\n \u0004*\u0004\u0018\u00010\u00030\u0003H\n¢\u0006\u0002\b\u0005"}, d2 = {"<anonymous>", "", "it", "Landroid/view/View;", "kotlin.jvm.PlatformType", "onClick"}, k = 3, mv = {1, 1, 16})
/* compiled from: MainActivity.kt */
final class MainActivity$onCreate$2 implements View.OnClickListener {
    final /* synthetic */ Ref.ObjectRef $message;

    MainActivity$onCreate$2(Ref.ObjectRef objectRef) {
        this.$message = objectRef;
    }

    public final void onClick(View it) {
        ((TextView) this.$message.element).setText("こんにちは 世界!");
    }
}

おわりに

いろいろ躓きましたが、結果的に簡単にデコンパイルできることがわかりました。こんなに簡単にソースの中身を見ることってできるんですね。びっくりです。同時に、難読化や堅牢化の必要性も改めて感じました。
堅牢化できるツールってすごく高価なようですね。。なので、リバースエンジニアリングされることを想定して被害が最小限で済むような設計をする必要がありますし、そのあたりは今後勉強しないといけないところだなと思いました。

ちなみに、Windowsでもjadxは使えるそうです!(Windows環境がなかったので、試せていません。)

失敗した方法(dex2jar、jad)・手順詳細

それぞれのツールのインストール、デコンパイルの方法の詳細を一応残しておきます。

dex2jar

dex2jarのインストールとPATHの設定を行います。
このツールは.dex.jar.classを圧縮したファイル)に変換するために使うものです。

$ brew install dex2jar

$ echo 'export PATH="$PATH:/usr/local/Cellar/dex2jar/2.0/bin"' >> ~/.bash_profile
$ source ~/.bash_profile

jad

jadのインストールを行います。
このツールは.class.javaに変換するために使うものです。

インストールに必要なcaskは、brewに標準で組み込まれるようになったそうです。

$ brew install homebrew/cask/jad

ターミナルでJDKを使えるようにする

d2j-dex2jarを使うにあたりJDKをインストールする必要があるようです。ただ、今回はJDKをOracleのサイトからインストールするのではなく、Android studioで使っているJDKをターミナルで使えるようにするために、PATHを通しました。

$ echo 'export PATH=$PATH:/Applications/"Android Studio.app"/Contents/jre/jdk/Contents/Home/bin' >> ~/.bash_profile
$ echo 'export JAVA_HOME=/Applications/"Android Studio.app"/Contents/jre/jdk/Contents/Home' >> ~/.bash_profile
$ source ~/.bash_profile

デコンパイル

apkファイルを展開します。

$ unzip app-debug.apk

d2j-dex2jarを使って生成されたclasses.dex.jarに変換します。

$ d2j-dex2jar classes.dex

classes-dex2jar.jarが生成されるので、展開します。

$ unzip classes-dex2jar.jar

(もともと難読化されているのかを確認していたので、気づかなかったのですが、このあたりで難読化設定を入れておくとMainActivity.classが見つかるのですが、入れてないと見つからないという不思議な状況になっていました。)
とりあえず続きを進めてみます。

jadを使って.class.javaにデコンパイルを行います。

$ jad hoge.class

この手順で、本来はデコンパイルが完了するはずです。

参考文献

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android で Youtube の動画を再生する[Kotlin]

つくるもの

YoutubeAPI を使って動画再生するためには、そもそも端末に Youtube アプリが入っている必要があるとかで、Youtube アプリを必要としない Youtube 動画プレイヤーを実装する。

現在は権利フリーの動画しか再生できず、おそらく大半の動画の再生に失敗してしまう。
(どうすればここの問題を解決できるのか知っている方がいれば教えていただきたいです…)

再生できることが確認できている動画
https://www.youtube.com/get_video_info?video_id=rvkxtVkvawc
これが再生できなければ実装側のバグです。

image.png

今回やったこと

できることは AndroidでYoutubeの動画を再生する とほぼ同じ。
このコードを参考に、下記の内容を追加実装した。

  • Kotlin化
  • ExoPlayer を中心に各ライブラリの更新
  • Youtube動画ID から直接再生

方法

ここのソースで動かせる
https://github.com/tktcorporation/ExoPlayerYoutubeSample

そこそこボリューミーになりそうなので、解説は 筆が乗れば or 要望があれば 追加します…

参考

https://qiita.com/TaigaNatto/items/384318fb73b4f4712f6e
https://qiita.com/TaigaNatto/items/d3ff98961c346098eaf7

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む