DBILITY

안드로이드 정리 1 본문

android/kotlin

안드로이드 정리 1

DBILITY 2023. 9. 15. 15:32
반응형

Jetpack Compose는 나중에 보자. 안드로이드 생존 코딩을 보면서 작성했다.

IDE는 Giraffe 2022.3.1 Patch 2 다. 뭘 몰라서 그런지 어렵다^^

AppCompatActivity Class = 구버전 기기에서도 최신 기능을 쓸 수 있게 해주는 기능을 제공, 하위 호환이 되는 앱에는 필수

activity = 화면 ( 개별윈도우 )

activity는 반드시 onCreate를 구현해야 하며, 부모 클래스의 생성자를 호출해야 한다.

1) onCreate -> 2)  onStart -> 3)  onResume -> 4)  (Execute) -> 5)  onPause -> 6)  onStop -> 7)  onDestory 순이다

시작하면 1~4까지가 되겠고, 중간에 다른 Activity가 실행되면 5로 화면에 가려지면 6 강제 또는 정상종료면 7이 호출된다.

5상태는 다른앱이 실행되거나 홈키를 누르거나 전원버튼으로 화면을 끄는 경우고,

5상태에서 Activity로 돌아가면 3으로, 6상태에서 돌아가면 2가 호출(onRestart후에)된다. 헌데 메모리 부족 등으로 강제 종료 되었다면 1로 간다고. 각각 생명주기에 뭔가 하고 싶으면 override해야 하겠다.

뭐든 생명주기(lifecycle)가 있으니 숙지해야겠다. 상식 수준에서 이해하면 되려나? 나의 생명주기는 어디쯤인가...

Activity를 생성하고 코드를 보면 항상 onCreate를 override하는데 이때 parameter에 savedInstanceState가 있다.

말그대로 저장된 인스턴스 상태(메모리저장)라는 건데 상태를 저장할 필요가 있으니 저게 있겠지?

화면방향전환,입력기,언어,지역 등의 변경이 설정의 변경이라고 하는데 그때 저걸 쓰면 되나?

onSaveInstanceState, onRestoreInstanceState를 override할 수 있는 걸보기 여기서 처리하나 봄..아니면 말고

https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko

 

활동 수명 주기에 관한 이해  |  Android 개발자  |  Android Developers

활동은 사용자가 전화 걸기, 사진 찍기, 이메일 보내기 또는 지도 보기와 같은 작업을 하기 위해 상호작용할 수 있는 화면을 제공하는 애플리케이션 구성요소입니다. 각 활동에는 사용자 인터페

developer.android.com

다른 activity를 호출할때는 Intent를 생성하고 startActivity(Intent)와 같이 실행한다.

제일 중요한~프로그램 종료는 아래처럼 했다. 종료 관련은 여러 상황마다 다르므로 검색해서 공부하자.

Handler(Looper.getMainLooper()).postDelayed({    
    moveTaskToBack(true)
    finishAndRemoveTask()
    exitProcess(0)
}, 500)

미리 정의된 인텐트(암시적 인텐트 ) 는 Intent.ACTION_DIAL, Intent.ACTION_SEND, Intent.ACTION_SEND TO,Intent.ACTION_VIEW 처럼 되어 있다. 안드로이드 11부터는 제약이 있다고 함.AndroidManifest.xml에 아래 같이 패키지 가시성 설정을 한다고.

    <queries>
        <intent>
            <action android:name="android.intent.action.DIAL" />
        </intent>
        <intent>
            <action android:name="android.intent.action.SENDTO" />
            <data android:scheme="*" />
        </intent>
    </queries>

https://developer.android.com/guide/components/intents-common?hl=ko

 

공통 인텐트  |  Android 개발자  |  Android Developers

An intent allows you to start an activity in another app by describing a simple action you'd like to perform (such as "view a map" or "take a picture") in an Intent object. This type of intent is called an implicit intent because…

developer.android.com

setContentView() 메서드는 표시할 레이아웃을 지정한다.R.layout.activity_mian 형태를 인자로 받는데,

