20200221のAndroidに関する記事は8件です。

【Android】Fragment切り替えの時にアプリバーを更新する

概要

FragmentTransactionreplace()からadd()に変更した際、Fragment遷移時に更新していたアプリバーのタイトルやメニューボタン等が更新されなくなった。
add()の場合でもアプリバーを更新できる方法をまとめる。この記事ではonBackStackChangedListenerを使っている。

Fragment遷移時にアプリバーの要素を更新する

MainActivity.kt
class MainActivity : AppCompatActivity() {
    val activeFragment: BaseFragment?
        get() {
            val fragment = supportFragmentManager.findFragmentByTag("baseFragment")
            return fragment as? BaseFragment
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ... () ...
        // BackStackが変更された時
        supportFragmentManager.addOnBackStackChangedListener {
            activeFragment?.updateActionBar()
        }
    }
}
BaseFragment.kt
abstract class BaseFragment : Fragment() {
    ... () ...
    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        updateActionBar()
        return null
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
    }

    open fun updateActionBar() {
        appActivity?.supportActionBar?.apply {
            setTitle("")
             // 戻るボタンを表示するかどうか
            setDisplayHomeAsUpEnabled(showsBackBarButton)
            setBackgroundDrawable(ColorDrawable(Color.parseColor("#008577")))
        }
    }
}

BackStackが変更された時に、Fragment遷移後にアプリバーを更新する(activeFragmentは遷移後のFragmentになる)。
各FragmentはBaseFragmentを継承して作成し、updateActionBar()をオーバーライドして、Fragmentごとのタイトルやメニュー等をセットする。

HomeFragment.kt
class HomeFragment : BaseFragment() {
    ... () ...
    override fun updateActionBar() {
        super.updateActionBar()
        this.setHasOptionsMenu(true)
        appActivity?.supportActionBar?.title = "ホーム"
    }
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.home_menu, menu)
    }
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_next -> {
                // SampleFragmentへ遷移するコード
                true
            }
            else -> {
                super.onOptionsItemSelected(item)
            }
        }
    }
}
SampleFragment.kt
class SampleFragment : BaseFragment() {
    ... () ...
    override fun updateActionBar() {
        super.updateActionBar()
        this.setHasOptionsMenu(true)
        appActivity?.supportActionBar?.apply {
            setTitle("サンプル")
            setBackgroundDrawable(ColorDrawable(Color.RED))
        }
    }
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.sample_menu, menu)
    }
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_sample -> {
                // icon pressed
                true
            }
            else -> {
                super.onOptionsItemSelected(item)
            }
        }
    }
}

update_actionbar_home.png update_actionbar_sample.png
これで、Fragment遷移の時にアプリバーのタイトルやメニューなどの要素が更新されるようになっ……あれ?

update_actionbar_sample_increase_icon.png
update_actionbar_sample_increase_icon_line (3).png
お分かりいただけただろうか……

しかもこの「次へ」ボタンも押せるため、SampleFragmentとそのメニューアイコンが無限に増える。無限ループって怖くね?
update_actionbar_sample_increase_icon_4.png   update_actionbar_sample_increase_icon_infinite.png

表示しているFragmentのメニューのみを表示する

SampleFragmentに遷移したにもかかわらず、HomeFragmentのメニューが表示されたままになってしまっている。
そこで、現在表示しているFragmentのメニューのみを表示したい。

画面遷移をする時に、this.setHasOptionsMenu(false)で遷移前のFragmentのメニューを隠すことにする。

HomeFragment.kt
class HomeFragment : BaseFragment() {
    ... () ...
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_next -> {
                // このFragmentのメニューを隠す
                this.setHasOptionsMenu(false)
                // SampleFragmentへ遷移するコード
                true
            }
            else -> {
                super.onOptionsItemSelected(item)
            }
        }
    }
}

これで、違うメニューが表示されなくなった。
update_actionbar_sample_success.png

