[Android] Android 11 대응 - 1. 패키지 공개 상태

변경사항 확인해보는 방법

Android Developer

문제가 될만 한 호출 메소드

  • packageManager.getInstalledApplications() 또는 packageManager.getInstalledPackages()

  • packageManager.resolveActivity(intent, 0)

  • packageManager.queryIntentActivities(intent, flags)

  • packageManager.getPackageInfo("packageName", flags)

  • packageManager.getLaunchIntentForPackage(packageName)

방법 1. QUERY_ALL_PACKAGES

QUERY_ALL_PACKAGES 권한을 추가하면 모든 앱을 찾거나 실행할 수 있다. 이 퍼미션은 어떤 앱이든 받을 수 있는 Install permission이다.

1
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />

방법 2. <queries> 태그

gradle version 확인하기

queries 태그를 사용하기 위한 준비작업이다.

Android developer

좌측과 같은 버전을 사용하고 있다면 우측 이상에 해당하는 버전이 맞는지 확인해야 한다.

queries 태그 사용

이제 의존성을 가진 모든 앱의 패키지 정의를 queries 태그에 추가 적용한다.

Android11에서는 기본적으로 자신의 앱이 아닌, 다른 패키지를 찾거나 다른 패키지의 액티비티를 실행할 수 없다. 따라서 자신의 앱 동작에 의존적인 패키지가 있다면 AndroidManifest에 <queries> 태그로 필요한 패키지를 정의해야 한다.

다시 말해, 자신의 앱에서 다른 앱을 찾거나(탐색), 다른 앱을 실행하는 동작이 필요할 경우 그 앱의 패키지를 미리 자신의 앱에 정의해두어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- manifest 태그에 package 속성을 정의하는 것은 optional -->
<manifest package="com.example.game">
<queries>
<package android:name="com.example.store" />
<package android:name="com.example.services" />
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="text/plain" />
</intent>
</queries>
...
</manifest>

[Android] ViewPager.addOnPageChangeListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
loopViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageSelected(position: Int) {
loopViewPager.currentItem = position
}
override fun onPageScrolled(position: Int, positionOffset: Float,
positionOffsetPixels: Int) {
  }
override fun onPageScrollStateChanged(state: Int) {
if (state == RecyclerView.SCROLL_STATE_DRAGGING) {
// 수동으로 스크롤 시에만 터치했을 때
} else if (state == RecyclerView.SCROLL_STATE_SETTLING){
// 스크롤이 움직인 후 픽스되는 순간
} else if (state == RecyclerView.SCROLL_STATE_IDLE) {
// 아무것도 수행하지 않을 때
// (즉, 스크롤이 움직이지 않고, 스크롤을 수동으로 잡고 있지 않음.)
}
}
})

onPageSelected

  • 페이지 인덱스가 변경되지 않는다면 이 메소드는 호출되지 않는다. (수동으로 스크롤 하다가 뗐을 때 재현가능)
  • position: 스크롤 시 선택된 페이지의 인덱스

onPageScrolled

  • 사용자의 터치 스크롤 & smooth 스크롤 시에 호출된다.
  • position: 링크에 따르면, “SETTLE 상태에서는 대상 페이지가 넘어오고, DRAGGING 상태에서는 출발 페이지가 넘어온다”고 한다. 따라서 이를 활용하기는 어려울 것 같아 사용하지 않음.
  • positionOffset: 0.0 ~ 1 사이의 값. 왼쪽으로 스크롤 시 1 -> 0.0으로 감소하며, 오른쪽으로 스크롤 시 0.0 -> 1로 증가한다.

onPageScrollStateChanged

  • 페이지 인덱스가 변경될 경우, 변경되지 않을 경우 포함
  • 상태가 변경될 경우 한 번씩 호출된다.

이벤트 전달 순서

  • 수동으로 스크롤 시 페이지를 변경했을 때 순서 SCROLL_STATE_DRAGGING -> SCROLL_STATE_SETTLING -> onPageSelected SCROLL_STATE_IDLE
  • loopViewPager.setCurrentItem으로 스크롤될 경우 순서 SCROLL_STATE_SETTLING -> onPageSelected -> SCROLL_STATE_IDLE
  • 수동으로 스크롤 시 페이지를 변경하지 않았을 경우 순서 SCROLL_STATE_DRAGGING -> SCROLL_STATE_SETTLING -> SCROLL_STATE_IDLE

