2018년 11월 24일 토요일

Gradle 의존성 옵션 & api 와 implementation 차이점

의존성 옵션


Gradle 프로젝트는 여러 모듈을 포함할 수 있고 하나의 모듈은 다른 모듈을 의존할 수 있다
그리고 의존하고 있는 모듈이 또다른 모듈을 의존할 수 있다

모듈이 커지면 빌드 시간이 오래 걸리고 여러 모듈이 많이 얽혀 있을 수록 빌드 시간이 오래 걸린다


compile 옵션 사용시 warning 문구

Gradle 3.0 부터 의존 라이브러리 수정 시 재빌드가 필요한 라이브러리를 선택적으로 할 수 있도록 compile 대신 api 와 implementation 으로 나눠 필요없는 경우 재빌드 하지 않도록 함

Gradle 버전 호환을 위해 compile 은 api 와 동일한 동작을 함
  • 2018년 까지만 지원할 것으로 보임

api 와 implementation 차이점



참고

차이점
  • api: 의존 라이브러리 수정시 본 모듈을 의존하고 있는 모듈들 또한 재빌드
    • A(api) <- B <- C 의 경우 C 에서 A 를 접근할 수 있음
    • A 수정시 B 와 C 모두 재빌드
  • implementaion: 의존 라이브러리 수정시 본 모듈까지만 재빌드
    • A(implementation) <- B <- C 의 경우 C 에서 A 를 접근할 수 없음
    • A 수정시 B 까지 재빌드

의존성 옵션들


  • implementation: 의존 라이브러리 수정시 본 모듈까지만 재빌드
    • 본 모듈을 의존하는 모듈은 해당 라이브러리의 api 를 사용할 수 없음
  • api: 의존 라이브러리 수정시 본 모듈을 의존하는 모듈들도 재빌드
    • 본 모듈을 의존하는 모듈들도 해당 라이브러리의 api 를 사용할 수 있음
  • compileOnly: compile 시에만 빌드하고 빌드 결과물에는 포함하지 않음
  • runtimeOnly: runtime 시에만 필요한 라이브러리인 경우
  • annotationProcessor: annotation processor 명시 (gradle 4.6)

특정 build flavor 에서만 동작


예) 무료 버전 빌드라는 의미로 free 라는 flavor 를 미리 작성한 경우
freeImplementation 으로 지정하면 free flavor 일때만 의존성 빌드

testImplementation 은 test 시에만 의존되며 본 모듈을 의존하는 다른 모듈은 빌드에 영향이 없게 함


계속해서 warning 이 뜨는 경우


자신의 프로젝트에 모든 compile 옵션을 제거했는데도 warning 이 뜨는 경우
종속 라이브러리 버전을 올리면 warning 이 사라질 수 있음

참고:


결론


주로 외부 라이브러리를 사용하는 경우 api 나 implementation 나 별 차이 없을 것으로 보임


2018년 3월 11일 일요일

ListView 의 footer view 가 보이지 않는 문제

AddFooterView() 함수를 사용해서 footer view 를 추가했지만 화면에 표시가 되지 않았다

AddFooterView() 함수를 ListView 에 adapter 를 설정하기 전에 불러주어야 했다

2017년 7월 4일 화요일

Service AIDL


다른 앱들간 통신하기 위한 여러 방법이 있지만 method 를 직접적으로 호출하고 바로 리턴 받을 수 있는 AIDL 구현 예제 설명

용어


Bind

서비스와 통신하기 위해 연결과정을 binding 이라고 함
bindService() 로 실행된 서비스는 unbindService() 될 때 종료됨

AIDL (Android Interface Definition Language)

Service 와 client 간 통신 규약 파일이며 service 측과 client 측이 통신하기 위해 동일한 aidl 파일을 공유해야 함
예를 들어 service 와 client 가 다른 프로젝트라면 aidl 을 같은 package 위치에 복사해야 하며 변경 사항 발생시 공유 해야 함

AIDL 예제

1. Service (com.sample.myservice)


com.sample.myservice.IMyService.aidl 작성


// IMyServiceInterface.aidl
package com.sample.myservice;

// Declare any non-default types here with import statements

interface IMyServiceInterface {

    String greet();
}


  • greet() 함수 제공


com.sample.myservice.MyService.java 작성



package com.sample.myservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

public class MyService extends Service {
    public MyService() {
    }

