** 방법

그냥 한 파일에 같이 적으면 됨.





이게 뭐라고 나는 고민을 했던 걸까...


** 사용 이유

기존에 테스트 서버와 연결되는 앱과 실제 서비스 서버와 연결되는 앱이 따로 설치되기를 희망하였음.

이번에 마이그레이션 작업 진행하며 기존 앱과 따로 설치하여 테스트하기를 희망함.

(기존 앱은 이클립스로 작업)




** 방법

1. 해당 모듈의 build.gradle에 아래와 같은 형식으로 추가

- 나는 마이그레이션/DEV/SERVICE로 나누었음.


flavorDimensions "mode"

    productFlavors {

        migration {

            dimension "mode"

            applicationIdSuffix ".migration"

            buildConfigField 'boolean', 'isTestMode', "true"

        }

//        dev {

//            dimension "mode"

//            applicationIdSuffix ".dev"

//            buildConfigField 'boolean', 'isTestMode', "true"

//        }


        service {

            dimension "mode"

            applicationIdSuffix ""

            buildConfigField 'boolean', 'isTestMode', "false"

        }

    }


flavorDimensions - 무엇인지는 모르겠지만.. flavor 설정 시 모든 항목이 같은 flavorDimensions에 속해야 한다는 에러메세지가 떠서 추가


migration/dev/service - 그냥 자기가 마음대로 설정 가능. 이후 폴더를 생성할 때 동일한 이름으로 생성해야 한다. (대문자가 되는지는 아직 확인하지는 못했다.) 이 때 Flavor가 하나 이상 등록되어있으면 나중에 APK 뽑을 때 Flavor를 무조건 선택해야되서 service를 추가하였음.


applicationIdSuffix - 식별자를 정하는 것 같음. Suffix 설정 시 applicationId 뒤에 붙여지게 된다. 예를 들어 마이그레이션용 앱의 applicationId는 com.example.flavortest.migration 이 되는 식. 나중에 google-services.json 복사 시 문서 내의 applicationID와 동일하게 적어야 한다. service는 기본 설정과 동일하게 해 주기 위해서 빈 값으로 설정하였다.


buildConfigField - 설정하게 되면 BuildConfig 내부에 static 변수로 등록이 되서 소스 코드 내에서 사용이 가능하다. 예를 들어 migration과 dev의 앱에서 BuildConfig.isTestMode는 true지만 service에서는 false가 된다.




2. 각 flavor 별로 폴더 추가



위에서 만든 flavor별로 폴더를 만들고, 내부에 google-services.json을 복사해서 넣는다.

(이제와서 보니 service 폴더랑 main 안의 google-services.json 파일은 없어도 될듯..)


각 flavor별로 리소스 관리를 할 수 있다.

따로 설정하지 않으면 기본(main)폴더 내의 리소스를 사용하는 듯.



이런식으로 앱 아이콘을 따로하고 싶을 때 사용한다. (우측은 플레이 스토어에 등록된 앱, 좌측은 migration flavor로 설치한 앱)




3. google-services.json 수정


google-services.json 파일은 



이렇게 package_name 부분만 변경해주면 된다.


gradle의 applicationId에 위에서 추가했던 applicationIdSuffix 를 더한 문자열을 쓰면 됨.

예를 들어 migration 폴더의 package_name은 "com.example.flavortest.migration"로 변경하면 됨.






4. Manifest 수정(FileProvider 사용 시)


마지막으로 혹시 FileProvider을 정의해서 사용한다면, 혹은 Manifest 파일에서 android:authorities 속성을 사용한다면 android:authorities 속성의 값을 변경해야 한다.

(이렇게 안 하면 앱이 동시에 설치가 안 된다.)


android:authorities="${applicationId}.fileprovider"



이와 같이 applicationId를 변수로 가져오게 수정하여야 한다. 





5. 빌드 시 flavor 선택하기





Build Variants 메뉴를 통해 Run 및 Instant Run 시 어떤 flavor로 실행할 지 선택할 수 있다.







APK 추출 시에는 Flavors 부분에서 선택할 수 있다.

(그런데 Flavors가 하나 이상 등록되어있을 때 선택 해제가 안 된다. 그래서 service Flavor를 따로 만들었음.)








** 참고

http://gun0912.tistory.com/74

https://stackoverflow.com/questions/16267785/install-shows-error-in-console-install-failed-conflicting-provider 

https://medium.com/@iammert/android-product-flavors-1ef276b2bbc1

항상 최상단에 위치하는 뷰 구현 시 문제가 발생한다.


1. '다른 앱 위에 그리기' 권한을 허용해 준다.


