구글 플레이 콘솔에 '결제 프로필' 관련 경고가 있었다.

해결 하기 위해 구글측에 문의도 수차례 보내고 해볼 수 있는 건 거의 다 해봤다.

들은 바에 따르면, 문제의 원인은 결제프로필 생성시에 통화를 USD로 설정 한 부분 때문이라고 한다. 그와 관련해서 구글 측 내부 시스템 간에 꼬이는 부분이 있어서 해결할 방법이 없다고 한다.(아직도 문제의 정확한 원인은 모른다. ㅡㅡ;)

누가 결제프로필을 생성했는지, 왜 USD로 설정이 되었는지 등에 대해서는 알 수 있는 방법이 없었다.

개발자 계정과 결제 프로필 생성한 시기가 워낙에 오래 되기도 했고 관리에 대한 정책이나 절차도 없었던 상황이라 그때 그때 상황에 맞게 담당자가 생성하고 설정했을 것이다.

결국 이 문제 해결을 위해 거의 2달을 소비하고 나서야 "앱 이전"을 단행했다.(앱 이전을 더 빨리 결정하고 진행했다면 좋았을거라는 아쉬움도 남는다.)

개발자 계정을 새로 만들고

"앱 이전"을 위해 구글 측의 가이드 대로 준비하고

앱 이전 신청 후 승인 까지 기다렸다가

승인 후에 GCP와 Firebase 이전까지 수행했다.

그리고 중요한 한가지는, 만약 인앱결제 서비스를 하고 있는 앱이라면 앱 이전 후에 앱을 꼭 새로운 버전으로 업데이트 등록 해야 한다.

업데이트 등록하지 않아서 앱이 스토어에서 사라지는 미친 상황을 당할 수 있다.

 

 

구독 정기결제를 구현하는 도중에 라이브러리가 업그레이드 되었다.

즉, 이전에 버전이 4.x.y 에서 5.x.y 가 되었다.

implementation "com.android.billingclient:billing:5.0.0"

이로 인해, deprecated 된 메서드가 많다.

https://developer.android.com/google/play/billing/migrate-gpblv5

 

Google Play 결제 라이브러리 4에서 5로의 이전 가이드  |  Google Play 결제 시스템  |  Android Developers

Google Play 결제 라이브러리 4에서 5로의 이전 가이드 이 주제에서는 Google Play 결제 라이브러리 4에서 Google Play 결제 라이브러리 5로 이전하는 방법과 새로운 정기 결제 기능을 사용하는 방법을 설

developer.android.com

겸사 겸사 현재 개발중인 인앱결제 부분을 마이그레이션 하기로 했다.

기 개발된 코드를 변경하는게 쉽지는 않지만, codeLab과 깃허브에 샘플 코드가 잘 작성되어 있으니 참고해서 개발한다면 큰 무리 없이 개발할 수 있을 것으로 예상된다.

 

코드랩

https://codelabs.developers.google.com/play-billing-codelab?hl=ko#0 

 

Play 결제 라이브러리 5를 사용하여 앱에서 정기 결제 항목 판매  |  Google Codelabs

이 Codelab에서는 기본 요금제 및 탄력 요금제를 비롯하여 자동 갱신되는 정기 결제와 선불 요금제를 구현하는 방법을 알아봅니다.

codelabs.developers.google.com

 

구글 인앱결제 샘플코드

https://github.com/android/play-billing-samples/tree/main/PlayBillingCodelab

 

GitHub - android/play-billing-samples: Samples for Google Play In-app Billing

Samples for Google Play In-app Billing. Contribute to android/play-billing-samples development by creating an account on GitHub.

github.com

 

안드로이드와 iOS에서 설치 및 실행 추적을 위해 다이나믹링크를 사용하기로 결정했다.

특히, 구글의 utm 데이터를 사용해야 하는 요구사항이 있었다.

 

1.  Firebase에서 다이나믹링크 생성하기

  1. Firebase > 참여 > Dynamic Links 페이지로 이동한다.
  2. "새 동적링크" 버튼을 클릭한다.
  3. 동적링크 만들기 페이지로 이동한다.
  4. 필요한 내용들을 차례대로 입력한다.
  5. 입력 항목 중에서 "캠페인 추적, 소셜 태그, 고급 옵션(선택사항)"이 UTM 관련 데이터이다.(아래 그림 참고)

다이나믹 링크 생성

2. 삼성 브라우저에서 다이나믹 링크 클릭시 문제

다이나믹 링크를 클릭하여 앱이 실행되면 utm 값들을 확인할 수 있다. Firebase.dynamicLinks의 PendingDynamicLinkData 객체를 이용하면 된다.

하지만, 삼성브라우저에서 링크를 클릭해서 앱을 실행한 경우에는 utm 데이터가 제대로 수신 되지 않는다.