[Android] Parcelable: Java와 Kotlin 구현 차이

Parcelable을 상속받아 클래스 생성할 경우 CREATOR의 정의

Parcelable protocol requires a Parcelable.Creator object called CREATOR

1
2
3
4
5
6
7
8
9
10
11
public static final Creator<DataClass> CREATOR = new Creator<DataClass>() {
@Override
public SearchInfo createFromParcel(Parcel parcel) {
return new DataClass(parcel);
}

@Override
public DataClass[] newArray(int i) {
return new DataClass[i];
}
};

이걸 코틀린 코드로 변환하면 다음과 같이 만들 수 있으나-

1
2
3
4
5
6
7
8
9
10
@JvmField // 프로젝트에 자바 클래스도 존재하는 경우 JvmField 어노테이션 누락에 주의할 것 
val CREATOR: Parcelable.Creator<DataClass> = object : Parcelable.Creator<DataClass> {
override fun createFromParcel(parcel: Parcel): DataClass {
return DataClass(parcel)
}

override fun newArray(i: Int): Array<DataClass?> {
return arrayOfNulls<DataClass?>(i)
}
}

[코틀린] Parcelable 구현

-그러나 코틀린에서는 Parcelable 구현을 위해 @Parcelize 어노테이션을 제공하고 있기 때문에 꼭 필요한 경우가 아니면 자바와 같이 CREATOR를 만들 필요는 없다.

코틀린에서 구현한 Parcelable을 상속받은 Data Class는 다음과 같다. 이 코드는 이 자체만으로도 내부에서 CREATOR 기능을 수행한다.

1
2
3
4
5
6
7
8
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.android.parcel.Parcelize

@Parcelize
data class ParcelData(@SerializedName("A") var a: String? = "",
@SerializedName("B") var b: String? = "",
@SerializedName("C") var c: ArrayList<AnyData>? = arrayListOf()): Parcelable

참고를 위해 Parcelize 어노테이션에 작성된 주석의 일부를 가져왔다.

Instructs the Kotlin compiler to generate writeToParcel(), describeContents()[android.os.Parcelable] methods, as well as a CREATOR factory class automatically.

이는 자바 코드(또는 코틀린 코드) 상에서 putExtra를 통해 전달할 수 있다.

1
2
ParcelData p = new ParcelData();
intent.putExtra("extra", p);

그리고 intent를 통해 이동한 액티비티에서는 getParcelableExtra로 데이터를 받아올 수 있다.

[Android] 다른 앱 위에 그리기 권한: TYPE_APPLICATION_OVERLAY

[다른 앱 위의 그리기]는 폰 설정 > 애플리케이션 > 앱 > 고급 [다른 앱 위에 표시되는 앱]에서 권한 설정할 수 있다.

퍼미션

[다른 앱 위에 그리기] 설정을 추가하려면 Manifest에 이를 사용하겠다고 퍼미션을 추가해야하는데,

1
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

권한: 다른 앱 위에 그리기(Draw over other apps / Appear on top)

위 퍼미션을 추가하면 앱 설정(설정>애플리케이션>해당 앱)에 [다른 앱 위에 그리기] 정보가 뜬다. (안드로이드 버전마다 언어:영어일 때의 표시나 노출 위치는 다를 수 있다.)

중요한 것은 [다른 앱 위에 그리기]의 기본값이 버전마다 다르다는 건데,

  • API level 23(Android 6.0 Marshmallow) 미만에서는 true
  • 그 이상에서는 false

앱스토어에서 설치할 경우 기본적으로 위와 같이 설정되어 유저의 단말에 설치됨을 숙지하여 기능구현 프로세스를 짜야한다.

버전 체크 방법

[다른 앱 위에 그리기] 기능이 필요할 경우 API level 23 이상에서는 버전 체크 처리를 해주어야한다.

