2017년 7월 4일 화요일

Service AIDL


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

용어


Bind

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

AIDL (Android Interface Definition Language)

Service 측은 aidl 파일을 작성하고 통신하려는 앱들과 같은 aidl 파일을 공유해야 함

AIDL 예제


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 리턴

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 을 등록

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


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 등록 해제

2017년 5월 26일 금요일

adb 및 adb shell 명령어 정리

PC 에 연결된 Device 목록 확인
$ adb devices

logcat 캐시 지우기
$ adb logcat -c

logcat 시간 정보 포함하여 출력
$ adb logcat -v threadtime

APK 설치
$ adb install <file_path>

pm 명령 (adb shell)
$ pm install <apk path>

  • full path 로 입력해야 됨
  • -r 옵션: 기존에 설치된 경우 재설치


앱 제거
$ adb unintsall <package_name>

pm 명령 (adb shell)
$ pm uninstall <package name>

앱 종료 (adb shell)
$ kill <pid>

activity 시작 (adb shell)
$ am start <package>/<activity>

e.g.)
$ am start com.sample/.MainActivity

service 시작
$ am startservice <package>/<service>

e.g.)
$ am startservice com.sample/.MyService

package 정보 확인
$ dumpsys package <package name>
  • permission 정보, data path 등 여러 정보 확인 가능

e.g.)
$ dumpsys package com.android.systemui

permission 목록
$ pm list packages -g

broacast
$ adb shell am broadcast -a ${action_name} -e ${extra_key} ${extra_string_value}

  • 가능한 옵션들

    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]
    [-c <CATEGORY> [-c <CATEGORY>] ...]
    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]
    [--esn <EXTRA_KEY> ...]
    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]
    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]
    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]
    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]
    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]
    [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]
    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]
        (mutiple extras passed as Integer[])
    [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]
        (mutiple extras passed as List<Integer>)
    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]
        (mutiple extras passed as Long[])
    [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]
        (mutiple extras passed as List<Long>)
    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]
        (mutiple extras passed as Float[])
    [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]
        (mutiple extras passed as List<Float>)
    [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]
        (mutiple extras passed as String[]; to embed a comma into a string,
         escape it using "\,")
    [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]
        (mutiple extras passed as List<String>; to embed a comma into a string,
         escape it using "\,")
    [--grant-read-uri-permission] [--grant-write-uri-permission]
    [--grant-persistable-uri-permission] [--grant-prefix-uri-permission]
    [--debug-log-resolution] [--exclude-stopped-packages]
    [--include-stopped-packages]
    [--activity-brought-to-front] [--activity-clear-top]
    [--activity-clear-when-task-reset] [--activity-exclude-from-recents]
    [--activity-launched-from-history] [--activity-multiple-task]
    [--activity-no-animation] [--activity-no-history]
    [--activity-no-user-action] [--activity-previous-is-top]
    [--activity-reorder-to-front] [--activity-reset-task-if-needed]
    [--activity-single-top] [--activity-clear-task]
    [--activity-task-on-home]
    [--receiver-registered-only] [--receiver-replace-pending]
    [--selector]
    [<URI> | <PACKAGE> | <COMPONENT>]

2015년 7월 10일 금요일

[NDK] ndk-build 빌드 명령 출력

ndk-build 시 기본으로 화면에 출력하는 정보가 적습니다.

빌드 에러가 발생하는 경우 어떤 명령으로 빌드를 수행하였는지 중요합니다.

참고: https://groups.google.com/forum/#!topic/android-ndk/5OHkW6Hf2JA

$ ndk-build V=1 옵션으로 빌드 명령을 화면에 출력할 수 있습니다.

2015년 1월 29일 목요일

Eclipse -> Android Studio

참고

Android Studio 로 변환 과정

Android Studio 가 Android 공식 개발 도구로 지정되었습니다.
Android Studio 사용법을 익혀야 겠습니다.