삼성브라우저 차원에서 다이나믹링크 정보를 전송할때 암호화를 하는지, 아니면 다른 처리를 하는지 정확한 이유는 알 수 없지만, 크롬브라우저를 통해서 전송된 데이터와는 다르게 표현되었다.

 

* 참고로 삼성브라우저의 "설정>유용한 기능"에 들어가면 '다른 앱에서 링크 열기' 항목이 있다. 기본은 비활성 상태인데, 이걸 활성 상태로 변경하면 데이터가 정상적으로 수신된다.

삼성브라우저 설정>유용한 기능

 

3. 해결

해결이라기 보다는 임시방편이 맞겠다.

다이나믹 링크 생성시에 2번 항목에서 입력하는 딥링크 URL에 utm 파라미터를 동일하게 넣어줬다.

 

4. 회고

웹과의 연동을 할때는 각 브라우저마다 테스트가 필요하다.

 

안드로이드 앱에서 구독상품 결제를 구현하면서 결제 상태 변경 시 '실시간 개발자 알림' 구현을 위해 작성하는 가이드이다.

 

Google Play Developer API는 Android에서 Google Play 결제 라이브러리를 보완하는 서버 간 API입니다.
이 API는 구매를 안전하게 인증하고 사용자에게 환불을 처리해 주는 등 Google Play 결제 라이브러리에서는 사용할 수 없는 기능을 제공합니다.

 

먼저, Google Play Developer API 프로젝트를 생성하고 연결한다. (가이드 참고)

  • 소유자 계정으로 Google Play Console에 로그인 한다.
  • 설정 > API 액세스 로 이동한다.

 

실시간 개발자 알림 구성

실시간 개발자 알림(RTDN)은 앱 내에서 사용자의 자격이 변경될 때마다 Google의 알림을 수신하는 메커니즘입니다. RTDN은 
Google Cloud Pub/Sub를 활용합니다. 이를 통해 설정된 URL로 푸시되거나 클라이언트 라이브러리를 사용하여 폴링되는 데이터를 수신할 수 있습니다. 이러한 알림을 사용하면 Google Play Developer API를 폴링할 필요 없이 정기 결제 상태 변경에 즉시 대응할 수 있습니다. Google Play Developer API를 비효율적으로 사용하면 API 할당량 제한이 초래될 수 있습니다.

 

아래에서 GCP의 Pub/Sub을 구성하고 Google Play Console의 앱과 연결하는 방법을 알아본다.

1. Cloud Pub/Sub 설정

GCP의 Pub/Sub 서비스를 생성한다. (Pub/Sub 아키텍처를 모른다면 참고)

계정과 프로젝트는 어떤 것에 해도 상관은 없지만, 가급적이면 Google Play Console의 API 액세스에 연결된 프로젝트에 구성하는게 좋겠다.

https://console.cloud.google.com/cloudpubsub

 

  1. 주제(Topic)를 만든다.
    https://cloud.google.com/pubsub/docs/publish-receive-messages-console?hl=ko#create_a_topic
  2. 주제에 게시 권한을 부여한다.(참고)
    Cloud Pub/Sub를 사용하려면 주제에 알림을 게시하는 권한을 Google Play에 부여해야 한다.
  3. 구독을 생성 및 추가한다.

주제 만들기와 구독 추가, 그리고 실행해 보는 방법 참고 https://cloud.google.com/pubsub/docs/publish-receive-messages-console?hl=ko#create_a_topic

 

2. 앱에 실시간 개발자 알림 사용 설정

    1. Google Play Console을 엽니다.
    2. 앱을 선택합니다.
    3. 수익 창출 > 수익 창출 설정으로 이동합니다.
    4. 페이지 상단의 실시간 개발자 알림 섹션으로 스크롤합니다.

  1. 주제 이름 입력란에 이전에 구성한 전체 Cloud Pub/Sub 주제 이름을 입력합니다. 주제 이름의 형식은 projects/{project_id}/topics/{topic_name}이어야 합니다. 여기에서 project_id는 프로젝트의 고유 식별자이고 topic_name은 이전에 만든 주제의 이름입니다.
  2. 테스트 알림 보내기를 클릭하여 테스트 메시지를 보냅니다. 테스트 게시를 실행하면 모두 제대로 설정되고 구성되었는지 확인할 수 있습니다. 테스트 게시에 성공하면 테스트 게시에 성공했다는 메시지가 표시됩니다. 이 주제의 정기 결제에 연결했다면 테스트 메시지를 받아야 합니다.게시에 실패하면 오류가 표시됩니다. 주제 이름이 올바르고 google-play-developer-notifications@system.gserviceaccount.com 서비스 계정이 주제에 대한 Pub/Sub 게시자 액세스 권한을 보유하고 있는지 확인하세요.
  3. 풀 정기 결제의 경우 Cloud Console에서 정기 결제로 이동하고 메시지 보기를 클릭한 다음 메시지 가져오기를 진행합니다. Cloud Pub/Sub의 반복 전송을 방지하기 위해 가져온 모든 메시지를 확인해야 합니다. 푸시 정기 결제의 경우 테스트 메시지가 푸시 엔드포인트로 전달되었는지 확인합니다. 성공하는 경우 응답 코드가 메시지 확인 역할을 합니다.
  4. 변경사항 저장을 클릭합니다.

 

