문제점 : 뎁스가 깊어질때 CustomVideoView가 가지고 있는 mediaplayer 메모리가 제대로 해제 되지 않는 문제

해결 : life cycle 사용해서 Stop 시 mediaplayer release 처리

VideoView 는 LifecycleObserver 인터페이스를 가지고 있다. 우선 뷰 생성시 라이프 사이클 옵저버 등록

     public VideoView(Context context) {
        super(context);
        initialize(context);
    }

    private void initialize(Context context) {
        if (context instanceof AppCompatActivity) {
            ((AppCompatActivity) context).getLifecycle().addObserver(this);
        }
    }

@OnLifecycleEvent 종류에 따라 life cycle 콜백이 불리게 되는데 stop 되었을때 release destroy 될때 등록되어 있던 observer를 제거 시켜준다.

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    public void onLifecycleStop() {
        stop();
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onLifecycleDestroy() {
        if (getContext() != null) {
            if (getContext() instanceof AppCompatActivity) {
                ((AppCompatActivity) getContext()).getLifecycle().removeObserver(this);
            }
        }
    }

사용법 참 쉽구먼 ㅎㅎ

Window 사용할때는 이런일 없었는데 맥북 쓰면서 SourceTree 에서 계정 입력 요청이 계속 떠서 구글링 해서 해결 방법을 찾았다.

git config credential.helper store    

입력해주면 해결완료! 그다음부터는 계정 요청을 하지않는다.

ConstraintLayout 의 강점을 살려보자.

초록색 링이 꺼졌다 켜졌다 해야할 때 layout 어떻게 배치 할까!?

보통 디자이너 분들은 해당 다운로드 이미지, 공유 이미지,초록색 링 이미지 이렇게 주신다. RelativeLayout을 사용하게 되면 만들기가 쉽지 않다. 기존에 RelativeLayout을 사용했을 때는 다운로드 이미지, 공유 이미지, 초록색 링이 포함된 다운로드 이미지 , 초록색 링이 포함된 공유 이미지 이렇게 받아서 작업을했다. 하지만 ConstraintLayout 을 사용했을 때 이런 문제점을 해결할 수 있었다. 물론 RelativeLayout 으로도 아마 가능할수도 있을지 모르지만 쉽게 떠오르지 않았다.

여기서 키워드는 GuideLine 과 ConstraintGroup 이다.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true">

    <View
        android:id="@+id/bottom_background"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:background="@color/take_bottom_black"
        app:layout_constraintBottom_toBottomOf="parent" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline_bottom_center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:orientation="horizontal"
        app:layout_constraintGuide_end="40dp" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline_share"
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.33333" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline_down"
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.666666"/>

    <ImageView
        android:id="@+id/share_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/take_btn_finish_share"
        app:layout_constraintBottom_toBottomOf="@id/guideline_bottom_center_horizontal"
        app:layout_constraintEnd_toEndOf="@+id/guideline_share"
        app:layout_constraintStart_toStartOf="@+id/guideline_share"
        app:layout_constraintTop_toTopOf="@id/guideline_bottom_center_horizontal" />

    <ImageView
        android:id="@+id/down_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/take_btn_finish_save"
        app:layout_constraintBottom_toBottomOf="@id/guideline_bottom_center_horizontal"
        app:layout_constraintEnd_toEndOf="@+id/guideline_down"
        app:layout_constraintStart_toStartOf="@id/guideline_down"
        app:layout_constraintTop_toTopOf="@id/guideline_bottom_center_horizontal" />


    <ImageView
        android:id="@+id/image_share_guide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/quick_round_03"
        app:layout_constraintBottom_toBottomOf="@+id/guideline_bottom_center_horizontal"
        app:layout_constraintEnd_toEndOf="@+id/guideline_share"
        app:layout_constraintStart_toStartOf="@id/guideline_share"
        app:layout_constraintTop_toTopOf="@+id/guideline_bottom_center_horizontal" />

    <ImageView
        android:id="@+id/image_download_guide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/quick_round_03"
        app:layout_constraintBottom_toBottomOf="@+id/guideline_bottom_center_horizontal"
        app:layout_constraintEnd_toEndOf="@+id/guideline_down"
        app:layout_constraintStart_toStartOf="@id/guideline_down"
        app:layout_constraintTop_toTopOf="@+id/guideline_bottom_center_horizontal" />

    <android.support.constraint.Group
        android:id="@+id/group_quick_guide"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        app:constraint_referenced_ids="image_share_guide,image_download_guide" />

</android.support.constraint.ConstraintLayout>

해당 이미지의 Layout 이다. 가로 세로의 각각 GuideLine 을 설정 한 후 각 뷰는 부모의 뷰에서 몇 dp 만큼 떨어진게 아니라 기준점이 GuideLine 이 되는 것이다. 쉽게 만들수 있다. 그리고 ConstraintGroup을 만들어서 referenceIds 에 뷰들이 GONE처리 되어야 하는 아이디들을 넣어 주면 된다. group_quick_guide 를 찾아서 Visible Gone 처리를 해주면 참조한 뷰들이 모두 적용된다. 매우 편리하다!!ㅎㅎ

문제점 : 다국어 지원 앱 다국어 지원을 위한 라이브러리를 사용해서 앱을 실행할 때 다국어 지원 관련 셋팅을 하게 된다. 간체,번체의 경우 language 와 country 두가지 모두 사용해서 구분을 하게 된다. 하지만 지역별로 이벤트를 설정 할 경우 locale 지역 정보를 가져올때 시스템에 설정된 지역 정보가 아닌 앱에서 설정된 지역을 가져오기때문에 문제

해결 : Application Class 에서 다국어 관련 Locale 을 셋팅 하기 전 country 정보를 미리 SharedPreference 에 저장해 놓고 필요한 경우 가져 온다. 시스템 언어를 변경 할 경우 onConfigurationChanged 가 불리게 되어서 여기서 다시 지역 정보를 갱신 시켜 주어야 한다.

    Application Class

    @Override
    public void onCreate() {
        super.onCreate();
        PreferenceManager.getDefaultSharedPreferences(this)
        .edit()
        .putString(getString(R.string.pref_key_system_country), Locale.getDefault().getCountry())
        .apply();
        LocaleChanger.initialize(getApplicationContext(), SUPPORTED_LOCALES);
    }

      @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.getLocales().get(0) != null) { //첫번째가 변경된 Locale 정보를 가진다.
            PreferenceManager.getDefaultSharedPreferences(this).edit().putString(getString(R.string.pref_key_system_country), newConfig.getLocales().get(0).getCountry()).apply();
        }
        LocaleChanger.onConfigurationChanged();
    }

