[Android] Notification RemoteView 억까 해결기, has too high data size

곽의진·2024년 7월 6일
2

Android

목록 보기
16/17
post-thumbnail

Notification RemoteView란?

안드로이드 앱 개발 시 알림(Notification)을 커스터마이징하기 위해 RemoteViews를 사용하게 됩니다.

커스텀 알림 공식 문서 링크

이 RemoteView라는 건 쓸 수 있는 View 종류도 적으면서 툭하면 크래시 내뿜으면서 뻗는 개복치 같은 레이아웃입니다.

RemoteViews 객체를 재사용할 경우 데이터 크기가 점점 증가하여 알림 서비스가 오류를 일으킬 수 있는데요.

오늘은 RemoteViews 객체 재사용 시 데이터 크기 증가 문제의 원인과 해결 방법을 설명해보겠습니다.

문제 발생 상황

문제가 된 코드

앱 개발 중, Foreground service와 RemoteView로 이루어진 Notification 생성했습니다.

이를 updateProgress 함수를 통해 1초마다 프로그레스바를 업데이트하여 진행도를 유저에게 알려주는 로직이 포함되어있습니다.

private suspend fun updateProgress(
    remoteViews: RemoteViews,
    expandedRemoteViews: RemoteViews,
    eventData: EventData,
) {
    withContext(Dispatchers.Main) {
        val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
        val currentEventData = eventData.current

        val progress = calculateProgress(now, currentEventData)

        expandedRemoteViews.setProgressBar(R.id.progressBar, 100, progress, false)
        val updateNotification = buildNotification(remoteViews, expandedRemoteViews, eventData)
        NotificationManagerCompat.from(context).notify(1, updateNotification)
    }
}
. . .


fun buildNotification(. . .) {
    NotificationCompat.Builder(. . .)
    . . .
    .setCustomContentView(remoteViews)
    .setCustomBigContentView(expandedRemoteViews)
    .build()
}

에러 로그

이 타이머를 돌리던 중 알림 데이터 크기 초과 오류가 발생했습니다. 로그는 다음과 같았습니다:

2024-07-06 19:01:43.027  2535-13342 NotificationService     system_server E  notification pkg : app.xxx.yyy has too high data size(179500) above 100000
2024-07-06 19:01:43.027  2535-13342 NotificationService     system_server E  notification key : 0|app.xxx.yyy.android|1|null|11031 has too high data size(179500) above 100000

이는 RemoteViews 객체가 데이터 크기 제한(100000)을 초과했음을 나타냅니다.

원인 분석

이런 어이없는 이슈가 발생하는 이유가 궁금하여 RemoteView가 뷰 객체에 데이터를 set할 때 어떤 로직이 내부에서 동작되는지 확인해보았습니다.

RemoteViews의 데이터 크기가 증가하는 이유

RemoteViews 객체를 재사용하면 데이터 크기가 증가하는 이유는 RemoteViews가 내부적으로 변경 사항을 누적하여 저장하기 때문입니다.

RemoteViews는 안드로이드의 View 시스템에서 원격으로 UI를 업데이트할 수 있는 방법을 제공하며, 이 과정에서 변경 사항을 명령(Command) 객체로 저장하고 이를 누적하여 처리합니다.

RemoteViews의 내부 동작

  1. 명령 객체(RemoteViews.Action): RemoteViews는 변경 사항을 추적하기 위해 각 변경 사항을 Action 객체로 저장합니다.

예를 들어, 제가 위에서 사용한 setProgressBar라는 Progress를 업데이트하는 명령이 있을 수 있습니다.