다음 단계

 

 

[참고] https://developer.android.google.cn/google/play/billing/getting-ready?hl=ko#dev-api

 

준비하기  |  Google Play 결제 시스템  |  Android Developers

참고: 이 주제를 읽기 전에 먼저 Play Console 고객센터 문서를 읽어야 합니다. 문서에서는 중요한 구매 관련 개념과 더불어 판매용 제품을 만들고 구성하는 방법을 설명합니다. 이 주제에서는 앱에

developer.android.google.cn

 

최근 앱 업데이트에서 targetSdkVersion 을 기존에 30에서 31로 올렸다.

플레이스토어 게시하고 하루만에 1만건이 넘는 크래시 리포트가 접수되었다. ㅠ.ㅜ

 

아래는 에러 로그.

Fatal Exception: java.lang.IllegalArgumentException
kr.connect.touch.joins: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.

PendingIntent 인텐트 사용시 플래그에 FLAG_IMMUTABLE 이나 FLAG_MUTABLE 을 사용해야 한다.

 적용하기 위해서는 gradle 파일의 디펜던시에 라이브러리를 추가한다.

implementation 'androidx.work:work-runtime-ktx:2.7.1'

그리고 PendingIntent를 사용하는 부분에서 flag를 수정한다.

val contentIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    PendingIntent.getActivity(
            this,
            statusBarNotifications.hashCode(),
            statusBarNotifications,
            PendingIntent.FLAG_MUTABLE
    )
} else {
    PendingIntent.getActivity(
        this,
        statusBarNotifications.hashCode(),
        statusBarNotifications,
        PendingIntent.FLAG_UPDATE_CURRENT
    )
}

 

저걸로 끝난줄 알았는데...

앱이 백그라운드에 있는 동안 푸시를 수신하면 계속 같은 에러가 발생한다.

 

도무지 어디서 문제인지 찾기가 너무 어렵다.

에러 로그를 한참 째려보고 'firebase-messaging' 라이브러리와 관련한 부분에서 문제가 발생하는것 같다.

그래서 'firebase-bom'의 버전을 업그레이드 한다.(기존엔 26을 사용했다.)

implementation platform('com.google.firebase:firebase-bom:28.0.0')

이제 백그라운드에 있는 동안 푸시를 수신해도 문제가 없다.

 

 

[참고 자료]

https://keelim.tistory.com/entry/안드로이드-SDK-31-대응-사항-중-PendingIntent-에-관하여

 

[안드로이드] SDK 31 대응 사항 중 PendingIntent 에 관하여

java.lang.IllegalArgumentException: com.~~~, ~~~: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Strongly conside..

keelim.tistory.com

https://hckim999.tistory.com/45

 

Target version 31로 올린 후 IllegalArgumentException 오류 수정

Target Version을 31로 업데이트 해 보았다. 그랬더니 아래와 같은 오류가 발생하였다. Fatal Exception: java.lang.IllegalArgumentException: com.example.app: Targeting S+ (version 31 and above) requires t..

hckim999.tistory.com

 

사용중인 개발용 기기에서 우리 앱을 실행하는데,

어느날부터 로그캣에 제목과 같은 에러 메시지가 출력되면서 FCM token 값을 가져오지 못하는 문제가 발생했다.

검색을 해봤지만 들어맞는 해결책을 찾지는 못했다.

 

그러다 우연히 '구글 플레이스토어'를 실행하게 되었는데, 정상적으로 실행이 되지 않았다.

플레이스토어가 실행은 되지만 '새로고침' 버튼만 노출되면서 콘텐츠를 가져오지 못하고 있었다.

이상하다 싶어 이런저런 방법을 써봤지만 문제는 해결되지 않았고

결국 해결 방법을 찾지 못해 디바이스 초기화를 진행했다.

이후 플레이스토어는 정상 실행 되었으며, 우리 앱에서도 FCM token 값을 정상적으로 받아오게 되었다.

나 같은 경우엔 기기에 설치된 구글 플레이스토어의 문제였다.

웹뷰에 '당겨서 새로고침' 기능을 적용 경우,

웹 콘텐츠에 가로 스와이프 기능이 들어가 있으면 문제가 발생하는 경우가 있다.