    private IMyServiceInterface.Stub binder = new IMyServiceInterface.Stub() {
        @Override
        public String greet() throws RemoteException {
            return "Hello";
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}


  • aidl 파일 작성 후 Android Studio 의 build -> make project 를 해야 aidl 을 자동 인식
  • IMyServiceInterface.Stub 을 구현
  • onBind() 시 구현한 IMyServiceInterface.Stub 리턴

2. Client 예제 (com.sample.myapp 다른 프로젝트)


IMyService.aidl 복사



  • 위에 생성한 aidl 을 복사


com.sample.myapp.MainActivity.java 생성


package com.sample.myapp;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.sample.myservice.IMyServiceInterface;

public class MainActivity extends AppCompatActivity {

    private final static String TAG = "MainActivity";

    private IMyServiceInterface myservice;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            myservice = IMyServiceInterface.Stub.asInterface(iBinder);
            try {
                Log.d(TAG, myservice.greet());
            } catch (RemoteException e) {
                Log.e(TAG, "exception", e);
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            myservice = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent();
        intent.setClassName("com.sample.myservice", "com.sample.myservice.MyService");
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}


  • 마찬가지로 make project 를 해야 Anroid Studio 가 aidl 파일을 자동 인식
  • 외부 service 이기 때문에 intent 에 setClassName() 으로 서비스 지정
    • Service 의 package 명과 Service 의 full package 명이 파라메터
  • ServiceConnection 의 onServiceConnected() 와 onSerivceDisconnected() 처리
    • IMyServiceInterface.Stub.asInterface() 로 따로 casting 필요없음
  • aidl 구현 함수들은 RemoteException 을 throw 할 수 있음
    • 연결 해제된 경우 등
  • onCreate() 에서 bindService() 했으니 onDestroy() 시 unbindService()

Callback 예제


Service -> Client 방향으로 통신할 수 있도록 callback 을 등록

1. Service 수정


com.sample.myservice.IMyCallbackInterface.aidl 생성


// IMyCallbackInterface.aidl
package com.sample.myservice;

// Declare any non-default types here with import statements

interface IMyCallbackInterface {
    void onUpdate(int value);
}

  • Service 에서 호출할 onUpdate() 함수 제공

com.sample.myservice.IMyServiceInterface 수정


// IMyServiceInterface.aidl
package com.sample.myservice;

// Declare any non-default types here with import statements
import com.sample.myservice.IMyCallbackInterface;

interface IMyServiceInterface {

    String greet();
    boolean registerCallback(IMyCallbackInterface callback);
    boolean unregisterCallback(IMyCallbackInterface callback);
}


  • registerCallback(), unregisterCallback() 추가

com.sample.service.MyService.java 수정


package com.sample.myservice;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

public class MyService extends Service {

    private final static String TAG = "MyService";
    private RemoteCallbackList<IMyCallbackInterface> callbackList = new RemoteCallbackList<>();
    private int value;

    public MyService() {
    }

    private IMyServiceInterface.Stub binder = new IMyServiceInterface.Stub() {

        @Override
        public String greet() throws RemoteException {
            return "Hello";
        }

        @Override
        public boolean registerCallback(IMyCallbackInterface callback) throws RemoteException {
            return callbackList.register(callback);
        }

        @Override
        public boolean unregisterCallback(IMyCallbackInterface callback) throws RemoteException {
            return callbackList.unregister(callback);
        }

    };

    private final static int MSG_BROADCAST = 0;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_BROADCAST:
                {
                    value++;
                    int size = callbackList.beginBroadcast();
                    for (int i = 0; i < size; i++) {
                        try {
                            callbackList.getBroadcastItem(i).onUpdate(value);
                        } catch (RemoteException e) {
                            Log.e(TAG, "exception", e);
                        }
                    }
                    callbackList.finishBroadcast();
                    sendEmptyMessageDelayed(MSG_BROADCAST, 1000);
                }
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        handler.sendEmptyMessageDelayed(MSG_BROADCAST, 1000);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
}


  • android.os.RemoteCallbackList 사용
    • broadcast
      • beginBroadcast() 로 등록된 callback 통신 시작
        • 등록된 callback 객수 리턴
      • getBroadcastItem() 로 등록된 callback 접근
      • finishBroadcast() 로 callback 통신 작업 종료
    • register/unregister


2. Client 수정


com.sample.myapp.MainActivity.java 수정



package com.sample.myapp;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.sample.myservice.IMyCallbackInterface;
import com.sample.myservice.IMyServiceInterface;

public class MainActivity extends AppCompatActivity {

    private final static String TAG = "MainActivity";

    private IMyCallbackInterface.Stub callback = new IMyCallbackInterface.Stub() {
        @Override
        public void onUpdate(int value) throws RemoteException {
            Log.d(TAG, "value: " + value);
        }
    };

    private IMyServiceInterface myservice;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            myservice = IMyServiceInterface.Stub.asInterface(iBinder);
            try {
                Log.d(TAG, myservice.greet());
                myservice.registerCallback(callback);
            } catch (RemoteException e) {
                Log.e(TAG, "exception", e);
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            myservice = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate()");
        setContentView(R.layout.activity_main);

        Intent intent = new Intent();
        intent.setClassName("com.sample.myservice", "com.sample.myservice.MyService");
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy()");
        try {
            myservice.unregisterCallback(callback);
        } catch (RemoteException e) {
            Log.e(TAG, "exception", e);
        }
        unbindService(serviceConnection);
    }
}


  • IMyCallbackInterface.Stub 구현
    • onUpdate() 구현
  • onServiceConnected() 에서 callback 등록
  • unbindService() 하기 전에 callback 등록 해제