まとめ

  • onBackStackChangedListenerを使って、Fragment遷移時にアプリバーの要素を更新した。
  • this.setHasOptionsMenu(false)でFragment遷移時に遷移前のFragmentのメニューを隠した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS/androidアプリのレイアウト微調整がめんどくさかった話

はじめに

iOSアプリのレイアウト微調整、めんどくさいですね。
機種もたくさんあるし、スクショ撮ってくらべてやり直して、また撮って・・・。
おまけにモックとスクショのサイズをあわせてくらべたりね。超面倒:joy:

ってやってたんですが、もっと楽な方法がありました。

楽な方法

シミュレータ/エミュレータと、できればzeplin等を使う。

zeplinという便利ツールでデザイナーとやりとりしてて、モックもそれなんですが、サイズがiOSシミュレータと一緒なので、リサイズも必要無し。スクショ無し。
並べて比べるのみ。
Androidはzeplinでzoom to fitして同じサイズにして使う。

zeplin紹介記事

zeplinじゃなくてもいいしね。モックの表示サイズ調整できれば良し。

終わりに

一時期ライブラリの関係でシミュレータ向けビルドができなくなってて、すっかり使わなくなってたんですが、シミュレータをガシガシ使った方が楽だってことに気がつきました。

日英切り替えとかも2台起動しておいて、それぞれにしておいて、設定切り替え無しで確認できますしね。

もっと早く気づけば良かった:joy:

超絶に楽になりました。

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

[Android]Dagger2の導入

はじめに

AndroidアプリにDaggerを導入した際の手順と
自分がDaggerを学び始めた際に、とっかかりとして欲しかったなぁと思う情報をまとめます。

今回の記事は自分自身がDagger初心者ということもあり、初歩的な内容となっています。
その分、同じDagger初心者の方に分かりやすく書けたらいいなと思いこの記事を書き始めました。

Daggerは、JavaやKotlinでDI(Dependency Injection)を行うことをサポートするライブラリです。
DIはたびたび「依存性の注入」と訳されますが、この訳がDIを意味不明なものにしていると思っています。
ネットで検索していると「DI = 依存オブジェクトの注入」と訳されている方がおり、
とてもスッキリしたので本記事でも「DI = 依存オブジェクトの注入」で進めようと思います。
Dependency Injectionを「依存性の注入」と訳すのは非常に悪い誤訳

Dependency Injection

例えば、ViewModelでSharedPreferencesを扱う必要があるとき、
SharedPreferencesのラッパークラスをSharedPreferenceManagerとして定義していたとすると、
単純に実装すると下記の例のようになると思います。

class SampleViewModel (
    app: Application
) : AndroidViewModel(app) {
    private val prefManager: SharedPreferenceManager = 
        SharedPreferenceManager(app.applicationContext)
}

このようにすると、
ViewModelが本来知る必要のないSharedPreferenceManagerのインスタンス生成方法を知る必要が出てきます。
ここで、ViewModelで必要なオブジェクト(依存オブジェクト = Dependency)を外部から注入(Injection)するのがDependency Injectionです。

class SampleViewModel (
    private val prefManager: SharedPreferenceManager
) : ViewModel()

ただし、このように単純にコンストラクタで依存オブジェクトを注入しても
結局、呼び出し元(この場合はActivityやFragment)でも知る必要のない
SharedPreferenceManagerのインスタンス生成方法を知る必要が出てきます。
このジレンマを簡単に解消してくれるのがDaggerです。

導入

開発言語はKotlinです。

まず、app/build.gradleにDaggerへの依存関係を追加します。

dependencies {
    def dagger_version = "2.26"

    // dagger
    implementation "com.google.dagger:dagger:$dagger_version"
    kapt "com.google.dagger:dagger-compiler:$dagger_version"
}

画面上部の"Sync Now"をクリックしてDaggerへの依存関係を解決します。

アノテーション

Daggerでは、変数やクラス、コンストラクタ等にアノテーションをつけることで
クラス同士の依存関係を定義できたり、インスタンスのスコープを明確にしたりすることができます。