따라서 세로로 스와이프 할때만 새로고침이 되도록 해야 한다.

 

먼저, SwipeRefreshLayout을 확장해서 OnlyVerticalSwipeRefreshLayout 클래스를 생성한다.

OnlyVerticalSwipeRefreshLayout.kt

open class OnlyVerticalSwipeRefreshLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet?=null) :
    SwipeRefreshLayout(context, attrs) {

    private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
    private var prevX = 0f
    private var declined = false

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                prevX = MotionEvent.obtain(event).x
                declined = false // New action
            }
            MotionEvent.ACTION_MOVE -> {
                val eventX = event.x
                val xDiff = Math.abs(eventX - prevX)
                if (declined || xDiff > touchSlop) {
                    declined = true // Memorize
                    return false
                }
            }
        }
        return super.onInterceptTouchEvent(event)
    }
}

 

사용은 SwipeRefreshLayout을 사용할 때와 동일하다.

<com.ahikuya.OnlyVerticalSwipeRefreshLayout
    android:id="@+id/swipe_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        android:focusable="true"
        android:focusableInTouchMode="true"/>
</com.ahikuya.OnlyVerticalSwipeRefreshLayout>

 

아래 문서를 참고했다.

https://stackoverflow.com/questions/34136178/swiperefreshlayout-blocking-horizontally-scrolled-recyclerview



public class Item{
    public String name,id,phNo,phDisplayName,phType;
    public boolean isChecked =false;
}

private ArrayList<Item> fetchGroups(){
    ArrayList<Item> groupList = new ArrayList<Item>();
    String[] projection = new String[]{ContactsContract.Groups._ID,ContactsContract.Groups.TITLE};
    Cursor cursor = getContentResolver().query(ContactsContract.Groups.CONTENT_URI,
            projection, null, null, null);
    ArrayList<String> groupTitle = new ArrayList<String>();
    while(cursor.moveToNext()){
            Item item = new Item();
            item.id = cursor.getString(cursor.getColumnIndex(ContactsContract.Groups._ID));
            String groupName =      cursor.getString(cursor.getColumnIndex(ContactsContract.Groups.TITLE));
             
         if(groupName.contains("Group:"))
        groupName = groupName.substring(groupName.indexOf("Group:")+"Group:".length()).trim();
             
        if(groupName.contains("Favorite_"))
        groupName = "Favorite";
              
        if(groupName.contains("Starred in Android") || groupName.contains("My Contacts"))
        continue;
             
        if(groupTitle.contains(groupName)){
            for(Item group:groupList){
             if(group.name.equals(groupName)){
                group.id += ","+item.id;
                break;
             }
           }
        }else{
          groupTitle.add(groupName);
          item.name = groupName;
         groupList.add(item);
         }
             
    }
     
    cursor.close();
    Collections.sort(groupList,new Comparator<Item>() {
        public int compare(Item item1, Item item2) {
        return item2.name.compareTo(item1.name)<0
                    ?0:-1;
        }
    });
    return groupList;
}



참고


http://www.101apps.co.za/index.php/articles/using-toolbars-in-your-apps.html


http://pluu.github.io/blog/android-study/2015/05/31/android-design-support-library/


  • Creating Lists and Cards (영문) : 리스트와 카드 위젯을 이용해서 화면 구성을 하는 방법을 설명하였습니다.
  • Android Design Support Library (영문) : 네비게이션 드로어, 플로팅 액션 버튼, 탭과 앱 바의 각종 기능들을 구현하는 도구인 Android Design Support Library를 설명하고 있습니다.
  • Defining Custom Animations (영문) : Shared element transition을 이용해 히어로 전환을 구현 하는 방법을 설명하고 있습니다.


[출처]http://googledevkr.blogspot.kr/2015/06/material.html



http://hmkcode.com/material-design-app-android-design-support-library-appcompat/



TabLayout

http://www.google.com/design/spec/components/tabs.html#tabs-usage

https://guides.codepath.com/android/Google-Play-Style-Tabs-using-TabLayout#sliding-tabs-layout

http://www.android4devs.com/2015/01/how-to-make-material-design-sliding-tabs.html



CoordinatorLayout

http://www.jayrambhia.com/blog/contacts-demo/



RecyclerView

http://www.kmshack.kr/안드로이드-listview에서-recyclerview로-마이그레이션-하기/


http://www.kmshack.kr/안드로이드-머티리얼-디자인-위젯-오픈소스-21가지-2/

'개발 > 안드로이드' 카테고리의 다른 글

그룹의 전화번호 가져오기  (0) 2016.03.17
Toolbar  (0) 2015.07.07
디버그용 키 만들기  (0) 2015.06.26
webvew 에서 브라우저로 링크(_blank)  (0) 2015.06.24
Animation  (0) 2015.06.19

+ Recent posts