문제 상황

Notification을 클릭하면 Intent에 담긴 Extra의 url을 웹뷰로 띄우는 작업이다.

Intent로 전달하는 url을 바꾸어도 이전 url이 계속 로드되는 현상이 발생했다.

해결

pending intent의 request code때문이었다.

PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE)

request code인 2번째 파라미터 값을 변경해주면 된다. 

문제

에러코드

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
    Process: , PID: 25378
    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$3.done(AsyncTask.java:354)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
        at java.util.concurrent.FutureTask.run(FutureTask.java:271)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)
     Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter activity
        at android.os.AsyncTask$2.call(AsyncTask.java:333)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
        at java.lang.Thread.run(Thread.java:764)

발생코드

class MainActivity extends AppCompatActivity {
    ...
    public void method() {
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected void onPreExecute() {
            }

            @Override
            protected Void doInBackground(Void... params) {
            	getParent(); // null
                MainActivity.this // ok
                return null;
            }

            @Override
            protected void onPostExecute(Void result) {
            }
        };
    }
}

 

Activity 안의 메소드에서 익명클래스로 AsyncTask를 구현하고 있었고, AsyncTask의 메소드 안에서 getParent() 를 호출하고 있었다.

Activity의 context를 얻고싶어 했던 것같다.

하지만 getParent()는 null로 전달되고 있었다. 

getParent()의 값을 사용하는 메소드의 callee를 kotlin으로 변환하며, Nullable이던 자바의 타입에서 NotNull로 바뀌면서 에러가 생겼다.

해결

코드가 포함되어있는 액티비티의 context를 참조하도록 바꾸었다.

getParent() -> MainActivity.this

의문점

getParent()는 뭐하는 메소드이고, 왜 null이 나오는가?

 

getParent()는 뭐하는 메소드인가?

Activity.java

/** Return the parent activity if this view is an embedded child. */
public final Activity getParent() {
    return mParent;
}
  • mParent를 반환하고 있다.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
final void setParent(Activity parent) {
    mParent = parent;
}

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
        IBinder shareableActivityToken) {
    ...
    
    mParent = parent;
    
    ...
}
  • mParent는 Activity.java의 setParent와 attach에서 할당된다.
  • 이 메소드들은 어디서 호출되는 걸까?
  • ActivityThread의 performLaunchActivity() 에서 attach()를 호출한다.
  • performLaunchActivity()는 ActivityThread의 startActivityNow()와 handleLaunchActivity()에서 호출된다. 

intent할 때 호출되는 콜스택을 보면 handleLaunchActivity()가 호출되고 있다.

startActivityNow()같은 경우는 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 어노테이션이 있다. 
P(AOS9, SDK28)이하에서 접근가능한 메소드인 것이다. (테스트 기기: AOS13)

parent 액티비티의 정보는 ActivityClientRecord 라는 클래스의 parent속성값으로, handleLaunchActivity()의 파라미터를 통해 들어온다. 

하지만 ActivityClientRecord의 Parent를 설정해주는 코드는 startActivityNow() 함수에만 있다. 이는 intent상에서 실행되지 않는 함수이기때문에 parent를 불렀을 때, null이 나왔던 것이다.

그럼 parent는 무엇일까?

getParent()의 설명을 보면 "Return the parent activity if this view is an embedded child."라고 나와있다. 

embedded child라는 것은, 현재 뷰 안에 속해있는 다른 뷰를 말하는 것으로 보인다.
한 액티비티가 다른 액티비티를 호출했을때, Caller를 Parent, Callee를 Child라고 하는 것과는 약간 달라보인다.

ActivityGroup의 설명을 보면 

"A screen that contains and runs multiple embedded activities"라고 되어있는데,

Fragment가 도입되기 전에(API11 에서 추가됨) 한 화면에 뷰를 여러개 띄울 때, 다른 뷰에 속해있는 뷰를 embedded child라고 불렀던 것같다.

 

 

문제상황

안드로이드 7.1이하에서는 알림이 status bar에만 표시되고, head up (pop up) 알림은 안나오는 상황이였다.

원인

AOS 7.1 (SDK25) 이하에서 알림의 중요도는 알림의 priority에 따라 결정된다.

8.0 부터는 channel의 important에 따라 결정된다. 

Priority는 다음 4 종류가 있다.

  • 긴급: 알림음이 울리며 헤드업 알림으로 표시됩니다.
  • 높음: 알림음이 울립니다.
  • 중간: 알림음이 없습니다.
  • 낮음: 알림음이 없고 상태 표시줄에 표시되지 않습니다.

해결

AOS 8.0 미만에서는 채널은 설정할 필요가 없기 때문에 NotificationBuilder에 priority설정을 해주면 된다.

NotificationCompat.Builder(this, CHANNEL_ID)
    .setPriority(NotificationCompat.PRIORITY_HIGH)
    .setDefaults(NotificationCompat.DEFAULT_ALL)

 