1
2
3
4
5
6
7
/* REQ_CODE_OVERLAY_PERMISSION는 임의로 정한 상수
onActivityResult(int requestCode, int resultCode, Intent data)에서 requestCode로 받을 때 사용함 */
@TargetApi(Build.VERSION_CODES.M)
private static void onObtainingPermissionOverlayWindow(Context context) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName());
((Activity) context).startActivityForResult(intent, REQ_CODE_OVERLAY_PERMISSION);
}

덧붙여 [다른 앱 위에 그리기] 설정값이 true인지 확인하는 방법은 다음과 같다.

1
2
3
4
public static boolean alertPermissionCheck(Context context) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& !Settings.canDrawOverlays(context);
}

주의: deprecated 된 WindowManager.LayoutParams의 플래그

  • TYPE_PHONE
  • TYPE_PRIORITY_PHONE
  • TYPE_SYSTEM_ALERT
  • TYPE_SYSTEM_ERROR
  • TYPE_SYSTEM_OVERLAY
  • TYPE_TOAST

위에 나열된 타입은 API level 26(Android 8.0 Oreo)에 deprecated 되어 아래와 같이 Android Developer 에서는 이것들 대신 TYPE_APPLICATION_OVERLAY를 쓰도록 가이드 하고 있다.

1
2
3
4
5
6
7
val param: WindowManager.LayoutParams
val flag = if (Build.VERSION.SDK_INT >= Build.Version_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}
param = WindowManager.LayoutParams(width,
WindowManager.LayoutParams.WRAP_CONTENT,
flag, ...);

탐욕법(Greedy Algorithm)

탐욕법(Greedy Algorithm)이란?

미래를 고려하지 않고 각 단계에서 최적의 해를 찾아 모든 단계를 진행할 경우 최선의 결과에 도달한다고 생각하는 알고리즘.

특징

  • 전체적인 최적해를 보장할 수 없다.
  • 선택한 것을 번복하지 않는다.
  • 직관적

예시, 최소 신장 트리

예시, 거스름돈 최소 개수 반환

거슬러줄 돈(w)에서 동전(10, 50, 100, 500)을 뺐을 때 그 값이 가장 작은 경우의 동전을 우선 반환한다.

여기서 “뺀 값이 가장 작은 경우가 최적의 해”라는 게 이 문제에서 가장 근본적인 명제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 거스름돈 최소 개수 반환
int change = w; //입력: 거슬러줄 돈
int n500, n100, n50, n10 = 0;
while(change >= 500) {
change -= 500; n500++;
}
while(change >= 100) {
change -= 100; n100++;
}
while(change >= 50) {
change -= 50; n50++;
}
while(change >= 10) {
change -= 10; n10++;
}
return n500 + n100 + n50 + n10;

단, 200원에 대해 동전이 위의 네 가지 밖에 없다면 최종해는 100*2로 “2”겠지만, 160원짜리 동전이 만들어진다면  160*1 + 10*4로 “5”가 나오기 때문에 탐욕법으로 최적해를 찾을 수 없게 된다. 이처럼 모든 상황에서 최적해를 찾을 수 있는 유연한 방법이 아니다.

동적 계획법과 비교된다.

동적 계획법(Dynamic Programming)이란, 전체를 바라보고 그것을 여러 개의 하위 문제들로 나누어 각 하위 문제들의 답을 이용해 최종 답을 내는 것이다.(복잡한 문제를 간단한 여러 개의 문제로 나누어 푸는 방법)

동적 계획법 특징

  • 큰 문제 안에 작은 문제가 중첩되어 있는 문제를 해결하는 데 사용. 예를 들어, 피보나치 수열.
  • 중첩되는 데이터라면 저장하고, 지속적으로 데이터를 참조한다.

피보나치 수열을 간단하게 코드화하면 아래와 같다.

1
2
3
4
5
public void fib(int n) {
if (n == 0) return 0;
else if (n == 1) return 1;
else return fib(n-1) + fib(n-2);
}

이때, fib(5)를 구하려고 하면 fib(2)의 계산은 여러번 중복된다. 이로 인한 계산 속도의 저하를 막기 위해 fib(2)와 같이 중복되는 값은 배열에 저장하여 필요할 때 배열에 접근해서 값을 가져오는 방식이다.

중복계산이 줄어들기 때문에 시간 복잡도는 O(n)가 된다.