if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){

    Log.i("TEST", "Permission Granted ? " + Settings.canDrawOverlays(context));

    //M 이상에서만 퍼미션 확인(그 이하에서는 자동으로 허용됨)

//다른 앱 위에서 그리기 권한에 대한 허용 여부 체크

    if(Settings.canDrawOverlays(context)) {

    //이미 권한 설정 되어있음

        context.startService(new Intent(context, XXSerivce.class));

    }else{

        //권한 없음        

        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));

        context.startActivity(intent);    //startActivityForResult로 대체 가능

    }

}




2. 서비스에서 addView를 할 때 버전에 따라 Flag를 다르게 설정


int LAYOUT_FLAG;

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

}else{

    LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;

}

WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(

  가로 크기,

세로 크기,

LAYOUT_FLAG,

WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,

PixelFormat.TRANSLUCENT);

mWindowManager.addView(표시할 뷰, mParams);




** 참고

https://stackoverflow.com/questions/46208897/android-permission-denied-for-window-type-2038-using-type-application-overlay

RetroFit2 라이브러리 연습한 내용을 정리한 포스팅입니다.

POST방식으로, List가 포함되지 않은 단일 데이터로 이루어진 데이터를 가져오는 예제입니다.




1. Dependencies 추가


implementation 'com.squareup.retrofit2:retrofit:2.4.0'

implementation 'com.google.code.gson:gson:2.8.5'

implementation 'com.squareup.retrofit2:converter-gson:2.4.0'


Project Structure에서 추가하면 편함.




2. 데이터 모델 클래스 생성

public class Model {

    @SerializedName(키1)

    String 변수1;

    @SerializedName(키2)

    String 변수2;


    public String toString() {

        return 변수1 + " / " + 변수2;

    }

}


@SerializedName(문자열)

문자열 = JSONObject의 key 값

나중에 GsonConverterFactory에서, 해당 문자열의 키를 가진 데이터를 해당 변수에 넣음.


예를 들어 


@SerializedName("result")

String res;


가 의미하는 것은

결과로 받는 JSONObject에서 키 값이 result인 데이터를 res 안에 넣어준다는 것 같다.


키를 못 찾거나 하는 경우엔 기본 값을 넣는 것 같기도..? (String은 null, int는 0으로)





3. Interface 생성

public interface RetroFitInterface {

    @FormUrlEncoded

    @POST(URL)

    Call<Model> 메소드 (@Field(파라미터이름) 데이터타입 변수명@Field(파라미터이름2) 데이터타입 변수명 ... );

}


@FormUrlEncoded - POST 방식 사용 시 입력해야함

@POST - POST 방식을 사용한다는 의미. 괄호 안에는 연결할 페이지 URL 주소를 입력하면 된다.

나중에 BaseDomain을 입력한다면, 이곳에 입력하는 주소는 BaseDomain 이하의 주소를 입력하면 된다.

Call<Model> XX (@Field()) - 파라미터 값을 함께 보내는 경우에 사용. 뒤의 변수명은 파라미터 값 입력 시 유추할 수 있게 적당하게 넣으면 됨. 

ex) Call<Model> test (@Field("MODE") String mode)

※ String 말고 int 형이나 boolean도 자동으로 변환이 되는건지는 내일 실험해 볼 것.




4. 사용(3번과 함께 보면 좋음)

Retrofit retrofit = new Retrofit.Builder()

        .baseUrl(기본 도메인 주소)

        .addConverterFactory(GsonConverterFactory.create())

        .build();


RetroFitInterface callback = retrofit.create(RetroFitInterface.class);

Call<Model> call = callback.메소드(파라미터1, 파라미터2);


call.enqueue(new Callback<Model>() {

    @Override

    public void onResponse(Call<Model> call, Response<Model> response) {

            textView.setText("응답코드 > " + response.code() + " \n데이터 > " + response.body().toString());

    }


    @Override

    public void onFailure(Call<Model> call, Throwable t) {

        textView.setText("통신 실패");

    }

});



baseUrl(도메인주소) - 통신하려는 페이지 url이 "http://abc.com/topic/" 이라면 "http://abc.com/"이 baseUrl이다. (슬래시는 오류 방지를 위하여 추가함)

enqueue - 비동기식 통신을 할 때 사용

execute - 동기식 통신을 할 때 사용


onResponse

 - 통신 성공 시 실행된다.

 - 통신이 실패하는 경우, JSON의 파싱에 실패하는 경우에도 호출될 수 있으니, response의 code() 값과 결과값을 확인 후 처리하여야 함.

 - 데이터를 가져와서 사용할 때는 response.body() 값을 가져와서 사용.


onFailure

 - 완전한 통신 실패 시 실행


onResponse와 onFailure 둘 다 메인Thread에서 실행되므로 별도로 runOnUIThread 메소드 내부에 넣지 않아도 됨.




** 참고 사이트

http://newland435.tistory.com/25

http://yoo-hyeok.tistory.com/79

http://nobase-dev.tistory.com/6

Glide.hwp


블로그에 옮기는 건 이다음으로..

+ Recent posts