8.0 이상에서는 채널에 important 설정을 해주면된다.

val importance = NotificationManager.IMPORTANCE_HIGH
val mChannel = NotificationChannel(CHANNEL_ID, name, importance)

기능

Relative Positioning

 

뷰의 배치 기준은 다음과 같다.

  • Text의 경우 baseline을 사용할 수 있다.

  • left와 start의 차이는 무엇일까?
    • RTL, LTR

ConstraintLayout은 뷰들의 상대적인 위치로 뷰들을 배치한다.

 

  • 안드로이드의 화면은 ConstraintLayout같은 뷰 그룹이 다른 뷰나 뷰 그룹을 가지고 있는 형태로 만들어진다.
    • 가장 바깥쪽의 뷰그룹이 parent 로 지칭되고, 나머지 요소는 id값으로 구분된다.

뷰의 크기 

  • wrap_content
  • match_constraint (0dp)
  • 크기 지정

배치 방법

  • layout_constraintLeft_toLeftOf
  • layout_constraintLeft_toRightOf
  • layout_constraintRight_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintTop_toTopOf
  • layout_constraintTop_toBottomOf
  • layout_constraintBottom_toTopOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintBaseline_toBaselineOf
  • layout_constraintStart_toEndOf
  • layout_constraintStart_toStartOf
  • layout_constraintEnd_toStartOf
  • layout_constraintEnd_toEndOf

Margin

  • Margin은 아는 그대로다.

Gone Margin

안드로이드 ConstraintLayout 개념과 사용법 정복하기 - 개발자 직강 (realm.io)

  • A뷰에 constraint를 같는 B 뷰가 있다. 만약 A뷰의 visiblity가 gone이 되면, B는 어떻게 될까?

Guideline

{"originWidth":1530,"originHeight":798,"style":"alignLeft","width":467,"height":244,"caption":"ConstraintLayout으로 반응형 UI 빌드  

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.55"/>

Bias

app:layout_constraintHorizontal_bias="0.3"
  • 뷰를 치우치게 만들 수 있다.

Ratio

  • 비율을 지정하려면 크기 중 하나를 match_constraint로 지정해야한다

Chains

  • 뷰 간에 상호참조를 할 때 Chain이 생긴다.
  • 가장 왼쪽 혹은 가장 위에 있는 뷰가 ‘헤드’ 뷰이다.

  • 체인 스타일을 지정할 수 있다.

체인을 사용하면, 적절한 배치와 정렬을 할 수 있다.

또한 헤드 뷰를 통해 일관된 속성(마진 등)을 적용할 수 있다.

Depth를 줄이면 좋은 이유

뷰를 그리는 과정

안드로이드에서 뷰가 렌더링되는 과정을 알아보자.

렌더링 프로세스는 3단계로 구성된다.

  1. Measure
    • 뷰 트리를 순회하며 각 ViewGroup과 View의 크기를 결정한다.
    • 뷰그룹의 크기가 측정되면, 자식뷰들도 측정된다.
  2. Layout
    • measure 단계에서 측정된 크기를 사용해서 각 뷰그룹에 있는 자식들의 위치를 결정한다.
    • 이 때도 순회가 일어난다.
  3. Draw
    • 뷰 트리에 있는 각 객체를 Canvas객체로 만들어 GPU에게 그리기 명령을 보낸다.
    • 이전단계에서 구한 크기와 위치가 포함된다.
    • 이 때에도 순회가 일어난다.

각 단계마다 뷰 트리의 순회가 필요하다. 그러므로 뷰가 더 많아지거나, 충첩이 될수록 더 많은 시간과 computation power가 필요하다.

By keeping a flat hierarchy in your Android app layouts, you can create a fast and responsive user interface for your app.

  • DFS 시간복잡도 → O(V+E)?

일반적으로 프레임워크는 단일 패스로 상당히 빠르게 레이아웃 또는 측정 단계를 실행합니다. 그러나 좀 더 복잡한 레이아웃의 경우 프레임워크는 궁극적으로 요소를 배치하기 전에 해결하기 위해 다중 패스가 필요한 계층 구조 부분에서 여러 번 반복해야 할 수 있습니다. 레이아웃 및 측정 반복을 두 번 이상 실행해야 하는 것을 이중 과세라고 합니다.

In Compose

In the View system, was the recommended way to create large and complex layouts, as a flat view hierarchy was better for performance than nested views are. However, this is not a concern in Compose, which is able to efficiently handle deep layout hierarchies.ConstraintLayout

  • View 시스템(xml)에서는 트리구조로 뷰를 관리했지만, Compose에서는 레이아웃이 Flat하게 관리되기 때문에 성능상의 이점이 사라졌다.

성능 및 뷰 계층 구조  |  Android 개발자  |  Android Developers

Understanding the performance benefits of ConstraintLayout

+ Recent posts