R은 Android Studio에서 자동으로 생성되는 리소스 정보 클래스다. 레이아웃, 메뉴, 그림, 문자열 모두 R클래스를 사용해 코드에서 조작할 수 있다.

activity는 layout xml과 세트라고 보면 된다?

Button Class는 TextView Class를 상속받는다. TextView에 clickListener를 등록하려면 focusable,clickable true

code에서 findViewById로 원하는 view를 선택할 수 있다. 책을 보니 이보다는 viewBinding을 활성화하는게 쉽다.

module수준의 grade build script의 android섹션? -> buildFeatures -> viewBinding = true 이렇게 하면 자동으로 뷰를 결합하는 클래스가 생성된다. 이때 activity이름이 MainActivity인 경우 ActivityMainBinding으로 생성된다. MainActivity에 사용할 수 있다. 아주 편하다.

build.gradle.kts
MainActivity.kt

ActionBar를 시험해보려니 죽어도 안된다 Theme을 보니 Theme.Material3.DayNight.NoActionBar다 ActionBar는 별도로 없길래 NoActionBar를 지웠더니 된다. 그런데, 다른 테마를 보면 DarkActionBar가 있다. UI디자인화면에서는 여러가지로 변경해서 미리보기가 된다.

프로젝트 생성할때 Empty Activity로 선택하고 프로젝트 내부에서 Empty View Activity로 Main Activity를 생성하니 기본테마가 그렇다.

ActionBar가 필요한 Activity는 AndroidManifest.xml에서 android:parentActivityName=".MainActivity" 형태로 편집이 필요하다. 다음에 소스에서 다음과 같이 추가하면 상단에 ActionBar가 나오고 상위도 가기도 클릭이 된다.

supportActionBar?.setDisplayHomeAsUpEnabled(true)

일부 activity에만 ActionBar가 필요한 경우라면 theme는 그대로 두고, activity에 Toolbar(appcompat)를 추가한 다음과 같이 해도 된다.

setSupportActionBar(binding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

단순 데이터의 저장에 SharedPreferences를 사용할 수 있다.

https://developer.android.com/training/data-storage/shared-preferences?hl=ko

 

SharedPreferences로 단순 데이터 저장하기  |  Android 개발자  |  Android Developers

DataStore는 로컬 데이터를 저장하는 최신 방법을 제공합니다. SharedPreferences 대신 DataStore를 사용해야 합니다. 자세한 내용은 DataStore 가이드를 참고하세요. SharedPreferences로 단순 데이터 저장하기 컬

developer.android.com

PreferenceManager.getDefaultSharedPreferences(this) 이건 depreciated란다.

다음과 같이 다음과 같이 하니 되기는 한다.

private fun saveData(height: Float, weight: Float) {
    val sharedPref =  this.getSharedPreferences("bmi_default", Context.MODE_PRIVATE)
    val editor = sharedPref.edit()
    editor.putFloat("KEY_HEIGHT",height).putFloat("KEY_WEIGHT",weight).apply();
}

private fun loadData() {
    val sharedPref =  this.getSharedPreferences("bmi_default", Context.MODE_PRIVATE)
    val height = sharedPref.getFloat("KEY_HEIGHT",0f)
    val weight = sharedPref.getFloat("KEY_WEIGHT",0f)

    if(height!=0f&&weight!=0f){
        binding.editTextHeight.setText(height.toString())
        binding.editTextWeight.setText(weight.toString())
    }
}

kotlin에서 주기적 반복이 필요할 경우 timer를 사용한다. 보통 다른 언어도 그렇지 않았던가..아래는 1초간격 수행이다.

timer(period = 1000) {
 //실행코드
}

android는 UI를 조작하는 main thread와 오래 걸리는 작업을 처리하는 worker thread가 존재하고, timer같은 경우는 worker thread에서 동작하기 때문에 ui를 변경 할 수 없는데, 이때는 unOnUiThread() 메서드를 사용해서 UI를 조작 할 수 있다.

timer(period = 1000) {
 //실행코드
   runOnUiThread {
    //UI 조작 코드
   }
   
   runOnUiThread(Runnable() {
     //UI 조작 코드
   })
}

다음은 textView에 현재 일자와 시간을 나타내 봤다. 1초마다 갱신된다.

class MainActivity : AppCompatActivity() {

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

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

        timer(period = 1000) {
            runOnUiThread {
                val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                binding.textViewCurrent.text = dateFormat.format(Date())
            }
        }
        
    }
}

