- 投稿日:2019-11-28T23:31:33+09:00
ARCoreを使ったARアプリをエミュレータで動かす
ARKitを使ったARアプリはエミュレータで動かせませんが、ARCoreを使ったARアプリはエミュレータで動かすことができます。
エミュレータで動かす際に少しハマったことがあるので共有します。
(Unityで作成したARアプリは、この記事とは違うやり方なので注意してください。)必要な環境
- Android Studio 3.1以降
- Android Emulator 27.2.9以降
サンプルプロジェクトのダウンロード
この記事ではARCore SDKにあるサンプルプロジェクトを使用します。
ARCore SDKをDLします。
そして、samples/hello_ar_java
をAndroid Studioで開きます。エミュレータ
エミュレータを起動します。
端末によってはARCoreをサポートしてないため、エミュレータはなるべく新しめの端末の方がいいです。
API Levelは27(Oreo)以降が必要です。(API Levelも最新の29が良さそう)サンプルアプリの起動
ここからが少しハマったところです。
サンプルアプリを起動します。
すると...
This application requires the atest version of Google Play Services for AR.
(このアプリを使用するには、最新バージョンのGooglePlay開発者サービス(AR)が必要です。)
と、出てきます。
どうやら、エミュレータでARアプリを使うにはGoogle Play Services for ARが必要らしいです。
CONTINUE
を押すとGoogle Play Storeに飛ばされます。これをインストールしなきゃいけないのに
Your device isn't compatible with this version.
(お使いのデバイスはこのバージョンに対応していません。)
って表示されて、インストールできなく、ARのサンプルアプリが無限にエミュレータで起動できない状態になってしまいます。こんな時は、大人しくARCoreのドキュメントをみましょう!
どうやらGitHubのリリースページから最新の
Google_Play_Services_for_AR.apk
をDLする必要があるっぽいです。
*現在(2019年11月時点)では、v1.13.0が最新
DLしたapkファイルをエミュレータ上にドラッグするか、エミュレータ起動中に、以下のadbコマンドを叩きます。adb install -r Google_Play_Services_for_AR_1.13.0_x86_for_emulator.apk無事、エミュレータでARアプリを動かせました!!!!
なぜか平面認識すると白くなるようです。他のアプリだとそうならないので、おそらくサンプルアプリの影響です。エミュレータ内で動くためには以下のKeyを使います
platform key mac option + WASD Linux, Windows Alt + WASD ちなみに、エミュレータの右にある「・・・」の中にあるVirtual sensorsで、センサの値を直接いじっても動きます。
API Levelを28以下にすると...
API Levelを29じゃなく、28や27でやった場合、
this device does not support AR
と表示されてアプリが使えないです。apkファイルのバージョンを下げたらいけそう...って思ったのですが、そもそもGoogle_Play_Services_for_AR.apk
を認識してくれませんでした。
知見ある方、コメント待ってます。
- 投稿日:2019-11-28T22:46:52+09:00
【Androidアプリ入門】内部ストレージに保存・読込する♪
今回は、前回のAndroidアプリで内部ストレージに保存・読込を追加した。
情報は、ググるとそれなりに出てくるが結構ハマって、しかも日本語の読込は失敗しているが、現状進捗がなさそうなので、ここでまとめておこうと思う。
英語保存 英語読込 日本語保存 日本語読込 内部ストレージへの保存
これは、以下の参考のまま動きました。
ここでは、入力された文字列をinputStrに取得しているので、それを書き込むこととした。
また、File名は"testfile.txt"とした。
【参考】
Android Internal Storage Example TutorialMainActivity.ktwhen(view.id) { //表示ボタンの場合… R.id.btClick -> { //入力された名前文字列を取得。 val inputStr = input.text.toString() //メッセージを表示。 output.text = df.format(date) + "\n"+inputStr + "さん、こんにちは!" //inputStr + "さん、こんにちは!" val fOut = openFileOutput("testfile.txt", Context.MODE_PRIVATE) fOut.write(inputStr.toByteArray()) fOut.close() } //クリアボタンの場合… R.id.btClear -> {...実際に書き込まれているかどうかを確認するために参考のような手順で確認してみた。
【参考】
[Android] アプリの内部メモリを覗くとパーミッションでブロックされるC:\Users\MuAuan\AndroidStudioProjects\HelloSample_qiita>bash ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb run-as プログラム 'adb' は以下のパッケージで見つかりました: * adb * android-tools-adb 次の操作を試してください: sudo apt install <選択したパッケージ> ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ sudo apt install adb ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb shell generic_x86:/ $ 1|generic_x86:/ $ run-as com.example.hellosample generic_x86:/data/data/com.example.hellosample $ ls cache code_cache files generic_x86:/data/data/com.example.hellosample $ cd files generic_x86:/data/data/com.example.hellosample/files $ ls file\ name testfile.txt generic_x86:/data/data/com.example.hellosample/files $ ls -l total 12 -rw-rw---- 1 u0_a137 u0_a137 0 2019-11-26 12:11 file\ name -rw-rw---- 1 u0_a137 u0_a137 4 2019-11-26 14:17 testfile.txtということで、以下のようにtestfile.txtにfshkが書き込まれていることが分かります。
generic_x86:/data/data/com.example.hellosample/files $ cat testfile.txt fshkgeneric_x86:/data/data/com.example.hellosample/files $読み込み
書き込んだファイルから、以下のコードで読み込みも出来ました。
ただし、英語のみ正しく読み込めました。
つまり、上記のように日本語もタイ語も文字化けしてしまいました。MainActivity.ktR.id.btClick1 -> { val fin: FileInputStream = openFileInput("testfile.txt") var c=0 var temp="" while (fin.read().also({ c = it }) != -1) { temp = temp + Character.toString(c.toChar()) } fin.close() output.text = df.format(date) + "\n" + temp input.setText("") }そして、日本語やタイ語がファイルにどう書き込まれているかを上記の方法でも可能ですが、今回は以下の方法で調べました。
【参考】
Android Studioでファイル一覧を確認したい!
この方法で、スマホ実機の内部ストレージが覗けます。
さらに、そこにあるファイルをコピペすると見事に安全な?Dirにダウンロードして以下の図のようにtestfile.txtに書き込まれている内容が見えました。
まとめ
・前回のAndroidアプリで入力データを内部ストレージに保存・読込してみた
・スマホ実機の内部ストレージを覗けるようになった・日本語は文字化けしてうまく出来ていない
おまけ
日本語文字化け解消のために以下を実施したがうまくいっていない
【参考】
Kotlin で手軽にテキストファイルを読み込む
試したコードは以下のとおり
このコードで一応実行は出来るが、読み込みを実施すると、アプリが中断してしまう。
これは、上記のコードでもファイル名が異なったりすると出る事象なので、やはりtemp = File(pathUtf8).readText(Charsets.UTF_8)で起きているようだ。
この部分をコメントアウトすると、読み込みはできないが、中断は起こさない。MainActivity.ktR.id.btClick1 -> { var temp="" var pathUtf8="testfile.txt" temp = File(pathUtf8).readText(Charsets.UTF_8) output.text = df.format(date) + "\n" + temp input.setText("") }
- 投稿日:2019-11-28T22:46:52+09:00
【Androidアプリ入門】内部ストレージに保存・読込する♪ハマった編
今回は、前回のAndroidアプリで内部ストレージに保存・読込を追加した。
情報は、ググるとそれなりに出てくるが結構ハマって、しかも日本語の読込は失敗しているが、現状進捗がなさそうなので、ここでまとめておこうと思う。
英語保存 英語読込 日本語保存 日本語読込 内部ストレージへの保存
これは、以下の参考のまま動きました。
ここでは、入力された文字列をinputStrに取得しているので、それを書き込むこととした。
また、File名は"testfile.txt"とした。
【参考】
Android Internal Storage Example TutorialMainActivity.ktwhen(view.id) { //表示ボタンの場合… R.id.btClick -> { //入力された名前文字列を取得。 val inputStr = input.text.toString() //メッセージを表示。 output.text = df.format(date) + "\n"+inputStr + "さん、こんにちは!" //inputStr + "さん、こんにちは!" val fOut = openFileOutput("testfile.txt", Context.MODE_PRIVATE) fOut.write(inputStr.toByteArray()) fOut.close() } //クリアボタンの場合… R.id.btClear -> {...実際に書き込まれているかどうかを確認するために参考のような手順で確認してみた。
【参考】
[Android] アプリの内部メモリを覗くとパーミッションでブロックされるC:\Users\MuAuan\AndroidStudioProjects\HelloSample_qiita>bash ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb run-as プログラム 'adb' は以下のパッケージで見つかりました: * adb * android-tools-adb 次の操作を試してください: sudo apt install <選択したパッケージ> ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ sudo apt install adb ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb shell generic_x86:/ $ 1|generic_x86:/ $ run-as com.example.hellosample generic_x86:/data/data/com.example.hellosample $ ls cache code_cache files generic_x86:/data/data/com.example.hellosample $ cd files generic_x86:/data/data/com.example.hellosample/files $ ls file\ name testfile.txt generic_x86:/data/data/com.example.hellosample/files $ ls -l total 12 -rw-rw---- 1 u0_a137 u0_a137 0 2019-11-26 12:11 file\ name -rw-rw---- 1 u0_a137 u0_a137 4 2019-11-26 14:17 testfile.txtということで、以下のようにtestfile.txtにfshkが書き込まれていることが分かります。
generic_x86:/data/data/com.example.hellosample/files $ cat testfile.txt fshkgeneric_x86:/data/data/com.example.hellosample/files $読み込み
書き込んだファイルから、以下のコードで読み込みも出来ました。
ただし、英語のみ正しく読み込めました。
つまり、上記のように日本語もタイ語も文字化けしてしまいました。MainActivity.ktR.id.btClick1 -> { val fin: FileInputStream = openFileInput("testfile.txt") var c=0 var temp="" while (fin.read().also({ c = it }) != -1) { temp = temp + Character.toString(c.toChar()) } fin.close() output.text = df.format(date) + "\n" + temp input.setText("") }そして、日本語やタイ語がファイルにどう書き込まれているかを上記の方法でも可能ですが、今回は以下の方法で調べました。
【参考】
Android Studioでファイル一覧を確認したい!
この方法で、スマホ実機の内部ストレージが覗けます。
さらに、そこにあるファイルをコピペすると見事に安全な?Dirにダウンロードして以下の図のようにtestfile.txtに書き込まれている内容が見えました。
まとめ
・前回のAndroidアプリで入力データを内部ストレージに保存・読込してみた
・スマホ実機の内部ストレージを覗けるようになった・日本語は文字化けしてうまく出来ていない
おまけ
日本語文字化け解消のために以下を実施したがうまくいっていない
【参考】
Kotlin で手軽にテキストファイルを読み込む
試したコードは以下のとおり
このコードで一応実行は出来るが、読み込みを実施すると、アプリが中断してしまう。
これは、上記のコードでもファイル名が異なったりすると出る事象なので、やはりtemp = File(pathUtf8).readText(Charsets.UTF_8)で起きているようだ。
この部分をコメントアウトすると、読み込みはできないが、中断は起こさない。MainActivity.ktR.id.btClick1 -> { var temp="" var pathUtf8="testfile.txt" temp = File(pathUtf8).readText(Charsets.UTF_8) output.text = df.format(date) + "\n" + temp input.setText("") }
- 投稿日:2019-11-28T22:02:34+09:00
Android Navigation Componentによる画面遷移時のToolbar
はじめに
この記事ではNavigation Componentの基本的な使い方は記載していません。
Navigation Componentを使った画面遷移時のToolbarの処理に着目しています。Activityが持つViewの処理をActivityに書く
Toolbarを表示する際、ほとんどの場合ホストとなるActivityがToolbarを持ちます。
その際、ゲストのFragmentでToolbarに関する処理を書くとval toolbar = view.findViewById<Toolbar>(R.id.toolbar) requireActivity().setSupportActionBar(toolbar)となりますが、あまり書きたくないです。
Toolbarに限らずFloatingActionButtonなど、Activityが持つViewに関する処理の変更はFragmentに書かずに極力Activityに書き、正しく役割分担することにより保守性を高めたいです。Navigation Componentによる画面遷移時も同様
Navigation Componentを使う場合に作成するActivityとFragmentも同様に正しく役割分担させたいです。
この記事ではNavigation Componentによる画面遷移時の、Activityが持つToolbarの処理に着目し、Activityに処理を書いています。
以下の項目について記載しています。
- FragmentによってToolbarの処理を分岐
- Fragment間の画面遷移時にToolbarに関するViewを変更
各種バージョン
コードを読みやすくするためにRxBindingを使っています。
- Kotlin 1.3.60
- Navigation Component 2.2.0-rc02
- RxBinding 3.1.0
FragmentによってToolbarの処理を分岐
Navigation Componentを使っているActivityに、表示しているFragmentによってToolbarの戻るボタン押下時の処理を分岐させた例です。
現在のDestinationのID(navigationレイアウトのfragmentのID)に応じて処理を分岐させています。MainActivity.ktbinding.toolbar .navigationClicks() .filter { navController.currentDestination != null } .subscribe { when (navController.currentDestination!!.id) { R.id.firstFragment -> finish() R.id.secondFragment -> navController.navigate( SecondFragmentDirections.secondToFirst() ) } }現在のDestinationがFirstFragmentの場合、Activityを終了(finish())します。
現在のDestinationがSecondFragmentの場合、FirstFragmentに遷移します。Fragment間の画面遷移時にToolbarに関するViewを変更
NavControllerにはaddOnDestinationChangedListenerというDestinationの変更に関するListenerがあります。
これを利用し画面遷移時にToolbarにあるButtonのvisibilityを変更しています。MainActivity.ktnavController.addOnDestinationChangedListener { _, destination, _ -> when (destination.id) { R.id.firstFragment -> binding.toolbarButton.visibility = View.GONE R.id.secondFragment -> binding.toolbarButton.visibility = View.VISIBLE } }
- 投稿日:2019-11-28T17:32:26+09:00
AndroidでToggleButtonを用いてServiceを起動・終了させてみる(起動時に通知バーに表示させる)
はじめに
Androidアプリ開発(javaも)を触るのはかなり久々で何も覚えていなかったのですが、ちょっと思い立ってアプリを作ってみようかと思って奮闘中です。あまり詳しくないので細かい解説は出来ませんしn番煎じ感が否めませんが、ログとして残す意味でも書いておきます。
昔は結構重かったEclipseを使ってAndroid用のプラグインやらAVD入れてゴチャゴチャやって・・・って感じでしたが、今ではAndroid studioでレイアウトもGUIで直感的に作れるようになって、エミュレータやら外部端末の設定も楽々で、サクサクになっていて便利になりましたね。やりたいこと
ある一定間隔で何らかの処理をさせるAndroidアプリを開発したいと思っています。勝手に死んで欲しくはないので、ここでは
IntentService
ではなく、Service
を使おうと思いました。
サービスの開始・停止にはToggleButton
を使用します。環境について
- 開発環境はAndroid studio 3.5.2
- テストを走らせる端末はAndroid 8.0.0 & 9
- 言語はJava
実装
レイアウトについて
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="wrap_content" tools:context=".MainActivity"> <ToggleButton android:id="@+id/service_toggle" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="ToggleButton" android:textOff="サービス開始" android:textOn="サービス終了" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>作り方は簡単です。
activity_main.xml
を開くとレイアウトをGUIで作れるDesignモードで開けるので、Palletteの中からButtons->ToggleButtonを選択します。On / Off時の表示させるテキストは右側Attributesから編集出来ます。MainActivityについて
ここでは、主にToggleButtonの処理を行います。
MainActivity.javapackage com.example.testproject; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; import android.widget.CompoundButton; import android.widget.ToggleButton; public class MainActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener { ToggleButton service_toggle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); service_toggle = (ToggleButton)findViewById(R.id.service_toggle); service_toggle.setChecked(TestService.isActive); service_toggle.setOnCheckedChangeListener(this); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { startService(new Intent(MainActivity.this, TestService.class)); } else { stopService(new Intent(MainActivity.this, TestService.class)); } } }serviceを起動するToggleButton
service_toggle
をレイアウトと紐付けます。後に出てくるTestServiceが起動中かを取得して、その結果をToggleに反映させます(service_toggle.setChecked(TestService.isActive);
)。
こうすることによって、例えばActivityが破棄された状態でもバックグラウンドでServiceが起動していればToggleがOnになるように出来ます。implementしている
CompoundButton.OnCheckedChangeListener
のonCheckedChanged
をOverrideして、Toggleを操作した時にServiceへIntentを送ります。Serviceについて
ここでは具体的にバックグラウンドで処理させたいものを書きます。
今回はServiceのStart / Stop時にToastを出力させ、Serviceの実行中は1秒毎にLogを出力させるようにします。TestService.javapackage com.example.roadmemo; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.util.Log; import android.widget.Toast; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import static android.widget.Toast.*; public class TestService extends Service { static boolean isActive = false; final Handler handler = new Handler(); final Runnable runnable = new Runnable() { Integer count = 0; @Override public void run() { ++count; Log.d("LocalService", "runnable" + count.toString()); handler.postDelayed(this, 1000); } }; @Override public void onCreate() { super.onCreate();; Log.d("LocalService", "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Context context = getApplicationContext(); int notificationId = 1; //------------ for test ------------ Log.d("LocalService", "onStartCommand Received start id" + startId + ": " + intent); Toast toast = makeText(this, "Start service", LENGTH_LONG); toast.show(); // -------------- begin notification --------------- Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); String channelId = "default"; NotificationChannel channel = new NotificationChannel( channelId, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW); if (notificationManager != null) { notificationManager.createNotificationChannel(channel); } NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("サービス") .setContentText("サービスが起動中です。終了するには通知をタップしてアプリから終了してください。") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentIntent(pendingIntent) .setAutoCancel(true); Notification notification = builder.build(); notification.flags |= Notification.FLAG_ONGOING_EVENT; startForeground(notificationId, notification); // -------------- end notification --------------- handler.post(runnable); return START_STICKY; } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { Log.d("LocalService", "onDestroy"); Toast toast = makeText(this, "Stop service", LENGTH_LONG); toast.show(); handler.removeCallbacks(runnable); } }作ったServiceが動かせるように、AndroidManifest.xmlに以下を追記します。
AndroidManifest.xml<service android:name=".CachePositionService" />Android 9以降に対応するのには、こちらにあるように
AndroidManifest.xml
に、以下の権限AndroidManifest.xml<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />を追加しなければなりません。
一定時間毎に行う処理
まず、一定時間(ここでは1秒)ごとの処理を記述するのには
Handler.postDelayed()
メソッドを使います。このHandlerは他のThreadとの通信をするもの。Runnableクラスのインスタンスを投げることが出来ます。Runnableクラスのインスタンスはお決まりがあるようです(Android studioでは、Runnableのインスタンスを書こうとすると自動的にRunnable.run()
をOverrideする形で記述するように出てきた)。
Serviceが実行中に行われるonStartCommand
内でhandler.post(runnable)とし、Serviceが終了される時に行われる
onDestroy
内でhandler.removeCallbacks(runnable)とすることで、handlerの処理を制御出来ます。
通知に関して
通知バーに出てくる通知をタップすると、MainActivityが呼ばれるようにしたいです。この際にもIntentを渡します。が、この時は通知を押されるという処理が行われるまでIntentを発行したくないので、そんな時には
PendingIntent
というものを使います。
そして、NotificationManagerCompat
1にてこのServiceにおけるContextについて通知を出すよと書いて、Builderで具体的な通知内容の雛形を書き、その雛形を元にBuildします。
ここでのポイントですが、BuildしたNotificationのインスタンスに、バックグラウンドで動いているときには通知を消せないようにnotification.flags |= Notification.FLAG_ONGOING_EVENT;を追加します。
また、notificationId
に0を入れると通知が表示されないので、別の数字にします。作ったServiceが動いているかをActivityで確認する
今、自分が作ったServiceが動いているのかをActivityで確認するのに最初は,
MainActivity.java
にてboolean isActive() { boolean active = false; ActivityManager activityManager = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo serviceInfo : activityManager.getRunningServices(Integer.MAX_VALUE)) { if (TestService.class.getName().equals(serviceInfo.service.getClassName())) active = true; } return active;のように全ての起動中のServiceを舐め回して、Stringで一致する時に
true
を返すようにしていましたが、これはAndroid 8 (API > 26)からはgetRunningServices
メソッドが非推奨となったようなので、代わりにServiceの方でstaticなbool型変数を用意しておいて、onDestroy()
が呼ばれるまでtrue
にしておくという形にしました。結果
まず、出来た画面がこちら
ToggleButtonをタップすると"Service start"というToast表示が出力されると共に通知バーに通知が現れます。Logcatを確認するとTestService.onStartCommand
が呼ばれているのが分かります。
通知もこのように出てきます(目隠ししているのはアプリの名前です)
Serviceが生きている時に、Activityを破棄(アプリを終了)されても、Serviceは生きていることが確認出来ます。通知バーの通知は消えませんし、Logcatの方ではServiceが生き続けていて1秒毎にログが出力されている筈です。
最後に、通知をタップしてActivityを再び起動させ、ToggleButtonを見ると、On状態のままになっています。このToggleを押すと"stop service"というToastと共に通知が消えます。TestService.onDestroy
も無事にCallされていることがLogを見ても分かります。
これで狙った挙動になりました!
参考
一通りのチュートリアルは公式のこちら
https://developer.android.com/training/basics/firstapp?hl=ja
レイアウトの作り方(公式)
https://developer.android.com/training/basics/firstapp/building-ui?hl=ja
トグルボタンについて
https://developer.android.com/guide/topics/ui/controls/togglebutton?hl=ja
Serviceの解説(公式)
https://developer.android.com/guide/components/services
サービス中の通知について(公式)
https://developer.android.com/guide/components/services#Foreground
サービスの使い方
https://akira-watson.com/android/service.html
定期的に処理を行う方法について
https://qiita.com/aftercider/items/81edf35993c2df3de353
公式のReferenceを見ると、
NotificationManager
,Notification
を用いるよりNotificationManagerCompat
,NotificationCompat
を使用した方が複数バージョンの互換性があるらしいです。 ↩
- 投稿日:2019-11-28T17:02:50+09:00
UXデザイナーと連携する際に考慮したい事項
1ピクセルのデザインにこだわるUXデザイナーとの連携で、いろいろ苦労した経験からの知見を簡易的にまとめます
ピクセル単位での調整を可能にする
エンジニアが、都度、確認できるデザインスペックが必要
XD、Sketch、Prott、Abstractなど、ピクセル単位で確認でき、素材を書き出せるツール・サービスを使う要件定義書
最初、エクセルやスプレッドシートを使っていたが、エンジニアは更新しやすいものの、デザイナーや企画者は使いづらい
さらに、デザインスペックを別で用意する必要がある
→ XDで要件定義を作成してもらい、デザインスペックも同時に作成&確認できるようにしたワイヤーフレーム
要件定義からデザインを起こすまでのステップで、デザイナーがどのレベルまでデザインに落とし込めばいいか分からない
→ アプリエンジニアは、要件とデザインの両方がないと、実現可能かを判断できないケースが多いので、ある程度のデザインは必要だが、完成版を見せられると、その時点で判断不能に陥ることが多いので、ワイヤーフレームレベルのデザインで十分。
デザイナーとエンジニアで話し合うステップが必要なので、ワイヤーフレームレベルのデザインで一旦、エンジニアに共有するデザインスペックの指示通りに、修正は結構な手間がかかる
デザイナーがXcodeを使えるようにして、文字サイズやフォント、ピクセル単位での修正は、デザイナーにやってもらう
デザイナーから受け取った素材を、リネームする手間がかかる
スプレッドシートで、素材依頼シートを作って、名前を指定して書き出してもらう
※あまりいい運用じゃなかったので、もっといい方法を考えた方がいいピクセル単位で調整していると同じような素材が増え、アプリサイズが肥大化する
共通で使う素材は、命名規則で判別しやすいようにする。common_XXXXX など
デザイナーがエンジニアに渡す際に、共通で使う素材かどうかを伝えてもらえると助かります。カラーが細かくなり、どのカラーを使えばいいか、迷ってしまう
基本は、デザインスペック通りだが、間違えてるのではと思う箇所が出がちなので、大まかに、テキスト用のカラーとか、テーマカラーとかを決めて、間違いに気づけるように
iOSのTips
- 行間は、TextFieldだと行間指定できないので、UILabelかTextViewに変更する必要がある
- フォントを、システムフォントじゃなくて、ヒラギノに明示的に設定する
AndroidのTips
- Android は、dpで指定 (端末のフォントサイズ設定に対応する場合は、spがいいが、フォントサイズ対応すると、レイアウト崩れを考慮しないといけないので、逆にdp指定で対応させない)デザインのベースにする解像度の決め方
各プラットフォームでの端末シェアを考慮
【2019最新】スマホ・タブレットの解像度一覧表(画面サイズの割合)
https://webdesign-abc.com/tech/resolution-list/【2019年】最新のディスプレイ・モニター解像度シェアが知りたい!
https://web-knight.net/display-resolution/「iPhone6/7/8」で解像度は「375 * 667」
「Android XPERIA」。解像度は「360 x 640」
が無難デザイナーにお願いしたいこと
Atomic Design を考慮して、パーツ・コンポーネント単位で定義してほしい→
これが出来ると、開発でもコンポーネント化しやすい
[参考]
Atomic Design を分かったつもりになる
https://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B/AppleのHIGを隅々まで熟読してほしい
Human Interface Guidelines
https://developer.apple.com/design/human-interface-guidelines/
HIGから学ぶ、3つのナビゲーションとそのあるべき姿とは
https://note.com/ta0o_o0821/n/n140cd75cc023HIGを理解したあと、iOSとAndroidアプリの違いや、マテリアルデザインを熟読
マテリアル デザインでアプリの魅力を高める
https://developer.android.com/distribute/best-practices/develop/use-material-design?hl=JA
ただ、個人的には、マテリアルデザインのリファレンスは、より具体的な実装方法のマニュアル的な部分が多く記事も多いので、熟読するというより、マテリアルデザインがどういうもので、どんな事ができるかを把握してほしいという感じ
Human Interface Guidelines は概念的な内容で、マテリアルデザインはデザイン手法のマニュアルといった感じその他
iOSとAndroidのUIの違い
https://nogson2.hatenablog.com/entry/2018/11/16/123256iOS、Androidの解像度について(iOSはpt / Androidはdp)
https://qiita.com/eKushida/items/ff1ecaacdb54ce7f9f5cエンジニアもデザインのことを知る必要がある
【これからのスキル】デザイナーとエンジニアの境界線がどんどん無くなる
https://blog.btrax.com/jp/designer-engineer/
優れたプロダクト開発のために。エンジニアがデザインを学ぶ時のおすすめ本
https://goodpatch.com/blog/designbooks-for-developer/
- 投稿日:2019-11-28T16:56:53+09:00
Android(Pixel 3) OSダウングレード手順メモ
はじめに
Androidアプリ開発で、OS依存の問題かを確認するためにAndroidOSをダウングレードする必要がありました。
少しハマったので手順をメモします。Android Studioは使わずに実施しました。
バックアップしてから実施してください。環境
PC:MacBook Pro
Android端末:Pixel3 AndroidOS10やりたいこと
Pixcel3のAndroidOSのバージョンを10から9にダウングレードしたい。
手順
1.ファクトリーイメージのダウンロード(PC)
pixcel3のイメージは以下にありました。
https://developers.google.com/android/images#blueline2.ダウンロードしたファクトリーイメージのフォルダを展開する(PC)
3.カレントディレクトリを2で展開したフォルダに変更する(PC)
4.開発者向けオプションの『OEMロック解除』と『USBデバッグ』を有効にする(Android)
開発者オプションの表示の仕方は以下。
https://enjoypclife.net/2018/12/23/android-9-developer-option-pixel-3/5.Android端末とPCをUSB接続する
6.電源ボタンと音量(下)ボタンを長押しでBootloaderを起動する(Android)
PC側では
adb reboot bootloader
を実行すれば起動できます。7.
fastboot flashing unlock
を実行する(PC)ブートローダーがアンロックされます。
8.「Unlock fastboot」を選択する(Android)
これでDevice statusがunlockになります。
9.
sh flash-all.sh
を実行するしばらく待つと、Pixcel3が再起動して無事にダウングレードすることができました。
おわりに
手順4の「OEMロック解除」をせずに実施していたこと、手順8が抜けていたことが原因で少しハマりました。
普段Androidを使っていないこともあって、Androidの勝手が分からず簡単なところで躓いてしまいます。。
- 投稿日:2019-11-28T16:00:32+09:00
[ Android ] HTTP通信時に発生するタイムアウトエラー処理
AndroidでHttp通信を行う際、起きるタイムアウトの処理について述べる。
サンプル
まずHttp通信は以下のような処理を行う。
HttpURLConnection connection = null; try { URL url = new URL(params[0]); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(20000); // 接続タイムアウト InputStream is = connection.getInputStream(); }catch・・・接続タイムアウト
サンプルではタイムアウトを20secに設定してある。サーバからのレスポンスが20secで返ってこなかった場合、SocketTimeoutExceptionが発生する。この例外のキャッチブロック中にタイムアウトが発生した場合の処理を書けば、例外処理できる。
}catch(SocketTimeoutException e){ //タイムアウトが発生したときの処理 }cacth(IOException e){ //・・・ }IOExceptionはHttpURLConnectionのキャッチブロックによく利用されるので書いている。
これらの順序が逆になるとコンパイルエラーになるので、気を付けて頂きたい。参考URL
- 投稿日:2019-11-28T14:57:55+09:00
MediaPlayer Classを使う
簡単なI/Fを作ります。
activity_main<?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" tools:context=".MainActivity" android:orientation="vertical" android:gravity="center"> <Button android:id="@+id/btnPlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Play" android:onClick="onPlay" /> <Button android:id="@+id/btnPause" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Pause" android:onClick="onPausePlayer"/> <Button android:id="@+id/btnStop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Stop" android:onClick="onStopPlayer" />res->new resource directory->resource type(raw)->resource name(好きなように)、を作ります。そして、mp3かwavなどの音声ファイルをこのdirectoryにコピペします。
(注意:ファイル名は小文字オンリー)MainActivitypublic class MainActivity extends AppCompatActivity { MediaPlayer mp; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onPlay(View v){ /*MediaPlayer consumes too much memory resource therefore we only create it when we want to play something */ if (mp==null){ /*makesure there's no old one before create new one we don't want to create too much and use too many resource */ mp= MediaPlayer.create(this,R.raw.globe); mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { DestroyPlayer(); } }); } mp.start(); } public void onPausePlayer(View v){ if (mp!=null) mp.pause(); } public void onStopPlayer(View v){ DestroyPlayer(); } private void DestroyPlayer(){ if (mp!=null) mp.release(); mp=null; Toast.makeText(this, "Media Player destroyed", Toast.LENGTH_SHORT).show(); } @Override protected void onStop() { super.onStop(); DestroyPlayer(); } }
- 投稿日:2019-11-28T01:14:47+09:00
AdaptiveIconに対応する
環境
Android Studio 3.5
概要
AdaptiveIconについては下記URLを参照。
https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive?hl=jaアイコンの表示の違い
未対応の場合はアイコンの中央に配置された状態でアプリアイコンが表示されます。様々な形状に対応
画像の用意
使用する画像はベクタ画像がおすすめです。
用意する画像は単色の背景の場合はColorResourceを使うのでアイコンのイラスト部分にあたる画像のみでOKです。
背景に画像を適用したい場合は別途画像が必要です。
ベクター画像の場合のサイズは108x108dpで作成します。
png等を利用する場合はサイズ次第で荒くなるので注意が必要です。
pngで画像を作る場合は背景透過で全体のサイズを432x432px、イラスト部分を288x288pxで作成すると粗さもなくいい感じになります。アイコンの作成
アイコンの作成はAndroidStudioのImageAssetを利用して作成するのが便利です。
New→ImageAssetで作成ツールが起動します。
NameとLayerNameはデフォルトのままにしておくと既存のアイコンに上書きされるので設定の手間が少なくなります。アイコン作成手順
1. ForegroundLayerタブを開き、前景に配置する画像を設定します。
Resizeのスライダーで画像のサイズ調整ができるのでガイドからはみ出ていたりする場合はこれで調整しましょう。
2. 次にBackgroundLayerタブを開き、前景に配置する画像を設定します。
単色背景の場合はAssetTypeをカラーに、画像を使用する場合はImageを選択します。
ImageはForegroundLayerと同様にResizeのスライダーで調整可能です。
調整が終わったらNextをクリックします。
3. 生成される画像が一覧で表示されるので表示に問題ないかチェックしましょう。
mipmap-anydpi-v26がAndroid8以降のAdaptiveアイコン、それ以外はAndroid8以前のアイコンとして使用されます。
4. 問題なければFinishをクリックします。アイコンの設定
AndroidManifestのapplicationタグのiconに作成したアイコンのDrawableを入力します。
作成時にデフォルトのままにしていた場合は上書きされるので上記の作業は不要です。
あとはアプリをインストールしてアイコンが設定されているかを確認しましょう。端末のアイコンの形状の設定変更方法
前提として開発者モードを有効にしておきます。
1. 設定アプリを起動
2. システム→詳細設定→開発者向けオプション→テーマ設定セクションのアイコンの形
3. 任意の形状を選択する
※端末によっては設定方法が異なるので注意