ここでは、Daggerを導入することで利用できる下記のアノテーションについて
簡単な説明を記載したのちに、具体的な実装例を記載します。

  • Component
  • Module
  • Inject
  • BindsInstance
  • Provide
  • Singleton

Component

Componentアノテーションが付与されたインターフェースは、オブジェクト注入屋さんです。
オブジェクトを注入して欲しいと宣言されている(Injectアノテーションが付与されている)変数やコンストラクタにオブジェクトを注入してくれます。

Module

Moduleはインスタンス生成屋さんです。
Componentが見つけた、Injectアノテーションが付与された変数のインスタンスを生成します。
各クラスで知る必要のなかったインスタンスの生成方法は、Moduleが全て知ることになります。

Inject

クラスのコンストラクタやフィールドに対してInjectアノテーションを付与することで
Componentがそれを見つけて、クラスが依存するオブジェクトを注入してくれます。

BindsInstance

Componentに対してインスタンスをバインドするアノテーションです。
主に、開発者は生成方法を知らない(知る必要がない)けれど、アプリ内で必要なインスタンスがバインドされます。
Androidアプリケーションの場合、Contextをバインドすることがほとんどかなと思っています。

Provide

Module内で、インスタンスの生成方法を記載する際に利用するアノテーションです。
Moduleをインスタンス生成屋さんとすると、Provideはレシピのようなものです。

Singleton

インスタンスをシングルトンで生成したい時に付与するアノテーションです。

実装例

まずは、オブジェクト注入屋さんであるComponentを定義します。

@Singleton
@Component
interface AppComponent {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): AppComponent
    }
}

Componentには、Component自体を生成するFactoryインターフェースを宣言します。
Factoryインターフェースには、Componentを生成することを示すため
@Component.Factoryアノテーションを付与します。

createメソッドの引数として受け取るcontextには、BindsInstanceアノテーションを付与して
contextインスタンスをComponentにバインドします。

次に、インスタンス生成屋さんであるModuleを定義します。

@Module
class AppModule {

    @Singleton
    @Provides
    fun provideSharedPreferenceManager(
        context: Context
    ): SharedPreferenceManager {
        return SharedPreferenceManager(context)
    }

}

ここでは、SharedPreferenceを管理する自作クラス(SharedPreferenceManager)の
インスタンス生成方法をModuleに記載し、
さらにSingletonアノテーションを付与することで、SharedPreferenceManagerをシングルトンインスタンスとして提供するよう定義しています。

そしてComponentに戻り、インスタンス生成屋さん(Module)の存在を伝えます。

@Singleton
@Component(
    modules = [AppModule::class]
)
interface AppComponent {

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): AppComponent
    }
}

ここまで進めば、あとはどこに対してDIを適用するかを決めるだけです。
今回はアプリ全体に適用させたいので、Applicationクラスを継承したクラスにComponentを持たせてアプリ全体にDIを適用させます。

class MyApplication : Application() {
    val appComponent: AppComponent by lazy {
        DaggerAppComponent.factory().create(applicationContext)
    }
}

これにより、Componentはアプリ内全域のInjectアノテーションが付与された変数に対して
Moduleで生成されたインスタンスを注入することができるようになります。

下記は、SharedPreferenceManagerに依存するSampleViewModelクラスを定義する際の例です。

class SampleViewModel @Inject constructor(
    private val preferenceManager: SharedPreferenceManager
) : ViewModel()

最後に

Daggerの概念が分かりにくく、導入に苦労しましたが、
導入してみるとアプリの全体像がかなり把握しやすくなり、インスタンスの取得が簡単にできるのでコードの可読性も上がりました。

今回紹介したのはDaggerのほんの一部の機能で、私自身もまだまだ勉強中です。
誤記等ありましたらコメントでご指摘頂けますと幸いです!

参考

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

Android10でimage_pickerを使ってライブラリから画像を読み込めない時の対処方法

画像が読み込めない!

Flutterで写真にアクセスしようとしてimage_pickerプラグインを使用している
Android10の端末でファイルにアクセスしようとするとpermission denied
targetSdkVersionを29にしていると発生