멈추고 싶다면 변수에 Timer객체를 할당하고 변수?.cance()하면 된다.

res > values > colors.xml을 열어 보면 컬러가 지정되어 있다. 이게 기본 테마용 색상을 정의하는 것이라고 한다.

res > values > themes > themes.xml을 열어보니 @color/purple_500 과 같은 형태로 지정이 되어 있다.

전화걸기를 실행하는 코드는 다음과 같다. 미리 정의된 ACTION이 있다.

val telNo = "02-1234-5678"
val phoneNumber = PhoneNumberUtils.formatNumber(telNo,Locale.getDefault().country)
Log.d("phoneNumber", phoneNumber)


val pattern = android.util.Patterns.PHONE
if (!pattern.matcher(phoneNumber).matches()) {
    Toast.makeText(this@MainActivity,"check phone number",Toast.LENGTH_LONG).show()
} else {

    val intent = Intent().apply {
        action = Intent.ACTION_DIAL
        data = Uri.parse("tel:${phoneNumber}")
    }

    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    }
}

webView에 page가 로드된 후에 소프트키보드를 사라지게 하고 싶었다.

binding.webView.apply {
    settings.javaScriptEnabled = true
    settings.databaseEnabled = true
    settings.domStorageEnabled = true
    webViewClient = object : WebViewClient() {
        override fun onPageFinished(view: WebView?, url: String?) {
            //super.onPageFinished(view, url)
            binding.editTextUrl.setText(url)
            //binding.editTextUrl.clearFocus()
            val manager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            manager.hideSoftInputFromWindow(view?.windowToken, 0)
        }
    }
}

daum,nate는 검색어를 입력하고 검색을 한후 재검색을 하려하면  다음과 같은 오류가 나서 안되더라는...

[INFO:CONSOLE(1)] "JQMIGRATE: Migrate is installed, version 1.4.1", source: https://search1.daumcdn.net/search/statics/common/js/fe/tot-search/vendor/common/jquery.migrate.plugin.202
[INFO:CONSOLE(1)] "Uncaught TypeError: s.cookie is not a function", source: https://search1.daumcdn.net/search/statics/common/js/fe/tot-search/vendor/m/common/vendor.m.common.2023101
[INFO:CONSOLE(586)] "Uncaught TypeError: Cannot read property 'send' of undefined", source: https://m.search.daum.net/nate?q=%EC%B2%9C%EA%B3%B5&w=tot&thr=mnms&input_search= (586)
[INFO:CONSOLE(877)] "Uncaught TypeError: Cannot read property 'send' of undefined", source: https://m.search.daum.net/nate?q=%EC%B2%9C%EA%B3%B5&w=tot&thr=mnms&input_search= (877)
[INFO:CONSOLE(1289)] "Uncaught TypeError: Cannot read property 'send' of undefined", source: https://m.search.daum.net/nate?q=%EC%B2%9C%EA%B3%B5&w=tot&thr=mnms&input_search= (1289)
[INFO:CONSOLE(1)] "Uncaught (in promise) TypeError: Cannot read property 'getItem' of null", source: https://search1.daumcdn.net/search/statics/common/js/fe/tot-workspace/collections
Flattened final assist data: 2884 bytes, containing 1 windows, 13 views
[INFO:CONSOLE(1)] "Uncaught TypeError: Cannot read property 'increase' of undefined", source: https://search1.daumcdn.net/search/statics/common/js/log/m_log.min.20231013053721.js (1)
Flattened final assist data: 2892 bytes, containing 1 windows, 13 views
[INFO:CONSOLE(1)] "Uncaught TypeError: Cannot read property 'increase' of undefined", source: https://search1.daumcdn.net/search/statics/common/js/log/m_log.min.20231013053721.js (1)

