====== androidstudio2で始めるアプリ開発入門 ====== [[https://codezine.jp/article/corner/627|androidstudio2で始めるアプリ開発入門]] ===== 第7回.Androidアプリの画面遷移 ~ アクティビティの起動をつかさどるIntentクラス ===== ==== SimpleAdapter ==== var list:MutableList> = mutableListOf() var map:Map = mapOf("name" to "お味噌汁" ,"price" to "100") list.add(map) map= mapOf("name" to "お寿司","price" to "2000") list.add(map) map= mapOf("name" to "カレー","price" to "600") list.add(map) val from:Array = arrayOf("name","price") val to:IntArray = intArrayOf(android.R.id.text1,android.R.id.text2 ) val adpter:SimpleAdapter= SimpleAdapter(this ,list,android.R.layout.simple_expandable_list_item_2,from,to) list1.adapter=adpter ==== アクティビティの起動とIntent ==== list1.setOnItemClickListener { adapterView, view, i, l -> val item:Map = adapterView.getItemAtPosition(i) as Map var intent:Intent=Intent(this,ThanksActivity::class.java) //javaのクラスはこのようにかく intent.putExtra("menuName",item["name"]) intent.putExtra("menuPrice",item["price"]) startActivity(intent) } val intent = getIntent() menu.text="あなたの注文は ${intent.getStringExtra("menuName")} で ${intent.getStringExtra("menuPrice")} 円です!" ==== intentの中身の確認 ==== val intent = getIntent() val ban:Bundle?=intent.extras for (k in ban?.keySet()!!){ Log.d("TagName", "key=${k} val=${ban[k] as String}") } ===== 第8回.Androidアプリのメニュー ~ オプションメニューとコンテキストメニュー ===== ==== 「戻る」メニュー ==== override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //この1行追加 supportActionBar?.setDisplayHomeAsUpEnabled(true) } override fun onOptionsItemSelected(item: MenuItem): Boolean { val id = item.itemId if(id == android.R.id.home) finish() return super.onOptionsItemSelected(item) } ===== 第11回.Androidアプリでの非同期処理とWeb API連携 ===== ==== UIスレッド(mainスレッド)へのアクセス ==== E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1 Process: com.nekotype.ips.api, PID: 22804 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.  ↓ ・・・ GlobalScope.launch { val apidata = URL("http://weather.livedoor.com/forecast/webservice/json/v1?city=130010").readText() async(Dispatchers.Main) { //UIスレッドでコルーチンを実行する text.text=apidata } ・・・ ==== HTTPアクセスのパーミッション ==== E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1 Process: com.nekotype.ips.api, PID: 22981 java.io.IOException: Cleartext HTTP traffic to weather.livedoor.com not permitted step1:xmlフォルダを作り、network_security_config.xmlを作成する。 livedoor.com AndroidManifest.xmlに作成したnetwork_security_configへの参照を追加する。 //←ここ ==== HTTPSの証明書エラー ==== E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1 Process: com.nekotype.ips.api, PID: 23831 javax.net.ssl.SSLPeerUnverifiedException: Hostname weather.livedoor.com not verified: 証明書エラーを無視するようにしてみたが、なぜか301が帰ってきてしまう。 301 Moved Permanently

301 Moved Permanently


nginx
一応やってみた2パターンの対応 === 証明証を無視するパターン1 === fun myTask(id:String){ GlobalScope.launch { disableSSLCertificateChecking() //証明書の無効処理呼び出し val urlStr = "https://weather.livedoor.com/forecast/webservice/json/v1?city=${id}" //val urlStr = "https://chargen-api.herokuapp.com/" //val urlStr = "https://www.google.co.jp/" val url= URL(urlStr) val con=url.openConnection() as HttpsURLConnection con.requestMethod="GET" con.connect() val stream=con.getInputStream() val response=streamToString(stream) async(Dispatchers.Main) { text.text=response } } } private fun streamToString(stream: InputStream?): String { val reader=BufferedReader(InputStreamReader(stream,"UTF-8")) val sb=StringBuilder() val b = CharArray(10240) var line:Int val result = reader.read(b).let { sb.append(b,0,it) } return result.toString() } // 証明書の無効処理 @Throws(Exception::class) fun disableSSLCertificateChecking() { println("[WARN] *** SSLCertificate Checking DISABLED ***") // ホスト名の検証を行わない val hv = HostnameVerifier { s, ses -> println("[WARN] *** HostnameVerifier DISABLED *** ") true } HttpsURLConnection.setDefaultHostnameVerifier(hv) // 証明書の検証を行わない val km: Array? = null val tm = arrayOf(object : X509TrustManager { @Throws(CertificateException::class) override fun checkClientTrusted(arg0: Array, arg1: String) { } @Throws(CertificateException::class) override fun checkServerTrusted(arg0: Array, arg1: String) { } override fun getAcceptedIssuers(): Array? { return null } }) val sslcontext = SSLContext.getInstance("SSL") sslcontext.init(km, tm, SecureRandom()) HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.socketFactory) } === 証明書を無視するパターン2 === 色々試しているとそもそも動かなくなった... // ↓ val tm = arrayOf(object : X509TrustManager { override fun getAcceptedIssuers(): Array? { return null } override fun checkClientTrusted(xc: Array, type: String) {} override fun checkServerTrusted(xc: Array, type: String) {} }) val ctx = SSLContext.getInstance("SSL") ctx.init(null, tm, SecureRandom()) // ↑ val urlStr = "https://weather.livedoor.com/forecast/webservice/json/v1?city=${id}" val url= URL(urlStr) val con=url.openConnection() as HttpsURLConnection // ↓ con.doOutput = true con.sslSocketFactory=ctx.socketFactory // ↑ con.requestMethod="GET" con.connect() ==== BufferedReaderのread ==== val reader=BufferedReader(InputStreamReader(stream,"UTF-8")) val sb=StringBuilder() val b = CharArray(10240) var line:Int // javaの場合は下。これはそのまま流用できな。 // while(0 <= {line = reader.read(b)}) { // sb.append(b, 0, line); // } val result = reader.read(b).let { sb.append(b,0,it) } return result.toString() ==== BufferedReaderが長すぎて読み込めない ==== read(b)をreadline()に修正 private fun streamToString(stream: InputStream?): String { val reader=BufferedReader(InputStreamReader(stream,"UTF-8")) val sb=StringBuilder() var line:Int val result = reader.readLine().let { sb.append(it) } return result.toString() } ==== 詳細が画面に収まらないためスクロールさせる ==== ==== HTTPアクセスパターン1 ==== val urlStr = "http://weather.livedoor.com/forecast/webservice/json/v1?city=${id}" val url= URL(urlStr) val con=url.openConnection() as HttpURLConnection con.requestMethod="GET" con.connect() ==== HTTPアクセスパターン2==== val apidata = URL("http://weather.livedoor.com/forecast/webservice/json/v1?city=${id}").readText() ===== 第12回.Androidアプリでのメディア再生 ===== ==== String リソースから文字列の取得方法 ==== btPlay.setText( R.string.bt_play_play) btPlay.text= getString( R.string.bt_play_play) ===== 第13回.Androidアプリでのバックグラウンド処理と通知機能 ===== android:exported 属性を含めて false に設定すると、サービスを自身のアプリでしか利用できないようにすることができます。これにより、他のアプリによるサービスの開始を効果的に回避でき、たとえ明示的インテントを使用したとしても開始できなくなります。 ==== No Channel found for ==== [[https://qiita.com/b_a_a_d_o/items/83c01942a348efd551d4|Android O(APIバージョン26)のPush通知]] Android O(APIバージョン26)以上ではNotifcationの使用には「通知チャネル」の登録が必要。 E/NotificationService: No Channel found for pkg=com.nekotype.ips.servicesample, channelId=null, id=1, tag=null, opPkg=com.nekotype.ips.servicesample, callingUid=10091, userId=0, incomingUserId=0, notificationUid=10091, notification=Notification(channel=null pri=0 contentView=null vibrate=null sound=null defaults=0x0 flags=0x0 color=0x00000000 vis=PRIVATE) _player.setOnCompletionListener { Log.d("service","setOnCompletionListener is called") val manager:NotificationManager=getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ val channel = NotificationChannel("channel_id_sample","プッシュ通知",NotificationManager.IMPORTANCE_DEFAULT) channel.apply { enableLights(true) // 通知時にライトを有効にする lightColor = Color.WHITE // 通知時のライトの色 lockscreenVisibility = Notification.VISIBILITY_PUBLIC //ロック画面での表示レベル } manager.createNotificationChannel(channel) } val builder=NotificationCompat.Builder(this) builder.apply { setSmallIcon(android.R.drawable.ic_dialog_info) setContentTitle("再生終了") setContentText("音声ファイルの再生が終了しました") } if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ builder.setChannelId("channel_id_sample"); } val notification = builder.build() manager.notify(1, notification) stopSelf() } {{:kotlin:pasted:20191018-062650.png}} ==== startForegroundService ==== [[https://qiita.com/gksdyd88/items/30df1f220001fad69d9e|Android Oreoでサービスを使ってみる]] [[https://qiita.com/naoi/items/03e76d10948fe0d45597|Foreground Serviceの基本]] [[https://qiita.com/nukka123/items/791bc4f9043764789ee6|Android Oからのバックグラウンド・サービスの制限事項を実演する。]] [[https://www.muaaru.com/2018/11/17/post-254/|[Android]バックグラウンドでセンサーなどのログを取得し続けるには]] [[https://developer.android.com/about/versions/oreo/background|バックグラウンド実行制限]] [[https://developer.android.com/about/versions/oreo/background-location-limits|バックグラウンド位置情報の制限]] Foreground(=前)Service(バックグラウンド)と思っていたので矛盾を感じていた。 しかし、Foregroundはユーザーが確認できること。 Serivceは画面がないことを意味する。 つまり画面はないが、ユーザーが確認できるのがForegroundService。 ユーザーは通知で動作を確認できるが画面がない。 設定ポイントは下記3点 - startForegroundService(intent)<呼出側> - 5秒以内にstartForeground(1, notification)<呼ばれ側> -  追加 下記エラーは3つ目のuses-permissionの追加で解消した。 2019-10-18 23:51:22.643 4837-4837/com.nekotype.ips.servicesample E/AndroidRuntime: FATAL EXCEPTION: main Process: com.nekotype.ips.servicesample, PID: 4837 java.lang.SecurityException: Permission Denial: startForeground from pid=4837, uid=10091 requires android.permission.FOREGROUND_SERVICE ===== 第14回.Android地図アプリとの連携とGPS機能の利用 ===== ==== Locationのパーミッション ==== [[https://developer.android.com/guide/topics/security/permissions.html?hl=ja|システム パーミッション]] GPS:高精度な位置情報を取得できるが、電力消費が激しい。屋内では使えない。 Network:位置情報の精度は落ちるが、電力消費が少ない。屋内でも取得できる ==== Parameter 'xxxx' is never used ==== {{:kotlin:pasted:20191019-105034.png}}  ↓ fun onMapShowCurrentButtonClick(@Suppress("UNUSED_PARAMETER")vieie: View){ val uriStr="geo:${latitude},${longitude}" val uri=Uri.parse(uriStr) val intent = Intent(Intent.ACTION_VIEW,uri) startActivity(intent) } ==== ユーザー許可を取得する ==== override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val locationManager=getSystemService(Context.LOCATION_SERVICE)as LocationManager // 位置情報のユーザー許可を確認 if (ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { // 許可がなければ許可を求める val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) ActivityCompat.requestPermissions(this, permissions, 1000) // 求めた結果はonRequestPermissionsResult return } locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,0L,0F,GPSLocationListener()) } // パーミッション許可を求めた結果 override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode==1000 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ //パーミッションが許可された場合 val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager val locationListener = GPSLocationListener() // パーミッション許可を求めた結果を確認しているので不要に思えるが、 // ↓ locationManager.requestLocationUpdates を呼ぶ前にかならず必要なだけ if (ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){ return } locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,0L,0F,locationListener) } } ===== 第16回.Androidのマテリアルデザイン ~マテリアルデザインとツールバー~ ===== ==== ツールバー ==== ・・・ ・・・ ===== 第17回.Androidのマテリアルデザイン ~スクロール連動~ ===== AndroidXを使用できるようにする。 {{:kotlin:pasted:20191015-065512.png}} //andorid名前空間の読み込み AAPT: error: attribute layout_scrollFlags (aka com.nekotype.ips.toolbar2:layout_scrollFlags) not found.  ↓ build.gradle(Modlue.app)に追加 implementation 'com.google.android.material:material:1.1.0-beta01' ==== enterAlwaysモードでスクロール連動させたい場合まとめ ==== 標準のアクションバーを非表示