TL;DR

対処方法のみ記載
以下リンクにあるようにAndroidManifestにおまじないを書く

<manifest ... >
  <!-- This attribute is "false" by default on apps targeting
       Android 10 or higher. -->
    <application android:requestLegacyExternalStorage="true" ... >
      ...
    </application>
</manifest>

参考リンク:
https://developer.android.com/training/data-storage/compatibility
https://developer.android.com/training/data-storage/shared/media?hl=ja#storage-permission

issue
https://github.com/flutter/flutter/issues/41459

実装などはあとで記載します。

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

Androidのソフトウェアキーボードの高さと表示状態をお手軽取得

概要

Androidのキーボードの高さと表示されているかを知る方法が標準で用意されていないので自作してみました.
そのままコピペすれば動きます.
コードは10行くらいです.

ソースコード

    var layoutBottom: Int = 0 // 現在のlayoutのbottomを保存
    var isShowingKeyboard: Boolean = false // キーボードが表示されていればtrue
    var keyboardHeight: Int = 0 // キーボードの高さ
    override fun onCreate(savedInstanceState: Bundle?) {
        val mRootView = window.decorView.findViewById<ViewGroup>(android.R.id.content)
        layoutBottom = mRootView.height
        mRootView.viewTreeObserver.addOnGlobalLayoutListener {
            val rect = Rect()
            mRootView.getWindowVisibleDisplayFrame(rect)
            isShowingKeyboard = layoutBottom > rect.bottom
            keyboardHeight = abs((layoutBottom - rect.bottom))
            layoutBottom = rect.bottom
        }
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] installreferrer:1.1.1の罠

「INSTALL_REFERRERインテントブロードキャストが廃止される」という公式アナウンスがあった。

https://android-developers.googleblog.com/2019/11/still-using-installbroadcast-switch-to.html

広告系ライブラリを使っているとAndroidManifestにINSTALL_REFERRERがだいたい入ってるのため、対応しなきゃならない。

対応方法

まずAndroidManifest.xmlからINSTALL_REFERRERを消す

- <receiver>
-     <intent-filter>
-         <action android:name="com.android.vending.INSTALL_REFERRER" />
-     </intent-filter>
- </receiver>

次にapp/build.gradleにcom.android.installreferrer:installreferrerを追加するのだが、、、
ここで自分は最新バージョンの1.1.1を追加した。

implementation 'com.android.installreferrer:installreferrer:1.1.1'

このバージョンが失敗だった。

ここでトラップ発動

Android Studioの Analyze APK でAndroidManifestを見てみると、余計なパーミッションが追加されている。

    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <uses-permission
        android:name="android.permission.READ_PHONE_STATE" />

    <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE" />

3つも!?

  • WRITE_EXTERNAL_STORAGE
  • READ_PHONE_STATE
  • READ_EXTERNAL_STORAGE

特にREAD_PHONE_STATEが追加されたままリリースすると、ユーザーは驚くこと間違いなし

解決方法

installreferrer:1.0を使うようにしよう。

- implementation 'com.android.installreferrer:installreferrer:1.1.1'
+ implementation 'com.android.installreferrer:installreferrer:1.0'

これで自動付与されるパーミッションは消えるはずだ

なぜ起きたのか?

おそらくGoogle側のバグで、過去に意図しないパーミッションが自動付与されることが何回か起きてるらしい。

https://developers.google.com/android/guides/releases

スクリーンショット 2020-02-21 9.03.21.png

今後、ライブラリを更新したらパーミッションを確認したほうがいいかも(面倒くさい…)

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

【kotlin】自作TwitterクライアントAndroidアプリでハッシュタグをリンク化する

自作TwitterクライアントAndroidアプリでハッシュタグをリンク化する

最近、qiitaに寄稿していなかったので久々に寄稿。
Kotlin系の記事です。

やりたかったこと

https://wandering-engineer.tech/wp-content/uploads/Screenshot_1580788577.png