settings.domStorageEnabled = true 해주니 된다.

AndroidManifest에선 다음과 같이 할 수 있단다. 뭔가 이상하다. windowSoftInputMode를 검색하자.

<activity
    android:name=".MainActivity"
    android:windowSoftInputMode="stateAlwaysHidden"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

URL, 이메일 등의 유효성(?)을 확인하기 위해 Patterns를 이용할 수 있다. 공식사이트 참조

다음은 키보드의 검색을 클릭(?)했을때 검사를 추가한 것이다.

binding.editTextUri.setOnEditorActionListener { _, actionId, _ ->

    if (actionId == EditorInfo.IME_ACTION_SEARCH && binding.editTextUri.text.isNotEmpty()) {
        val url = URLUtil.guessUrl(binding.editTextUrl.text.toString())
        val pattern = android.util.Patterns.WEB_URL
        
        if(pattern.matcher(url).matches()) {
            binding.webView.loadUrl(url)
            true
        } else {
            Toast.makeText(this,"주소를 확인하세요",Toast.LENGTH_SHORT).show()
            false
        }
    } else {
        false
    }
}

url이동을 테스트하다 다음과 같이 cleartext~오류를 만났다..뭔가...https로 연결이 안되네?

android 9부터 보안규칙이 적용되어 허용되지 않은 HTTP사이트에 대한 접근을 불허한다고 한다.

열어주려면 다음과 같이 useCleartextTraffic="true"

백버튼을 눌렀을때 history가 없으면 종료하도록 해봤다. onBackPressed를 구현하면 되나본데 depreciated!!

 

onBackPressedDispatcher에 callback으로 처리한다고 함.

this.onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if(binding.webView.canGoBack()) {
                    binding.webView.goBack()
                } else {
                    val toast = Toast.makeText(binding.root.context, "종료한다잉~", Toast.LENGTH_SHORT)
                    toast.show()

                    try {
                        Thread.sleep(3500)
                        //finish()// Activity 종료..
                        finishAndRemoveTask()
                        //exitProcess(0)//System.exit(0)
                    } catch (e: InterruptedException) {
                        e.printStackTrace()
                    }

                    /*try {
                        TimeUnit.MILLISECONDS.sleep(3500)
                        finish()
                    } catch (e : InterruptedException) {
                        e.printStackTrace()
                    }*/

                    /*Handler(Looper.getMainLooper()).postDelayed({
                        finish()
                    },3500)*/

                }
            }
        })

참고로 delay도 해봤다. 보통 java 예제처럼 Tread를 쓸 수 도 있고, TimeUnit이 있는 줄 처음 알았다. 편하군.
Handler도 당연히 처음이고^^;

Denying clipboard access to com.xxx.xxx...., application is not in focus nor is it a system service for user 0

위와 같은 로그기 줄줄이 있다.

AndroidManifest의 application에 다음을 추가

<uses-library android:name="org.apache.http.legacy" android:required="false" />

지원하는 센서도 많다.

https://developer.android.com/guide/topics/sensors/sensors_overview?hl=ko

 

센서 개요  |  Android 개발자  |  Android Developers

대부분의 Android 지원 기기에는 움직임, 방향 및 다양한 환경 조건을 측정하는 센서가 내장되어 있습니다. 이러한 센서는 높은 정밀도와 정확도로 원시 데이터를 제공하며 3차원으로 모니터링하

developer.android.com

센서를 사용하려면 시스템서비스에서 SensorManager에 대한 참조를 얻어야 한다.

에뮬레이터와 기기에서 센서 리스트를 출력해 봤다.

class MainActivity : AppCompatActivity() {

