💻STUDY/ANDROID STUDY

10주차 스터디 정리

coldNoodlePigeon 2022. 8. 1.

 

 

갤러리에서 이미지 가져오기 

//갤러리 여는 코드 
val REQ_GALLERY = 12 

fun openGallery(){
	val intent= Intent(Intent.ACTION_PICK) //선택하는 창이 나옴. 어떤 종류의 데이터를 선택할건지 정함 
    intent.type=MediaStore.Images.Media.CONTENT_TYPE 
	startActivityforResult(intent.REQ_GALLERY) 
}

갤러리에서 선택 후 onActivityResult 함수 호출. 

REQ_GALLERY ->{
	//uri 형태로 이미지가 불러오게 됨 
	data?.data?.let(uri ->
    	binding.imagePreview.setImageURI(uri)
    } 
}

onActivityResult에 다음과 같은 코드를 추가한다. REQ_GALLERY인 경우 이미지를 uri 형태로 불러온다. 

 

 

 

 

쓰레드 타이머 

package com.example.a45study

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import com.example.a45study.databinding.ActivityMainBinding
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity() {

    val binding by lazy{ActivityMainBinding.inflate(layoutInflater)}

    val total =0
    var started =false

    val handler = object:Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message) {
            //스레드를 통해서 값을 쓰면 값을 꺼내서 화면에 표시
            val minute =String.format("%02d",total/60)
            val second = String.format("%02d",total%60)

            binding.textTimer.text="$minute:$second"
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.buttonStart.setOnClickListener {
            thread(start=true){
                while(true){
                    Thread.sleep(1000)
                    //무한루프를 돌면서 1씩 증가함 
                    total = total+1 //여기서 val은 reassign 될 수 없다는 에러가 뜬다..  
                    handler?.sendEmptyMessage(0) //handler에 아무런 값을 주지 않음. 
                }
            }
        }
    }
}

시작버튼을 누르면 타이머가 작동되도록 하였다. total은 전역변수이기 때문에 모든 곳에서 access 가능하나, setOnClickListener 함수에서 reassign 될 수 없다는 에러가 발생한다. 뭐지? 

 

 

package com.example.a45study

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import com.example.a45study.databinding.ActivityMainBinding
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity() {

    val binding by lazy{ActivityMainBinding.inflate(layoutInflater)}

    val total =0
    var started =false

    val handler = object:Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message) {
            //스레드를 통해서 값을 쓰면 값을 꺼내서 화면에 표시
            val minute =String.format("%02d",total/60)
            val second = String.format("%02d",total%60)

            binding.textTimer.text="$minute:$second"
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.buttonStart.setOnClickListener {
            started=true
            thread(start=true){
                while(started){
                    Thread.sleep(1000)
                    if(started){
                        //무한루프를 돌면서 1씩 증가함
                        total = total+1 //여기서 val은 reassign 될 수 없다는 에러가 뜬다..
                        handler?.sendEmptyMessage(0) //handler에 아무런 값을 주지 않음.
                    }
                }
            }
        }
        
        binding.buttonStop.setOnClickListener { 
            if(started){
                started=false 
                total =0
                binding.textTimer.text="00:00"
            }
        }
    }
}

started가 true이면 무한루프를 돌며 total이 1씩 증가하고, buttonStop을 눌렀을때 started를 false로 바꾸고 textTimer의 텍스트를 초기화시킨다. 

 

 

코루틴 이미지 다운로드 

package com.example.a46study

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import com.example.a46study.databinding.ActivityMainBinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URL

class MainActivity : AppCompatActivity() {

    val binding by lazy{ActivityMainBinding.inflate(layoutInflater)}

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.buttonDownload.setOnClickListener {
            CoroutineScope(Dispatchers.Main).launch {
                //코드가 다른 영역에서도 동작하게 함.
                //메인스레드와는 다른 영역
                //코루틴만의 영역
                binding.progress.visibility= View.VISIBLE
                val url = binding.editUrl.text.toString()

                //Main 스코프와 openStream의 스트림과 통신이 불가능하므로 IO스코프에서 실행하도록 함.
                //withContext 사용해서 IO스코프 영역에서 열도록 함.
                val bitmap = withContext(Dispatchers.IO){
                    loadImage(url)
                }

                binding.imagePreview.setImageBitmap(bitmap)
                binding.progress.visibility=View.INVISIBLE
            }
        }
    }
}

//코루틴에서 사용할 함수는 suspend라는 예약어 붙여야함. top level에서 작성해야.
//imageUrl을 가져와서 화면의 Bitmap으로 반환 ->imageview에 표시
suspend fun loadImage(imageUrl:String): Bitmap {
    val url = URL(imageUrl)
    val stream = url.openStream() //url에 잇는 것을 스트림으로 염

    return BitmapFactory.decodeStream(stream)
}

manifest에서 인터넷 권한을 주는 것을 꼭 기억하자 

 

 

Android 서비스 

다음과 같이 MyService.kt를 생성해준다. 

 

