ViewBinding使ってみた
少し前にViewModelとDataBinding、LiveDataを使うことで画面の回転が行われても生成したテキストが消えなくなる処理を実装していたのですが、DataBindingよりもViewBindingの方が使いやすくて簡単だという記事を見つけたので実装方法を紹介しようと思いました。
目次
筆者の環境
PC:Windows10 エミュレータ API:27 IDE:AndroidStudio 4.2.2 使用言語:Kotlin version 1.5.2
サンプルアプリ
今回はViewModelとViewBinding、LiveDataを用いて以下の様なとても簡単なサンプルアプリを作成します。
アプリ起動時はただボタンがあるだけ。
ボタンを押すと、Hello_World!!
という文字列が画面に表示されるようになる。
勿論、画面を回転させてもHello_World!!
という文字列が消えることは無く(リセットされる)、もう一度ボタンをタップする必要もない。
ViewBindingの導入
まずプロジェクトの(:app)に以下のコードを追記して必要な機能を使えるようにします。
android { ・・・ buildFeatures { viewBinding true } ・・・ } dependencies { // 今回はViewModelとLiveDataも同時に使うのでここも追記 implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' }
レイアウトファイル
レイアウトについては詳しく説明する必要もないと思うので。
<?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" android:background="#dfe" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30sp" 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:text="表示する" android:layout_marginTop="20dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" /> </androidx.constraintlayout.widget.ConstraintLayout>
ViewModel
ViewModel はアクティビティやフラグメントに対するデータ置き場の役割を担います。
また、画面回転やウィンドウのサイズが変更されると、アクティビティやフラグメントインスタンスは再生成されますが、この ViewModel はインスタンスが保持されるという特徴を持っています。
class MyViewModel: ViewModel() { private val _hello = MutableLiveData<String>().apply { MutableLiveData<String>() } init { _hello.value = "" } fun setText(){ _hello.postValue("Hello_World!!") } fun hello(): MutableLiveData<String>{ return _hello } }
_hello
_hello
はViewModel内部のみで使えるprivateメンバで、後述するsetText()メソッド
やhello()メソッド
で使われます。
また、最初はinit
で空文字によって初期化されています。
setText()
見ての通り、Hello_World!!
という文字列をprivateなメンバである_helloメンバ
に代入しています。
セッターの様な役割を果たしていると思って構いません。
hello()
privateなメンバであるhello
の値を呼び出し元に返す処理です。
所謂、ゲッターです。
MainActivity
ここでようやく、今回のテーマであるViewBindingをふんだんに使っていきます。
class MainActivity : AppCompatActivity() { private val myViewModel: MyViewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) } private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater).apply { setContentView(this.root) } myViewModel.hello().observe(this, { binding.textView.text = myViewModel.hello().value }) binding.button.setOnClickListener { myViewModel.setText() } } }
myViewModel
ViewModelのインスタンスを取得している処理と思ってもらえれば大丈夫です。
onCreate以降ではこのインスタンスからViewModel内に定義したメソッドやメンバに対してアクセスできます。
binding
ここが今回のテーマであるViewBindingのメンバになるのですが、戻り値がActivityMainBinding
となっています。
今回はActivityでBindingを定義しているのでActivityMainBinding
ですが、もしこれがSubActivity
で定義される場合にはSubActivityBinding
という戻り値になるため注意が必要です。
またViewBindingを使うまでonCreateで記述されていたsetContentView(R.layout.activity_main)
という処理は不要になり、代わりに
binding = ActivityMainBinding.inflate(layoutInflater).apply {
setContentView(this.root)
}
という処理になります。
ViewBindingを用いると、今まで記述していたfindViewById
といった記述が不要になり、「binding.ViewのID名」になることが最大のメリットです!
observe
observe・・・つまり監視する処理です。
ViewModelで定義した_hello
メンバの値がsetText()
によって更新され、hello()
の戻り値が更新されるとその更新された値をレイアウトファイルで設定したTextViewに対してセットします。
まとめ
どうでしたか?
DataBindingと比べるとかなり容易に実装出来たのではないでしょうか?
実は更にMainActivityに記述する処理が減らすことができて、個人開発ならともかく複数人で開発しているアプリ等なら更に利便性が向上するライブラリもあったりするのですが、それはまた今度…。
参考・引用
Firebaseを使ってメール/パスワード認証を実装してみた
前々からFirebaseについて色々と勉強したいと思っていたが、5月のGWで時間に余裕が出来て勉強したのでその内容を共有するということと、メモ書きとして残すために記事を書きました。
目次
- 目次
- Firebaseって?
- 筆者の環境
- サンプルアプリの作成
- まとめ
- 参考・引用
Firebaseって?
まずFirebaseを知らない方に簡単に説明すると、Firebaseとは
Firebaseは、2011年にFirebase, Inc.が開発したモバイル・Webアプリケーション開発プラットフォームで、その後2014年にGoogleに買収された。 2020年3月現在、Firebaseプラットフォームには19の製品があり、9GAGを含む150万以上のアプリが利用されている。
とあり、Firebaseを使うことで様々なサードパーティーの認証を実装出来たり、Firebaseにデータを保存したり、クライアント端末に通知を送信する...etcといったことが比較的簡単に実装できるようになります。
筆者の環境
IDE:AndroidStudio4.21 ホストPC:Windows10 端末:Galaxy S8 SCV36 端末OS:Android9 使用言語:Java
サンプルアプリの作成
それでは今回の記事で作るサンプルアプリを紹介します。
また、これ以降ではコード前文の解説ではなく抜粋しての解説ですので、全ての処理を知りたい方はこちらのコードの全文を見ながら読むことを推奨します。
概要
起動直後のアプリのトップ画面は以下の画像のようになっています。
アカウントがある場合は「メールアドレスとパスワード」を入力してログインボタンをタップするとログインできます。
アカウントが無い場合は「SignUp」のスイッチを切り替えて、同じく「メールアドレスとパスワード」を入力してサインアップします。
なお、今回は特にメールアドレスの存在確認といった処理は行いません
(※後日、記事を出すかも…?)
「サインイン」と「サインアップ」の両方とも、認証フローが正常に終了すれば画面下部にユーザ情報が表示されるようにしています。
反対に、認証フローが失敗(メールアドレス or パスワードが違う)したならばトーストを表示させるようにしています
また、画面右上のオプションメニューからサインアウトすることが出来るようにもしています。
xmlの編集
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:background="#dfe" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_marginTop="15dp" android:text="@string/hello" android:textSize="25sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/description" android:layout_marginTop="5dp" android:text="@string/description" android:textSize="20sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:weightSum="1.1" android:orientation="horizontal"> <TextView android:id="@+id/mail_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:text="@string/mail" android:textSize="17sp" /> <EditText android:id="@+id/mail_edit" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:layout_marginStart="5dp" android:layout_weight="1" android:hint="@string/sample_mail" android:maxLines="1" android:maxLength="50" android:inputType="textEmailAddress"/> </LinearLayout> <com.google.android.material.textfield.TextInputLayout style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:layout_marginStart="5dp" android:layout_marginEnd="5dp" android:hint="@string/sample_pass" app:passwordToggleEnabled="true"> <com.google.android.material.textfield.TextInputEditText android:id="@+id/password_edit" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPassword" android:maxLines="1" android:maxLength="20"/> </com.google.android.material.textfield.TextInputLayout> <Button android:id="@+id/execute_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="10dp" android:text="@string/login_button" android:onClick="execute" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="@string/signup" android:layout_weight="0.95" android:gravity="right"/> <Switch android:id="@+id/signup_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_weight="0" tools:ignore="UseSwitchCompatOrMaterialXml" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="0dp" android:id="@+id/result" android:layout_weight="1" android:textSize="15sp" /> </LinearLayout>
最新のAndroidStudioを使ってる方はプロジェクトを作成した時点でxmlは「ConstraintLayout」になっていると思いますが、今回は「LinerLayout」を使用します。
com.google.android.material.textfield.TextInputLayout
app:passwordToggleEnabled="true"
上記の記述でユーザーは入力したパスワードをパスワード欄右のアイコンから確認できるようになります。
com.google.android.material.textfield.TextInputEditText
このフィールド内でパスワード入力欄の様々な設定(文字数制限やインプットタイプの指定等)が出来ます。
Firebaseとの連携設定
レイアウトや文字の記述が終わったら次は処理の記述なのですが、その前にFirebaseでアプリの登録を行い、連携を完了させておきましょう。
この記事では詳しい解説は行いませんが、Firebaseの公式ドキュメントでやり方が記述されているのでそちらをご覧ください。
SwitchListenerの定義
まず最初に「サインイン」と「サインアップ」の切り替えを行う「スイッチ」の処理を記述しましょう。*1
private class SignUpSwitchListener implements CompoundButton.OnCheckedChangeListener{ @Override public void onCheckedChanged(CompoundButton button, boolean isChecked){ flag = isChecked; if (flag){ execute_b.setText(getString(R.string.signup)); }else { execute_b.setText(getString(R.string.login_button)); } } }
onCheckedChanged
このメソッドの第2引数にswitchのon/offがtrue
とfalse
で格納されています。
そしてそれをflagというboolean型のグローバル変数に格納してその後の条件式や、他のメソッドでも用います。
ここではswitchの切り替えによってボタンテキストの中身を変更しています。
サインアップ時の処理
次に別クラスAuthenticationFlowClass
を作成してその中にまずはサインアップ処理を記述していきます。
public class AuthenticationFlowClass extends MainActivity{ private final FirebaseAuth mAuth; private final MainActivity mainActivity; AuthenticationFlowClass (MainActivity mainActivity){ this.mAuth = MainActivity.mAuth; this.mainActivity = mainActivity; } void CreateUser(String mail, String pass){ mAuth.createUserWithEmailAndPassword(mail, pass) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()){ Log.d("success","createUserWithEmail:success"); FirebaseUser user = mAuth.getCurrentUser(); mainActivity.updateUI(Objects.requireNonNull(user)); }else { Log.w("Error","createUserWithEmail:failure",task.getException()); Toast.makeText(mainActivity,"エラーが発生しました",Toast.LENGTH_SHORT).show(); } } }); } }
createUserWithEmailAndPassword
この内部でアカウント作成成功時と失敗時の処理がそれぞれ行われています。
第1引数にメールアドレス、第2引数にパスワードを渡して呼ぼ出します。
この時のmAuthはMainActivityのonCreate*2でFirebaseAuth.getInstance()
で初期化されています。
成功時はupdateUIに現在のログインしているユーザの情報を取得するgetCurrentUser()
によって格納されたFirebaseUser
を渡します。
(※updateUIについては後述します)
失敗時はトーストでエラーが発生しました
と表示されるようになっています。
サインイン時の処理
void SignIn(String email, String pass){ mAuth.signInWithEmailAndPassword(email, pass) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()){ FirebaseUser user = mAuth.getCurrentUser(); mainActivity.updateUI(Objects.requireNonNull(user)); }else { Toast.makeText(mainActivity,"Authentication failed.",Toast.LENGTH_SHORT).show(); } } }); }
signInWithEmailAndPassword
この内部でサインインの認証処理と、その結果を処理します。
第1引数はメールアドレス、第2引数はパスワードを渡して呼び出します。
成功時と失敗時の処理はサインアップ時と然程違いはありません。
updateUI処理
このupdateUI
ではメイン画面の下部にユーザ情報を表示させる画面表示処理です。
protected void updateUI(FirebaseUser user){ String email = "Email:" + user.getEmail() + "\n"; String emailVerified = "Verified:" + user.isEmailVerified() + "\n"; String uid = "Uid:" + user.getUid() + "\n"; String result_str = email + emailVerified + uid; resultText.setText(result_str); }
getEmail
FirebaseUserにはそれぞれの呼び出し元においてgetCurrentUser()
された現在のログインしているユーザの情報が格納されています。
その中でも`getEmail
はユーザのメールアドレスを取得するメソッドです。
user.isEmailVerified
このメソッドにはユーザのメールアドレスが認証されている(正しいメールアドレス)かどうかの情報を取得できるメソッドです。
ここでは解説していない確認メールの送信処理を別途、アプリ内に記述して処理を行うことでこの値がtrue
になります。
getUid
現在ログインしているユーザのユーザーID(一意の値)を取得することが出来るメソッドです。
認証フローを呼び出す
サインイン時の処理とサインアップ時の処理で認証フローの定義は行ったので、次はボタンを押したらその処理が呼び出されるコードを記述*3していきます。
public void execute(View view) { String mail_str = mail_e.getText().toString(); String pass_str = pass_e.getText().toString(); EditCheck(mail_str,pass_str); } private void EditCheck(String email,String pass){ if (email.length() > 0 && pass.length() > 0) { if (flag){ afc.CreateUser(email, pass); }else { afc.SignIn(email, pass); } } else if (email.length() == 0) { Toast.makeText(this, "mailアドレスが入力されていません", Toast.LENGTH_SHORT).show(); if (pass.length() == 0) { Toast.makeText(this, "passwordが入力されていません。", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(this, "passwordが入力されていません。", Toast.LENGTH_SHORT).show(); } }
execute
メールアドレスとパスワードを入力するエディットボックスからそれぞれ文字列を抽出して変数に代入しています。
またそれをEditCheck
という入力値チェックの関数に渡しています。
EditCheck
レイアウトファイルにも記載してメールアドレスとパスワードには文字数制限を掛けておきましたが、念のためJavaファイルの方でも文字数制限を記述しておきます。
また、そもそも入力が行われていないときや、制限文字数を超えて入力した場合はトーストを表示させて、認証フローに処理が飛ばないようにしています。
適切な文字数が入力された場合には、SwitchListenerで変化するflag
によって処理を分岐して認証処理を開始*4します。
サインアウト処理
アプリをアンインストールしたり、開きなおせば必然的にサインアウトも行われるでしょうが、それでは少し不便なのでユーザが明示的にサインアウトを行えるようにしましょう。
@Override public boolean onCreateOptionsMenu(Menu menu){ MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_options_menu_list,menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item){ int itemId = item.getItemId(); if (itemId == R.id.signout){ mAuth.signOut(); finish(); overridePendingTransition(0,0); startActivity(getIntent()); overridePendingTransition(0,0); } return super.onOptionsItemSelected(item); }
signOut
ここではオプションメニューを用いてサインアウトを行えるようにしています。
(※オプションメニューの詳しい解説は行いません)
サインアウトを行うにはFirebaseAuth.getInstance.signOut
でOKです。
onStart()のOverride
最後に現在ログインしていてサインアウトをしていないにも関わらず、アプリを開きなおすたびにメールアドレスとパスワードの再入力を求めるのはセキュリティの高いアプリ(銀行...etc)でない限り、不便です。
よってアクティビティライフサイクルのonStartメソッドをオーバーライドしてログインが行われていたら、updateUI
を呼び出すような処理を追加します。
@Override public void onStart(){ super.onStart(); FirebaseAuth mAuth = FirebaseAuth.getInstance(); FirebaseUser currentUser = mAuth.getCurrentUser(); if (currentUser != null){ updateUI(currentUser); } }
まとめ
今回はFirebaseでメールアドレスとパスワードを用いた認証を行う方法を解説しました。
やはりFirebaseを使うと自分でユーザ認証の実装を行うという面倒くさいことをしなくてよくなるのは大きなメリットですよね。
参考・引用
https://firebase.google.com/docs/android/setup?hl=ja
Android11でAsyncTaskが非推奨になった話
少し前の情報なのですが、Android11でAsyncTask
が非推奨となったという話を聞きました。
当時の自分はまだAndroidアプリの開発について勉強していた時期で、書籍を使って勉強していたのですが勿論、その書籍では当然AsyncTask
を使った手法で書かれていました。
そして、最近このことを知ったので実際にその代替実装手法としてどのようなものがあるのかを知るために調べて実装してみたので、その時のメモとして記事に残しました。
目次
AsyncTaskって?
そもそもAsyncTaskって何?って方に説明すると、Androidで非同期処理
と画面UI
の更新を同時に行うのを実装するために使うクラスのことです。
次に非同期処理の説明をするのですが、ここで気になる方もいると思います。
「非」ということは「非」ではない処理もあるのか?
結論から言うと、勿論あります!
それを同期処理
と言います。
同期処理と非同期処理の違い
では、同期処理と非同期処理の違いとは何でしょうか?
一言でいえば処理を順次に行うか、並列で行うかの違いです。
この説明だと少しイメージしにくい感じもするので、料理に例えたチャート(流れ図)を書いてみました。
同期処理
この処理では、料理を初めて1品目を作ってから2品目を作り始めています。
逆に言えば「1品目を作り終えるまでは2品目の作成に入れない」ということです。
つまり、1品目で膨大な時間が掛かってその間に2品目を作れる時間があっても作り始めることは出来ないということです。
そしてこのイメージを実際のアプリに適応すると以下のようになります。
1.処理1を実行(時間が掛かる処理) 2.処理2を実行(画面表示系の処理) 3.処理の終了
こうするとアプリとしては画面処理をしたいが、処理1が終わっていないので画面表示系の処理を行うことが出来ないことになってしまいます。
そしてユーザには「アプリがフリーズしたのか?」という風に思われることになってしまいます。
非同期処理
この処理では、1品目の食材を切った後にその食材を炒めながら2品目の食材を切り始めています。
こうすることで、たとえ1品目の調理に時間が掛かったとしても2品目の調理も並行して進めているのでさほど大きな問題は起きません。
これも先ほどと同様に、実際のアプリに適用すると以下のようになります。
1.処理1を実行(時間が掛かる処理) 2.処理2を実行(画面表示系の処理 -> 処理1を待たない) 3.処理の終了
よってアプリは処理1の結果を待たずに画面表示系の処理を行うことが出来るので、ユーザにアプリがフリーズしたようには思われなくなります。
これが非同期処理の強みです!!
実際に見てみる
同期処理と非同期処理の違いで同期処理と非同期処理の違いを文字とチャートで説明したので、今度は実際にアプリとして確認してみましょう!
同期処理
まず最初に、同期処理のアプリの流れを説明します。
ソース全体はこちらに掲載しています。
1.アプリが起動する 2.SlowProcessClass内でViewTextが実行される(処理が遅い) 3.画面表示が行われる 4.WebViewが読み込まれる
では実際にこちらからapkをダウンロード・インストールして、アプリを実行してみてください。
これからは、このアプリの処理についてもう少し詳しく説明します。
2.SlowProcessClass内でViewTextが実行される(処理が遅い)
Thread.sleep(10 * 1000);
SllowProcessClass
内のViewText
メソッドに上記の記述があるので、10000ミリ秒(=10秒)の間、処理が止まっている。
この間3.画面表示が行われるが実行できないので上記画像の状態で10000ミリ秒(=10秒)の間、画面が動かずユーザからはアプリがフリーズしているかのように見える。
処理が終了すると
This process was slow...
と表示される。
3.画面表示が行われる
アプリ名や「HelloWorld!」といった文字列が表示される。
4.WebViewが読み込まれる
最後にWebViewとしてGoogleのサイトが表示されたと思います。
非同期処理
まず最初に、同期処理のアプリの流れを説明します。
ソース全体はこちらに掲載しています。
1.アプリが起動する 2.SlowProcessClass内でViewTextが実行される(処理が遅い -> 非同期処理) 3.画面表示が行われる(2の処理の結果を待たない) 4.WebViewが読み込まれる(2の処理の結果を待たない)
では実際にこちらからapkをダウンロード・インストールして、アプリを実行してみてください。
これからは、このアプリの処理についてもう少し詳しく説明します。
(処理が終了した順に説明していくので、アプリの処理の流れの時の順序と前後して分かりにくくなるかもしれませんがすいません…。)
3.画面表示が行われる(2の処理の結果を待たない)
2.SlowProcessClass内でViewTextが実行される
が非同期処理として実行されているため、上記画像のように既にアプリ名
やHelloWorld!
等の画面表示系の処理が行われています。
4.WebView
WebViewとしてGoogleのホームページが表示されたと思います。
2.ViewTextメソッドの処理が終了する
ここでようやく、非同期で記述した処理が全て終了して
This process was slow...
と表示される。
非同期処理の記述方法
それでは実際にどのように非同期処理を記述したらいいのかを説明していきます。
(これから説明する内容はあくまで手法の1つです)
ExecutorServiceの利用
ExcutorServiceとは
終了を管理するメソッド、および1つ以上の非同期タスクの進行状況を追跡するFutureを生成できるメソッドを提供するExecutorです。
つまりこれを使えばスレッドを分割して上手く非同期の機能を実装出来そうです。
サンプルコードは以下のような感じです。
executorService = Executors.newSingleThreadExecutor(); executorService.execute(new Runnable() { @Override public void run() { // バックグランドで処理したい内容を記述 String result = spc.ViewText(); } }); private void shutdown(){ if (executorService == null){ return; } try { executorService.shutdown(); if (!executorService.awaitTermination(1L, TimeUnit.SECONDS)){ executorService.shutdownNow(); } }catch (InterruptedException e){ executorService.shutdownNow(); }finally { executorService = null; Thread.currentThread().interrupt(); } } @Override protected void onDestroy(){ super.onDestroy(); shutdown(); }
ここで注意すべきはExecutorService
の終了を忘れないようにすることです。
(※ExecutorServiceを使ったらシャットダウンするくせを付けておくと良いと思います)
ExecutorService
をシャットダウンする理由としては以下のような理由が挙げられます。
スレッドプールはタスクを待ち続けるので、スレッドプールが待機したままになるため、 メインスレッドが終了してもプログラムは実行したままになる。
UIを更新する方法
スレッドを分けた場合でありがちなミスなのですが、UIの更新はメインスレッドで行う必要があることに注意してください。
この問題を解決するのがHandler
です。
Handler
のpostメソッド
内にUIの更新処理をバッググラウンド処理の内部に追記します。
サンプルコードは以下のようになります。
// この上にはバックグラウンド処理が記述されている final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { textView.setText(result); } });
まとめ
今までAsyncTask
を使ってきた方には、ExecutorService
とHandler
を使った非同期処理はとても複雑に見えることでしょう。
(実際私は理解するのにかなり時間が掛かりました(笑))
と言っても、今はまだ非推奨になってから時間があまり経っていないので問題となっていませんが、この先のことを考えるとこのままずっとAsyncTask
を使い続けるわけにはいかないですし、だからといって非同期処理を使わないというのは論外です。
なので、ぜひこの方法を覚えて積極的に使っていきましょう!!
引用・参考情報
https://outofmem.tumblr.com/post/94711883294/android-executor-3
https://rightcode.co.jp/blog/information-technology/android-os-asynctask
Android NDKを使ってみた
AndroidNDKを使って簡単なアプリを作ってみようと思ったのですが、ドキュメントが古かったり少なかったりして大変だったのでメモとして記事にしました。
目次
NDKって何?
NDK(Native Development Kit)は、Android で C や C++(以下、ネイティブ)のコードを使用できるようにするツールセットです。
NDK のプラットフォーム ライブラリを活用することで、ネイティブ アクティビティを管理し、センサーやタップ入力など、実際のデバイスのコンポーネントにアクセスできるようになります。それほど経験のない Android プログラマーの場合、アプリを開発する際に必要となるのは Java コードとフレームワーク API に限られるため、通常、NDK はツールとして適していません。NDK が役に立つのは、以下のいずれかに該当する場合です。
・低レイテンシを実現したり、ゲームや物理学シミュレーションなど、演算負荷の高いアプリを実行したりするために、デバイスからできる限り高いパフォーマンスを引き出す必要がある場合。
・独自または他のデベロッパーの ネイティブライブラリを再利用する場合。
とあるように、簡単に言うと本来Javaで記述するAndroidアプリをCやC++で記述できるようにするためのツールということです。
私がこの先、演算不可の高いアプリを開発するかは分かりませんが(笑)
NDKの導入
まずAndroidStudioを起動して、画面左上の方にある「File」→「Settings」をクリック。
すると設定画面が開くと思うので「Appearance & Behavior」→「System Settings」をクリックした後、「Android SDK」を選択しましょう。
ここでAndroid SDKに関する様々なツールをインストール出来ます。
「Android NDK」のインストールは赤く囲んでいる部分の「SDK Tools」で行うので、そこをクリックしてください。
後は上記画像にも既に表示されているように「NDK(Side by side)」と「CMake」のチェックボックスにチェックを付けてください。
この時、「CMake」のチェックボックスの付け忘れには注意してくださいね!
インストールはネット環境にもよりますが、10分くらいかかると思うので気長に待ちましょう。
NDKを使ってみよう
NDKの導入にてAndroidでネイティブコードを扱うためのツールのインストールは終わりました。
そこで、サンプルアプリをNDKを用いて実装してみましょう。
先にサンプルアプリを動かしてみたい方はこちらをクリックしてapkを端末にインストールして起動してみてください。
サンプルアプリの挙動
今回扱うサンプルアプリの簡単な挙動を以下に示しておきます。
1.EditBoxに自分のニックネームを入力 2.ニックネームをネイティブコードで書かれた関数に渡す 3.関数でメッセージの付加を行った文字列をMainActivityに返す。
ソースファイルを配置するディレクトリの作成
まずはネイティブコードを記述しておくためのソースファイルを配置するディレクトリの作成を行います。
プロジェクトエクスプローラの赤く囲んだ部分をクリックして、「Project」を選択してください。
その後、「src」ディレクトリの配下にある「main」ディレクトリで右クリック→「New」→「Directory」をクリックしましょう。
ディレクトリ名は「cpp(jniでもいけるようです)」としてください。
ソースファイルの作成
ソースファイルを配置するディレクトリの作成にて作成したディレクトリで右クリックをして「New」→「C/C++ source File」をクリックしましょう。
ファイル名は今回は「hello」、Typeは「.cpp」にでもしておきます。
これでソースファイルの作成が完了しました。
(※記述自体はもう少し後で行います)
CMakeLists.txtを作成する
次に、CMakeListsと呼ばれるC/C++のビルド設定ファイルを作成、記述していきます。
ファイルを生成する場所はcpp直下で大丈夫です。
cmake_minimum_required(VERSION 3.4.1) add_library( # 識別用ライブラリ名を指定 hello # 共有ライブラリとしてビルドさせる SHARED # C/C++ソースへの相対パス指定 hello.cpp ) target_link_libraries( hello android log )
add_library
ここには作成したC/C++ライブラリの名前・ビルド方法・相対パス指定します。
今回はhello.cppの設定を追加しています。
注意点としては、「C/C++ソースへの相対パス指定」では相対アドレスを指定する必要があることです。
target_link_libraries
ここには独自ライブラリ名、android, log を指定しておきます。
こうすることでC/C++側でもログ出力が使えるようになります。
build.gradle(:app)の設定
ここの設定は簡単です。
android{}の中に
externalNativeBuild{ cmake{ path "src/main/cpp/CMakeLists.txt" } }
と記述するだけです。
MainActivityに関数呼び出し処理を記述する
次に、ソースファイルの作成にて作成したソースファイルに内容を記述していく前に先に関数の呼び出し処理を記述しておくとソースファイル内に関数定義を記述するで便利なので、関数の呼び出しを記述します。
public native String getMessage(String msg); static { System.loadLibrary("hello"); }
System.loadLibrary(hoge)
ここのhoge
の部分は、CMakeLists.txtを作成するで定義した「識別用ライブラリ名」と一致させる必要があるので注意しましょう。
ソースファイル内に関数定義を記述する
ここから今回はC++で関数の定義を記述していくのですが、先にMainActivityに関数呼び出しを記述していると関数名を記述しただけで自動である程度の雛形を作ってくれます。
extern "C" JNIEXPORT jstring JNICALL Java_com_websarva_wings_android_ndksample_MainActivity_getMessage(JNIEnv *env, jobject thiz,jstring j_name) { // TODO: implement getMessage() const char *name = env->GetStringUTFChars(j_name, 0); std::string msg = "Hello "; msg+= name; msg += "\nWelcome to JNI World!!"; return env->NewStringUTF(msg.c_str()); }
getMessage関数の処理
Hello hoge Welcome to JNI World!!
という文字列を生成してJavaコードに返す。
extern "C"
この記述をすることで、上コードのようにextern "C"としてマングリング(C++特有の機能)をさせなくする。
C++の場合、こうしないとビルドエラーが出ます。
env->NewStringUTF(hoge.c_str());
ネイティブ側からJavaに返すときはenv->NewStringUTF(msg.c_str());
のように変換する必要あります。
MainActivityの処理を記述
最後にMainActivityに残りの処理を記述していきます。
このサンプルアプリの全コードは以下にあります。
https://github.com/daiki0508/NDKSample
package com.websarva.wings.android.ndksample; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements TextWatcher { private EditText editText; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = findViewById(R.id.edit); editText.addTextChangedListener(this); } public void Execute_NDK(View view){ String name = editText.getText().toString(); String result = getMessage(name); textView = findViewById(R.id.result_text); textView.setText(result); } public native String getMessage(String msg); static { System.loadLibrary("hello"); } @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void afterTextChanged(Editable editable) { String name = editable.toString(); Button button = findViewById(R.id.button); // ユーザーのニックネームが未入力の場合はボタンを有効にしない if (name.length() > 0){ button.setEnabled(true); }else { button.setEnabled(false); } } }
まとめ
NDKを使ってネイティブコードで簡単なサンプルアプリを作ろうと思ったのですが、意外にも記事の情報が古くてビルド設定系でエラーが多発していました(笑)
引用・参考情報
https://pisuke-code.com/android-how-to-install-ndk/
https://github.com/android/ndk-samples
https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html
Androidエミュレータの紹介とroot化
最近、私のPCに入れているAndroidエミュレータが増えてきたので整理する意味でも一度まとめてみようと思い書きました。
あくまでまとめなので、エミュレータの詳しいインストール方法等は説明しません!
目次
エミュレータって何?
そもそもAndroidエミュレータって何?って方に説明すると
Androidエミュレーターとは、パソコン上に仮想のAndroid OSをインストールし、パソコンでAndroidアプリを使用できるソフトである。 Androidのスマホと同様に、アプリの対応により、相応のAndroid OSが必要となる
です。
つまりWindowsやMac、LinuxといったPCでモバイルアプリ(某パズルゲーム)等が出来るようになるということですね。
あとはそれなりにPCのスペックを必要とする点も特徴ですね。
紹介するエミュレータ
私が今回紹介するエミュレータは3つです。
(私のPCに入っているAndroidエミュレータが3つなので)
Android Studio 標準搭載エミュレータ
まあ、アプリを制作していて、なおかつAndroidStudioで開発を行っている方なら必ず入っているIDEなので当然ですよね(笑)
必要最低スペック
あくまで必要最低スペックなので、以下のPCスペックでサクサク動くかと言われると…。
(特にメモリは8GBじゃないと動かないかも)
Microsoft® Windows® 7/8/10 (64-bit)
4 GB RAM minimum
4 GB of available disk space minimum
1280 x 800 minimum screen resolution
メリット
- 開発の時にこまめにデバッグしたい時に便利
- 無駄なセットアップはいらない(デフォルトでついてくるので)
- 様々なAndroidOSを入れられる
- 広告が無い
デメリット
- root権限を取得できない
- 種類の異なる多くのAndroidOSを入れていくと容量が増えていく
LDPlayer
あれ?AndroidStudioのデメリットってあまり私に関係ない?
と思った方もいるのではないでしょうか?
実はこのデメリットが結構、面倒です。
私はAndroidSecurityを勉強中ということもあり、モバイル端末内に様々なセキュリティチェックツールを入れています。
そうです。実はこのツールのほとんどは端末のroot権限を必要とします。
勿論、必要ないという方には関係ないかもしれませんが…。
そんな時に便利なのが「LDPlayer」です。
必要最低スペック
・x86/x86_64プロセッサー(IntelまたはAMD CPU)
・WinXP SP3 / Win7 / Win8 / Win8.1 / Win10
・OpenGL 2.0的Windows DirectX 11 / Graphic驅動程式
・最低4GBのシステムメモリ (RAM)
・最低36GBのハードディスク空き容量
・CPU仮想化機能(Intel VT-x / AMD-V)はBIOSで有効にする必要があります
お気づきの方もいるかと思いますが、CPU仮想化機能(Intel VT-x / AMD-V)
とある通り、WindowsのHyper-V
の機能を必要とします。
よってWSL
等をインストールしている方は併用できません。
メリット
- root権限の切り替えが可能(非root化も可能)
- 1つのOSバージョンのみなので容量は少ない
デメリット
- Android7.1 / 5.1 しか入れられない
- 広告がある
Genymotion
上記の「LDPlayer」のデメリットを解消するエミュレータがこちらの「Genymotion」です。
また、私の環境の場合ではfrida等のツールがAndroid7のバージョンだとクラッシュしたりしたので入れました。(後述のデメリットの点から必要な時以外はLDPlayerを使ってます)
必要最低スペック
Windows Vista以降のOS(32bit/64bitのどちらにも対応)
VT-x または AMD-V
OpenGL 2.0
ハードディスクに400MB以上の空き
RAM 2GB以上
勿論、このエミュレータもIntel VT-x / AMD-V
とある通り、WindowsのHyper-V
の機能を必要とします。
よってWSL
等をインストールしている方は併用できません。
メリット
- Android4.4 ~ 10.0までの幅広いバージョンを入れられる
- root化が可能
- 広告が無い
デメリット
- セットアップが大変
- VirtualBoxが必須
- rootと非rootが切り替えられない
- 起動直後は少し重い
- 種類の異なる多くのAndroidOSを入れていくと容量が増えていく
- 無料版だと機能が一部制限
エミュレータのroot化
ここからは紹介するエミュレータで紹介したエミュレータをroot化する方法についてです。
Android Studio 標準搭載エミュレータ
はい、こちらはroot化出来ないですね。
LDPlayer
まずエミュレータを起動しましょう。
起動したら上記の画像の赤く囲んでいる部分を選択してください。
LDPlayerの設定画面が開かれます。
次にその設定画面から「他の設定」をクリックします。
そうすると、赤く囲んである通り「ROOT権限」の設定の有効化、無効化の切り替えがあるので、無効になっていたら有効にしましょう。(いつでも切り替えられます)
ちなみに「ADBデバッグ」の設定も「ローカルデバッグ」にしておきましょう。
これでAndroidStudioを用いたログの取得やデバッグ、adbの使用が可能になります。
Genymotion
こちらのエミュレータ、昔は「Genymotionconfigration」というアプリがセットアップした端末内に入っていたのですが、最近では無くなったようです。
そのため、rootと非rootの切り替えは出来なくなりました。
(SuperSUを入れれば問題ないらしいが正直、面倒くさい)
しかし、切り替えが出来なくなっただけでroot化は出来ます。
その手法とは?
…はい、実はエミュレータを起動した時点でroot化されています。
なので特にすることは無いです。
まとめ
今回は私が持っているエミュレータの機能を整理するためにこの記事を書きました。
なので、詳しいセットアップの方法が知りたかったという方はごめんなさい…。
ちなみに私は3つのエミュレータを用途によって使い分けている感じですね。
また、今回記事の中で出てきた「frida」等のツールについてはまたの機会に!!
引用・参考記事
AndroidStudioでのGithub連携がUN/PWで出来なくなった?
この間、新しいPCに「AndroidStudio」を入れてGithubと連携しようとしたら正しいユーザ名とパスワードでログインしても「404エラー」が発生してしまいました。
ネットで調べた噂によると最近のGithubの仕様変更で出来なくなった可能性がある(2021/3月時点)、とのことでした。
解決法としてはGithubでアクセストークンを発行して連携する方法があって今回、初めてやってみたのでメモ書き程度に残しておこうと思いこの記事を書こうと思いました。
目次
- 目次
- 私の環境
- Githubでアクセストークンを発行する
- Gitの設定
- AndroidStudioでの設定とGithubへのPush
- おまけ(2回目以降のCommitとPush)
- まとめ
- 参考情報・引用
私の環境
OS:Windows10
AndroidStudio:4.13
Git:2.28.0
Githubでアクセストークンを発行する
まずは上記画像で赤く囲まれた部分をクリックしてその中の「Settings」を開いてください。
すると上記画像の様な画面が表示されると思うので、次に「Developer settings」を開いてください。
そして「Personal access tokens」を選択してください。
すると以下のような画面になると思います。
ここで自分が作成したアクセストークンの一覧を確認できます。
新しく作成する場合は赤く囲んだ「Generate new token」をクリックします。
この画面でアクセストークンに付与する権限の設定を行っていきます。
注意!:ここで設定するアクセストークンの権限は間違えないようにしてください
設定項目を以下に示します。
Note...アクセストークンを識別するための任意の名前(今回私はhogehogeで作成) repo...プライベートリポジトリに関する操作に必要 workflow...Githubアクションワークフローを更新するのに必要 admin:org...組織やチームのプロジェクトの読み書きに必要 admin:repo_hook...この項目にチェックを入れないと追加のリポジトリを後で作成出来なくなる gist...要旨の作成に必要
以上の通りに設定できていると下記画像のようにチェックボックスにチェックが入っていると思います。
その後「Generate token」をクリックして以下の画像のようにアクセストークンが表示されればOKです!
実際には黒く塗りつぶしているところに「アクセストークン」が表示されています。
注意!:表示されるアクセストークンはこの時点でメモ帳等に保存しておいてください。一度しか表示されません!(忘れた場合は再発行することになります)
Gitの設定
既に設定されている方は問題ないのですが、この先のセクションでGithubと連携した場合でもこの設定が上手くいっていないと、ちょっと面倒くさいことになるのでやっておきましょう!
設定する項目は「ユーザ名」と「メールアドレス」です。
この項目がGithubにある「ユーザ名」と「メールアドレス」と異なると、コミットしたユーザ名等が第3者になってしまいます
(私もここで躓いて、コミットしたユーザ名が本名になっていて大変でした(笑))
どちらも簡単に変更することが出来てそれぞれ以下のコマンドを実行するだけです!
git config --global user.name ユーザー名 git config --global user.email メールアドレス
無事、変更できたかどうかを以下のコマンドを実行して確認します。
git config --list
「user.name」と「user.email」が先ほど設定した値に変更されていたらOKです!
AndroidStudioでの設定とGithubへのPush
AndroidStudioでGithubと連携させたいプロジェクトを開きます。
その後「VCS」を選択して「Enable Version Control Integration...」をクリック。
次に表示される画面にはデフォルトの「Git」を選択
上手くいけば、上の画像のようにファイル名が赤色になるはずです。
その後、もう一度「VCS」をクリックして次は「Import into Version Contorol」から「Share Project on Github」をクリックするとGithubのログイン情報を求められます。
この時「Token」が表示されておらず「UserName/Password」の入力を求められている場合はウィンドウ右上に「Use Token」という青色の文字の部分をクリックすると画面が切り替わります!
そして「Token」の部分にGithubでアクセストークンを発行するで作成した「Token」をコピー&ペーストして「Log In」ボタンをクリックすると、下記の画像の様なウィンドウが表示されると思います。
今回私は下記のような設定にしていますが、ここは皆さんの自由でOKです!
Repository name:Test(Private) Remote:origin Description:none
後は「Share」ボタンをクリック!!
GithubにPushするファイルを選択、コミットメッセージを入力して「Add」ボタンをクリックするとGithubにPushされます。
注意!:AndroidStudioではプロジェクトごとにGithub連携を行う必要があります。よって1つのプロジェクトで連携をしたからといって他のプロジェクトでも連携がされていると言う訳ではありません!!
おまけ(2回目以降のCommitとPush)
AndroidStudioでの設定とGithubへのPushでは最初の1回目のコミットとPushの説明をしました。
では2回目以降は…?
これも簡単です。
「VCS」から「Commit」をクリックしてコミットするファイルとコミットメッセージを入力(この時に注意メッセージが表示された場合は気にせず「Commit」を選択)、その後もう一度「VCS」から「Git」そして「Push」をクリックすれば、Pushウィンドウが表示されるので「Push」ボタンをクリック!(ここでも注意メッセージが表示された場合は「Marge」をクリック)
すると、Githubへの「Push」行われます。
まとめ
以上が「AndroidStudio」で「Token」を使ってGithub連携を行う手法になります。
結構、最近の話題ぽかったので調べても情報が少なくて大変だったので大変でした!(…どちらのバグかわかりませんが、早く修正させるといいですね(笑))