    private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager
    }

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

        val deviceSensors : List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_ALL)

        for (sensor in deviceSensors) {
            Log.i("sensor ","${sensor.name}")
        }
        
    }
}

/* 에뮬레이터
Goldfish 3-axis Accelerometer
Goldfish 3-axis Gyroscope
Goldfish 3-axis Magnetic field sensor
Goldfish Orientation sensor
Goldfish Ambient Temperature sensor
Goldfish Proximity sensor
Goldfish Light sensor
Goldfish Pressure sensor
Goldfish Humidity sensor
Goldfish 3-axis Magnetic field sensor (uncalibr
Goldfish 3-axis Gyroscope (uncalibrated)
Game Rotation Vector Sensor
GeoMag Rotation Vector Sensor
Gravity Sensor
Linear Acceleration Sensor
Rotation Vector Sensor
Orientation Sensor
*/
/* 기기
LSM6DSL Accelerometer
AK09918C Magnetometer
LSM6DSL Gyroscope
STK31610 Light
AK09918C Magnetometer Uncalibrated
LSM6DSL Gyroscope Uncalibrated
Significant Motion
Step Detector
Step Counter
Tilt Detector
Pick Up Gesture
Device Orientataion
Interrupt Gyroscope
Scontext
STK31610 Light CCT
Call Gesture
Wake Up Motion
STK31610 Auto Brightness Sensor
VDIS Gyroscope
Pro Tos Motion
Flip Cover Detector
Device Orientataion Wake Up
SBM
Hover Proximity
Touch Pocket
Samsung Hall IC
ISG5320A Grip sensor
ISG5320A_SUB Grip sensor for sub
Motion Sensor
Ear Hover Proximity Sensor (ProToS)
Camera Light Sensor
Samsung Game Rotation Vector Sensor
Samsung Gravity Sensor
Samsung Linear Acceleration Sensor
Samsung Rotation Vector Sensor
Samsung Orientation Sensor
*/

가속도센서를 읽는 걸 해놨다..SensorEventListener를 상속 받아 onSensorChanged, onAccuracyChanged를 구현해야 한다. 센서를 계속 읽으면 전력이 소모되니 onResume에 리스너를 등록하고,onPause에는 해제해야 한다고 한다.

센서 값이 가만히 놔둬도 9.81...어디서 본 값...중력가속도인가...맞다..

class MainActivity : AppCompatActivity(), SensorEventListener {

    private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager
    }
    private var accelerometer: Sensor? = null

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

        /*val deviceSensors: List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_ALL)

        for (sensor in deviceSensors) {
            Log.i("sensor ", "${sensor.name}")
        }*/
        //가속도센서,흔들기, 기울이기 등 동작감지
        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)

    }

    override fun onResume() {
        super.onResume()
        accelerometer?.also {
            sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_NORMAL)
        }
    }

    override fun onSensorChanged(event: SensorEvent?) {
        Log.i("onSensorChanged", event?.values.contentToString())       
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
        Log.i("onAccuracyChanged", accuracy.toString())
    }

    override fun onPause() {
        super.onPause()
        accelerometer?.also {
            sensorManager.unregisterListener(this)
        }
    }
}

화면을 계속 켜 놓으려면 다음과 같이

override fun onCreate(savedInstanceState: Bundle?) {
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}

화면 방향을 고정하려면 다음과 같이 Activity 소스에서는

override fun onCreate(savedInstanceState: Bundle?) {
    requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}

AndroidManifest에서는 activity마다?

<activity
    android:name=".LoginActivity"
    android:exported="true"
    android:screenOrientation="portrait">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

음..focus는 requestFocus,clearFocus로 설정 할 수 있다.

반응형

'android > kotlin' 카테고리의 다른 글

안드로이드 정리 3  (0) 2024.01.23
안드로이드 정리 2  (0) 2024.01.19
android avd path change ( 경로 변경 )  (0) 2024.01.16
android bmi  (0) 2024.01.11
kotlin fundamental summary ( 기초정리 )  (0) 2023.08.30
Comments