自作Twitterクライアントアプリ「TwitMorse 〜モールス信号でつぶやこう、あなたのSOSに誰かが答えてくれる〜」のAndroid版での挙動で、ハッシュタグに反応するLinkMovementMethodを実装して、ハッシュタグ検索結果画面へ遷移させよう、というものです。

  • ハッシュタグの色付け
  • ハッシュタグをタップしたらハッシュタグ専用画面への遷移

この時、kotlinのMatcherの挙動で苦労したので、後学のために残します。

修正前の実装(クラッシュ発生します)

以下、修正前のコード

        /**
         * ハッシュタグを抽出してタップできるようにする
         * タップするとHashTagSearchResultActivityに遷移する
         * いずれStringUtilなどのUtilクラスにまとめられるようにtextViewも渡す。
         */
        fun onTapHashTag(text: String, textView: TextView) {
            textView.movementMethod = LinkMovementMethod.getInstance() //ClickableSpan#onClickを動かすのに必要
            val spannableString = SpannableStringBuilder(text)
            val hashTagRegex = Regex("(?:^|\\s)([##]([^\\s]+))[^\\s]?")
            val matcher = Pattern.compile(hashTagRegex.toString()).matcher(text)
            // 参考 : https://qiita.com/droibit/items/75416c0955b797931bb8#kotlintext
            hashTagRegex.findAll(text)
                    .map { it.value }
                    .forEach {
                        val clickableSpan = object : ClickableSpan() {

                            override fun onClick(widget: View) {
                                val hashTagSearchResultActivity = Intent(widget.context, HashTagSearchResultActivity::class.java)
                                hashTagSearchResultActivity.putExtra(IntentKeyUtil.HASH_TAG_SEARCH_QUERY, it)
                                hashTagSearchResultActivity.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                                widget.context.startActivity(hashTagSearchResultActivity)
                            }

                            override fun updateDrawState(ds: TextPaint) {
                                super.updateDrawState(ds)
                                ds.isUnderlineText = false
                            }
                        }
                        // ここが問題の箇所
                        spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                        textView.text = spannableString
                    }
        }

ちょいと長めのコードで申し訳ないですが、
ポイントは2つ

// ここが問題の箇所
spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannableString

Matcher.find()を呼ばないとMatcher.start()もMatcher.end()も正常に動作しない

ハッシュタグの色付けには成功したものの、そのハッシュタグをタップすると下記例外でクラッシュします。
この修正に1日半かけて悩みました・・・。

java.lang.IllegalStateException: No successful match so far
修正前
// ここが問題の箇所
spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannableString

そんなMatchはおきねぇよ!!と。

そこで下記のように修正したら、正常に動作しました。

修正後
if (matcher.find()) {
    spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    textView.text = spannableString
}

上記を見れば分かるようにmatcher.find()を呼んであげるとクラッシュがなくなりました。

修正後の実装

        /**
         * ハッシュタグを抽出してタップできるようにする
         * タップするとHashTagSearchResultActivityに遷移する
         * いずれStringUtilなどのUtilクラスにまとめられるようにtextViewも渡す。
         */
        fun onTapHashTag(text: String, textView: TextView) {
            textView.movementMethod = LinkMovementMethod.getInstance() //ClickableSpan#onClickを動かすのに必要
            val spannableString = SpannableStringBuilder(text)
            val hashTagRegex = Regex("(?:^|\\s)([##]([^\\s]+))[^\\s]?")
            val matcher = Pattern.compile(hashTagRegex.toString()).matcher(text)
            // 参考 : https://qiita.com/droibit/items/75416c0955b797931bb8#kotlintext
            hashTagRegex.findAll(text)
                    .map { it.value }
                    .forEach {
                        val clickableSpan = object : ClickableSpan() {

                            override fun onClick(widget: View) {
                                val hashTagSearchResultActivity = Intent(widget.context, HashTagSearchResultActivity::class.java)
                                hashTagSearchResultActivity.putExtra(IntentKeyUtil.HASH_TAG_SEARCH_QUERY, it)
                                hashTagSearchResultActivity.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                                widget.context.startActivity(hashTagSearchResultActivity)
                            }

                            override fun updateDrawState(ds: TextPaint) {
                                super.updateDrawState(ds)
                                ds.isUnderlineText = false
                            }
                        }
                        if (matcher.find()) {
                            spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                            textView.text = spannableString
                        }
                    }
        }