소개

  • 간단하게 이야기 하자면 Android Studio 에서 eclipse project 를 불러오면 자동으로 변환이 됨
  • 예제 프로젝트를 생성하여 설명

1. sample 용 Eclipse 안드로이드 project 생성

  • hello : Android application project
  • appcompat_v7 : hello 생성하면서 자동으로 import 된 appcomat v7 library project
  • libproject :임의로 생성한 library project (hello 가 의존)



2. Android Studio 실행


3. Import Non-Android Studio project 를 선택하여 eclipse 프로젝트를 변환


Import Non-Android Studio projct 선택

eclipse 에서 생성한 hello 프로젝트를 선택
변경될 프로젝트가 저장될 위치 선택

변환 옵션 (기본으로 두고 finish 했음)


변환

변환 완료시 Language Level Changed 다이얼로그가 뜨기도 함 (Yes 선택)


4. 변환 완료

  • import-summary.txt : 변환과정 중 일어난 일들을 표시함
    • Ignored Files 항목을 보면 옮겨지지 않은 파일들이 표시됨 (필요시 직접 이동) 
  • 프로젝트 구조
    • hello(hello-hello 로 표시) : eclipse 의 hello project 에 해당하는 모듈
    • libproject : eclipse 의 libprojct project 에 해당하는 모듈


5.Android Studio프로젝트 구조 (맛보기)


구조
  • Eclipse 에는 workspace 라는 개념이 있고 그 아래 여러 프로젝트들이 존재
  • Android Studio 에는 프로젝트 아래 여러 모듈들이 존재
gradle 파일
  • settings.gradle
    • 관련 모듈 목록을 설정
    • 프로젝트에 하나 존재
  • build.gradle 파일
    • build 관련 설정 (의존성등을 설정)
    • 프로젝트에 하나, 모듈마다 하나씩 갖고 있음
왼쪽 상단에 Project 보기를 선택


변환된 폴더 구조를 확인 가능

6. 앱 실행 해보기


실행 버튼


기기가 없다면.. 에뮬레이터 선택

앱이 실행됨

하단에 logcat 창 표시됨

결론

이 외 아직 제대로 사용해 보진 않았지만 메뉴들을 잠깐씩 훑어보니 어렵지는 않겠습니다.
물론 겪어봐야 알 일이겠지요

2014년 9월 12일 금요일

14-day Notification: Violation of the Android branding guidelines


앱 이름에 Android 라는 단어를 잘못 사용하여서 경고 메일이 날라왔습니다.

메일 내용 전문:

We are writing to inform you that the title name of your application, Android Tool, with package ID com.tj.androidtool, is not in accordance with our Android branding guidelines and may additionally be in violation of our Google Play impersonation policies.
REASON FOR WARNING: Violation of the Android branding guidelines.
To demonstrate and achieve compliance, please perform one of the following actions within 14 days of this notice, or your app will be suspended on Google Play:
  • Edit the title of your app by removing "Android" as the prominent brand name. Instead, use "(title of app) for Android" as an alternative. Here's an example:
    • Incorrect: "Android MediaPlayer"
    • Correct: "MediaPlayer for Android"
  • Or, unpublish the listing from further availability on Google Play.
Before publishing applications, please ensure your apps' compliance with the Developer Distribution Agreement and Content Policy.
Thank for your understanding,
Google Play Developer Support


이에 앱 이름을 Tools for Android 로 변경하였습니다.
https://play.google.com/store/apps/details?id=com.tj.androidtool

2014년 5월 20일 화요일

App 소개 : UPnP Tool

UPnP 관련 업무를 시작하면서 UPnP 안드로이드 툴을 제작하게 되었습니다.

https://play.google.com/store/apps/details?id=com.tjjang.upnptool




UPnP 디바이스를 검색하고 디바이스 목록과 서비스들을 나열해 주고 action invoke 기능을 제공합니다.

감사합니다.