이러한 명령 객체는 ReflectionAction 클래스를 통해 RemoteViews.Action 타입으로 구현됩니다.


    public void setProgressBar(@IdRes int viewId, int max, int progress,
            boolean indeterminate) {
        setBoolean(viewId, "setIndeterminate", indeterminate);
        if (!indeterminate) {
            setInt(viewId, "setMax", max);
            setInt(viewId, "setProgress", progress);
        }
    }
    
    public void setBoolean(@IdRes int viewId, String methodName, boolean value) {
        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BOOLEAN, value));
    }
    
    public void setInt(@IdRes int viewId, String methodName, int value) {
        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INT, value));
    }
    
    private void addAction(Action a) {
        if (hasMultipleLayouts()) {
            throw new RuntimeException("RemoteViews specifying separate layouts for orientation"
                    + " or size cannot be modified. Instead, fully configure each layouts"
                    + " individually before constructing the combined layout.");
        }
        if (mActions == null) {
            mActions = new ArrayList<>();
        }
        mActions.add(a);
    }
    
  1. 명령 리스트 유지: RemoteViews 객체는 이러한 명령 객체들의 리스트를 유지합니다. 이 리스트가 커지면 RemoteViews 객체의 전체 크기도 커지게 됩니다.
  1. 명령 실행: RemoteViews 객체는 필요할 때 이 명령 리스트를 순차적으로 실행하여 UI를 업데이트합니다. 이는 apply 메서드를 통해 이루어집니다.
    // 내부에 더 많은 로직이 있지만 이해를 위해 요약하여 가져온 로직입니다.
    
        @Override
        public void apply(Context context, ViewGroup parent, OnClickHandler handler) {
            for (Action a : mActions) {
                a.apply(view, parent, handler);
            }
        }
    }
    

이와 같이 명령 객체가 누적되면서 RemoteViews 객체의 크기가 증가하고, 데이터 크기 제한을 초과하게 됩니다.

해결 방법

매번 새로운 RemoteViews 객체를 생성하여 사용하면, 변경 사항이 누적되지 않아 데이터 크기 증가 문제를 해결할 수 있습니다.

문제 해결을 위한 코드 변경

다음은 기존 코드를 수정하여 매번 새로운 RemoteViews 객체를 생성하는 방식으로 변경한 예시입니다:

private suspend fun updateProgress(eventData: EventData) {
    withContext(Dispatchers.Main) {
        val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
        val currentEventData = eventData.current
        val progress = calculateProgress(now, currentEventData)
        
        // RemoteView 객체 생성
        val remoteViews =
            createRemoteViews(R.layout.view_foreground_notification_layout, eventData)
        val expandedRemoteViews =
            createRemoteViews(R.layout.view_foreground_notification_expended_layout, eventData)
        expandedRemoteViews.setProgressBar(R.id.progressBar, 100, progress, false)
        val updateNotification = buildNotification(remoteViews, expandedRemoteViews, eventData)
        NotificationManagerCompat.from(context).notify(1, updateNotification)
    }
}
. . .
private fun createRemoteViews(
    layoutId: Int,
    eventData: EventData
): RemoteViews {
    val remoteViews = RemoteViews(context.packageName, layoutId)
    updateRemoteViews(remoteViews, eventData)
    return remoteViews
}

이제 RemoteViews 객체를 매번 새로 생성하므로, 변경 사항이 누적되는 문제를 방지할 수 있습니다.

결론

RemoteViews 객체를 재사용하면 내부적으로 변경들의 명령 객체들이 누적되어 데이터 크기가 증가할 수 있습니다 🔥

이를 해결하기 위해 매번 새로운 RemoteViews 객체를 생성하여 사용하는 것이 좋습니다ㅋㅋ

아니 객체를 생성하는게 비효율적이라고 생각해서 재활용했더니 더 큰 문제가 발생하네요...

💡 역시 제가 만든 클래스가 아니라면 의도한대로 동작할거라고 지레짐작 하는 것은 위험하다는 것을 오늘 또 깨달았습니다

쨋든 시원한 결말은 아닌 것 같지만, 이렇게 하면 알림 데이터 크기 초과 문제를 해결할 수 있으며, 안정적인 알림 서비스를 제공할 수 있습니다.

감사합니다.

profile
Android Developer

0개의 댓글