これでmatcherの参照が動くためか、java.lang.IllegalStateException: No successful match so farのクラッシュは発生しなくなりまして、思い通りの挙動をしてくれるようになりました。

複数ハッシュタグのリンク化

一つのTwitter投稿にハッシュタグが一つなわけがありません。下記やっておかないと、ひとつめのハッシュタグにしか反応しなくなるので、注意点として一応あげておきます

hashTagRegex.findAll(text).map { it.value }.forEach {}

参考資料

宣伝

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

Androidアプリ開発時にWiFi接続で無線デバッグ

Androidアプリの開発をしていると、実機確認する際にPCとAndroid端末が繋がっているのって邪魔じゃないですか?
私は邪魔でした!無線化しましょう!
環境はmac想定です。

まずはadbを使えるように

Android端末をコマンドラインで操作するために、Android Debug Bridge(adb)を使います。
Android Studioを入れている場合はすでにadbが入っていると思うので、まずはパスを通します。

bash_profileを編集しましょう。

vi ~/.bash_profile

パスを追加します。

# 下記を追加
export PATH=$PATH:/Users/[ユーザー名]/Library/Android/sdk/platform-tools

source の再読込を行い適用。

source ~/.bash_profile

これでオッケー!
パスが追加されているかは下記コマンドで確認できます。

echo $PATH

Android端末を接続

Android端末とPCを同じWi-Fi環境に置いてください。

事前にAndroid端末のIPアドレスを確認しておきます。
[設定] -> [システム] -> [端末情報] -> [機器の状態] -> [IPアドレス] などで確認できます。

Android端末とPCをケーブルで接続した状態で、下記を実行します。

adb tcpip 5555
adb connect xxx.xxx.xxx.xxx #ここにIPアドレス

これでケーブルを抜いても、デバッグできるようになります!
便利。

接続を解除する場合は下記を実行。

adb disconnect

せっかくなのでスクリプトも作るか

接続をひとつのコマンドで出来るようにしましょう。
てきとーにスクリプト入れる用のディレクトリを作ってパスを通しておきます。

mkdir /User/[ユーザー名]/scripts

パスを追加

# 下記を追加
export PATH=$PATH:/Users/[ユーザー名]/scripts

source の再読込

source ~/.bash_profile

次にスクリプトを書くファイルを作成します。
ここで作成したファイル名が実行するコマンドになります。
ファイル名は adb connect の略で adbc としましたが、好きに決めてもらって大丈夫です。

touch /User/[ユーザー名]/scripts/adbc
vi /User/[ユーザー名]/scripts/adbc

作成した adbc ファイルの内容は下記のようにします。

#!/bin/sh

# サーバー再起動
adb kill-server && adb start-server

# android端末に接続
adb tcpip 5555 && adb connect xxx.xxx.xxx.xxx #ここにIPアドレス

行頭に #!/bin/sh を付ける必要があるため注意してください。
また、スクリプトを実行可能にするため、ファイルの権限編集を行います。

chmod u+x /User/[ユーザー名]/scripts/adbc

ここまでで完了です!
Android端末をPCにつないで、ターミナルで下記を実行すれば接続されます!

adbc

ちなみに、IPアドレスを動的に指定したい場合は、スクリプトを下記のように修正します。

#!/bin/sh

# サーバー再起動
adb kill-server && adb start-server

# android端末に接続
adb tcpip 5555 && adb connect $1

これで実行時にIPアドレスを渡せるようになります。

adbc xxx.xxx.xxx.xxx #ここにIPアドレス

これでどこでも無線デバッグ!

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