package com.example.a47study

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class MyService : Service() {

    val ACTION_CREATE = "create"
    val ACTION_DELETE = "delete"

    override fun onBind(intent: Intent): IBinder {
        //onBind 바운드 서비스를 하기 위한 함수
        //major component이다.
        TODO("Return the communication channel to the service.")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        //서비스로 명령어를 날려서 서비스에 지시를 하는건데 onStartCommand에 intent로 명령어를 전달.
        val action = intent?.action
        when(action){
            ACTION_CREATE->create()
            ACTION_DELETE->delete()
        }

        return super.onStartCommand(intent, flags, startId)
    }

    fun create(){
        //뭔가를 생성
        Log.d("서비스","create()가 호출됨")
    }
    fun delete(){
        //뭔가를 삭제
        Log.d("서비스","delete()가 호출됨")
    }
}

 

 

package com.example.a47study

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View

class MainActivity : AppCompatActivity() {
    private lateinit var serviceIntent:Intent

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        serviceIntent=Intent(this,MyService::class.java)
    }

    fun serviceStart(view:View){ //parameter에 view 넣어놓으면 activity_main에서 선택하는 방식으로 실행 가능
        serviceIntent.action=MyService.ACTION_CREATE
        startService(intent)
    }

    fun serviceCommand(){
        serviceIntent.action=MyService.ACTION_CREATE
        startService(intent)
    }

    fun serviceStop(view:View){
        stopService(intent)
    }
}
package com.example.a47study

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.IBinder
import android.view.View

class MainActivity : AppCompatActivity() {
    private lateinit var serviceIntent:Intent

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        serviceIntent=Intent(this,MyService::class.java)
    }

    fun serviceStart(view:View){ //parameter에 view 넣어놓으면 activity_main에서 선택하는 방식으로 실행 가능
        serviceIntent.action=MyService.ACTION_CREATE
        startService(intent)
    }

    fun serviceStop(view:View){
        stopService(intent)
    }

    var myService:MyService? = null
    var isService=false
    val connection =object:ServiceConnection{
        override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
            isService=true
            val binder = p1 as MyService.MyBinder //p1을 미리 정해놓은 MyBinder 타입으로 변환
            myService=binder.getService() //서비스를 담아놓음
        }

        override fun onServiceDisconnected(p0: ComponentName?) {
            //바인딩이 언바인드 되었다고 호출되는 것이 아니라
            //알 수 없는 예외가 발생했을때 호출됨. ->isService를 통해 연결되었는지를 확인
            isService=false
        }
    }

    fun serviceBind(view:View){
        bindService(intent,connection, Context.BIND_AUTO_CREATE)//(intent, serviceconnection,int값(플래그)
    }

    fun serviceCommand(){
        myService?.create()
        myService?.delete()
    }

    fun serviceunBind(view:View){

    }
}

 

 

Android 포어그라운드 서비스 

 

서비스, notification과 함께 보여주는 서비스 

서비스가 돌아가고 있다는 것을 사용자에게 알림. 

장시간 동작하는 서비스들은 포어그라운드 서비스로 만들게끔 강제하고 있음. 

 

package com.example.a48study

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import kotlin.concurrent.thread

class Foreground : Service() {

    //notification이 오레오 이후부터 '채널'이라는 것을 사용함.
    //채널 아이디 정해두기
    val CHANNEL_ID= "FGS153"

    val NOTI_ID=153

    fun createNotificationChannel(){
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            val serviceChannel =NotificationChannel(CHANNEL_ID,"FOREGROUND",NotificationManager.IMPORTANCE_DEFAULT)
            //채널 아이디, 이름, 중요도(중요도 높으면 소리를 들려준다던가, 소리+진동이라던가..알림의 강도 세짐)
            val manager = getSystemService(NotificationManager::class.java) //notification 매니저 생성
            manager.createNotificationChannel(serviceChannel)
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        createNotificationChannel()

        //띄울 notification 만듬
        val notification= NotificationCompat.Builder(this,CHANNEL_ID)
            .setContentTitle("Foreground Service")//notification 제목에 해당되는 부분
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .build()

        startForeground(NOTI_ID,notification) //내가 현재 사용하는 서비스가 forground도 동작한다는 사실을 알려줌.

        return super.onStartCommand(intent, flags, startId)
    }

    fun runBackground(){
        thread(start=true){
            for(i in 0..100){
                Thread.sleep(1000)
                Log.d("서비스","count===>$i")
            }
        }
    }
    override fun onBind(intent: Intent): IBinder {
        return Binder()
    }
}

service에 new로 foreground.kt를 만들어준다. runBackground()는 log창에서 서비스가 계속 작동하고있는지를 체크하기 위한 함수이다. 

 

package com.example.a48study

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat

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

    fun serviceStart(view:View){
        val intent = Intent(this,Foreground::class.java) //호출할 서비스
        ContextCompat.startForegroundService(this,intent)
    }

    fun serviceStop(view:View){
        val intent = Intent(this,Foreground::class.java) //호출할 서비스
        stopService(intent)
    }
}

'💻STUDY > ANDROID STUDY' 카테고리의 다른 글

9주차 스터디 정리  (0) 2022.07.25
8주차 스터디 정리  (0) 2022.07.18
[Android-Kotlin] 7주차  (0) 2022.05.25
[Android-Kotlin] 6주차  (0) 2022.05.16
[Android-Kotlin] 5주차  (0) 2022.05.09

댓글