안드로이드 푸시 팝업

순서

  1. NotificationManager 얻어옴
  2. 오레오 버전 이상이면 Channel 생성 아니면 패스
  3. PendingIntent생성
  4. NotificationCompat 생성
  5. 빌드한 Notification을 notificationManager에 붙여주면 끝!
  6. Notification 클릭 시 핸들

1.NotificationManager 얻어옴

val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

2.오레오 버전 이상이면 Channel 생성 아니면 패스

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channel = NotificationChannel(getString(R.string.app_name) + getString(R.string.priority_high), getString(R.string.app_name) + getString(R.string.priority_high), NotificationManager.IMPORTANCE_HIGH)
                channel.description = getString(R.string.app_name) + getString(R.string.priority_high)
                channel.enableLights(true)
                channel.lightColor = Color.BLUE
                channel.enableVibration(true)
                channel.vibrationPattern = longArrayOf(100, 200, 100, 200)
                channel.lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
                notificationManager.createNotificationChannel(channel)
            }

Channel 생성 시 IMPORTANCE 설정에 따라 알림이 다르다. NotificationManager.IMPORTANCE_HIGH :소리 및 팝업 (헤드업 알림) NotificationManager.IMPORTANCE_DEFAULT : 소리 NotificationManager.IMPORTANCE_LOW : 소리 없음
NotificationManager.IMPORTANCE_MIN : 소리 및 시각적 알림 없음

3.PendingIntent생성

   val intent = Intent(applicationContext, MainActivity::class.java)
            remoteMessage.data?.apply {
                intent.putExtra(JSON_LETTER, get(JSON_LETTER))
                intent.putExtra(JSON_ANSWER, get(JSON_ANSWER))
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
            }
   val pendingIntent = PendingIntent.getActivity(applicationContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)

알림 팝업을 누르게 되면 PendingIntent 에 실어 두었던 Intent 정보를 가져와서 사용할 수 있다.

4.NotificationCompat 생성

   val builder = NotificationCompat.Builder(applicationContext, getString(R.string.app_name) + getString(R.string.priority_high))
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setContentTitle(title)
                    .setContentText(body)
                    .setDefaults(Notification.DEFAULT_VIBRATE)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setAutoCancel(true)
                    .setContentIntent(pendingIntent)
                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

5.빌드한 Notification을 notificationManager에 붙여주면 끝!

notificationManager.notify(1, builder.build())

6.Notification 클릭 시 핸들

  override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.apply {
            if (intent.extras.getString(JSON_ANSWER) != null) {
                val jsonLetter = intent.extras.getString(JSON_LETTER)
                val jsonAnswer = intent.extras.getString(JSON_ANSWER, EMPTY)
                if (supportFragmentManager.backStackEntryCount > 0) {
                    if (jsonLetter == null) {
                        val answer = Gson().fromJson(jsonAnswer, Answer::class.java)
                        openReAnswers(answer)
                    } else {
                        openReAnswers(jsonLetter, jsonAnswer)
                    }
                    return
                } else {
                    openHome()
                    if (jsonLetter == null) {
                        val answer = Gson().fromJson(jsonAnswer, Answer::class.java)
                        openReAnswers(answer)
                    } else {
                        openReAnswers(jsonLetter, jsonAnswer)
                    }
                    return
                }
            }
        }
    }

Notification 클릭 시 onNewIntent 메소드 호출 되는데 여기서 intent에 넣어 두었던 데이터를 사용하면 된다.