Android App Data save and restore


저장 경로

/data/data/<package_name>

  • databases/: here go the app's databases
  • lib/: libraries and helpers for the app
  • files/: other related files
  • shared_prefs/: preferences and settings
  • cache/: well, caches

저장 위치까지 모두 출력

  • adb shell pm list packages -f

backup 명령어

## backup nonsystem apk
adb backup -apk -shared -nosystem -all -f backup_apk.ab

### backup system and nonsystem apk
adb backup -apk -noshared -system -all -f backup_apk.ab

## backup individual apk
adb backup -apk com.example.testing -f testing.ab

## restore all
adb restore backup_apk.ab
  • apk – Backs up your apps.
  • noapk – Does not backup apps.
  • shared – Backs up data on the SD card.
  • noshared – Does not backup data on the SD card.

백업파일 tar로 변환하는 방법

Command 스타일

dd if=myAndroidBackup.ab bs=4K iflag=skip_bytes skip=24 | openssl zlib -d > myAndroidBackup.tar

dd if=data.ab bs=1 skip=24 | python -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" | tar -xvf

참고

정리잘된 블로그

AOSP 코드에 기반한 방법

깃허브 주소
https://github.com/nelenkov/android-backup-extractor

참고사이트

https://www.linkedin.com/pulse/backup-restore-your-android-phone-using-adb-ajibola-okubanjo/


구글 글래스 엔터프라이즈 에디션 (Google Glass Enterprise Edition) 리뷰


연구 과제를 위해서 2017.7월에 출시된 Google Glass Enterprise Eidition을 세 대 구매 했다.
온라인 상에 XE (Explore Eidition)에 대한 정보는 많은데 EE에 대한 정보는 별로 없는것 같아서 리뷰를 작성한다.

Imgur

구매 방법

구매를 위해서는 Google X와 파트너쉽을 해야한다.
Google X: https://www.x.company/glass/
위 사이트에 접속해서 Interested In Galss를 눌러서 메일을 보내면 된다.

그냥 학교에서 연구과제로 필요하다고 메일을 보내 보니 Streye라는 회사에서 직접 구매할 수 있다고 구글로 부터 답장을 받았다.

실제로 Glass partners를 누르면 많은 협렵 업체들이 이미 존재하기 때문에 새로 파트너쉽을 할 필요는 없는것 같다.

  • https://www.x.company/glass/partners/
    해당 파트너쉽 회사들을 눌러보면 AUGMEDIX와 같이 이미 의사와 환자와의 소셜 인터렉션 향상을 위한 제품을 출시하는 기업 등과 같이 많은 기업 제품들을 볼 수 있다.

Streye는 그리고 Vuzix의 M300과 같은 LED 디스플레이 형태 글라스도 판매하고 있다.
Streye는 자신들의 Platform으로 약간 Customized한다음 판매하고 있다. 필자는 Lite license로 구매 했다.

관세 감면 및 전파시험적합성 면제

필자 처럼 업체를 통하지 않고 Streye로 직접 여러대 구입하면 전파인증 문제가 발생 한다.

  • 국가 과제의 경우 90%까지 관세 감면 가능
  • 2.4Ghz대의 무선을 사용하므로 전파인증 대상 제품
  • 면제 사유로는 아래 두가지가 존재한다.
    • 개인 사용 목적으로 1대만 구매할 경우
    • 국가과제 사용 목적으로 구입한 경우 (최대100대)
    • 국립전파연구원에 접속하여 요건면제안내를 눌러서 지시사항을 보고 처리

제품 구성품

200만원 상당의 가격에 비해서 상당히 구성품이 없다.
아래 세개만 있고 심지어 설명서도 없다.
결국 Streye에 문의해서 개발 방법을 숙지하고 있다.

  • 글라스 본체
  • 렌즈를 끼울수 없는 단순 프레임
  • USB 충전선 (충전기 없음)

외형

2014년에 판매한 XE (Explore Edition)와 이번 EE (Enterprise Edition)과는 어느정도 차이가 있다.
XE는 다리가 접히지 않았지만 이번 EE버전은 다리가 접히게 된다.
개인적인 후기는 안경 착용자라 특수 프레임으로 렌즈를 맞추지 않으면 불편한 것 같다.
XE와 구조가 달라서 이전 프레임들은 사용 못하는 것 같아서 EE용 프레임을 찾아 봐야할 것 같다.

영상을 보기 위해서 눈을 위로 치켜들어야 하기 때문에 상당히 불편하다. 이런걸 200만원 주고 사는 사람은 아마 없을 것 같다.
학회가서 만난 교수님들이 좋다고 끼고 다니셨던 것이 진정성이 느껴지지 않았던 이유를 알겠다.

제품 스팩

스팩사항은 비공개 Teardown 정보를 참조 했다.

  • Display
    약간 더 넓어진 프리즘이다. 640x360사이즈와 거의 비슷 하다.
  • Audio
    단순 스피커이다.
  • Sensors
    XE: ambient light, digital compass, wink, blink sensors
    EE: XE sensors + barometer, a capacitive head sensor (in place of the proximity sensor), a hinge sensor (for determining whether the hinge is open or closed), assisted GPS & GLONASS
  • WiFi and Connectivity
    dual-band 2.4 + 5 Ghz 802.11a/b/g/n/ac
    Bluetooth LE and HID, supporting multiple Bluetooth connections at onece.
  • Camera
    5MP stills and 720p video.
    LED가 앞면에 있어서 recording 중인지 알 수 있게 한다.
  • AP
    Intel Atom, 커스텀 버전으로 다른 모델에서 사용된적은 없는것 같다.
    OS는 32bit이다.
  • Storage and Memory
    FLASH: 32GB
    RAM: 2GB
  • Battery and charger
    ditches microUSB: 5V and 1.5V
    780mAh (XE: 570mAh)

안경 사용자를 위한 구글 글래스

Streye홈페이지에서 안경착용자를 위하 Frames을 판매하기 시작했다.
RX EYEWEAR FRAMES FOR GLASS ENTERPISE

Explore Edition과 혼용이 안되기 때문에 새로운 EE 버전에 맞춰서 프레임이 필요 했다.
각격은 무려 330유로이다. 약 40만원돈이다. 홈페이지 외형은 너무나 조악 했지만 막상 받아보니 괜찮았다.
간단하게 분리한 후에 끼우면 된다.

Glass EE, Hello World 앱 개발

SDK Manager에서 Android 4.4에 해당 하는 부분에서 Glass Development Kit Preview를 다운 받는다.

Glass Enterpise->Setting->Device Options-> Device info-> Turn on debug를 선택한다.

아래와 같이 Glass 앱을 구동할 수 있다.

Ok Glass라고 말하면 아래와 같이 Quick launcher가 구동된다.
XML에 trigger 옵션을 넣으면 앱을 Show me a demo with [앱이름]으로 실행 가능 하다.

아래는 Ok glass camera로 실행해서 바라본 google glass EE에서의 화면 모습이다.
DDMS를 이용해서 Capture한 것이다.
실제로 찍히는 Video품질은 720p여서 상당히 고화질이다.


Activity와 Service간의 통신


Local Broadcast Manager를 이용한 방법 (BR)

그냥 broadcast receiver를 등록해서 사용하는 방법은 다른앱에서도 해당 방송을 들을 수 있기 때문에 private data leak문제가 생긴다.

API 22 (android 5.1)부터 지원하는 Local Broadcast Manager방법으로 구현한다.

Main Activity

리시버 등록 코드

    @Override
    protected void onResume() {
        ...
        // action 이름이 "custom-event-name"으로 정의된 intent를 수신하게 된다.
        // observer의 이름은 mMessageReceiver이다.
        LocalBroadcastManager.getInstance(this).registerReceiver(
                mMessageReceiver, new IntentFilter("custom-event-name"));
    }

    @Override
    protected void onPause() {
         ...
        LocalBroadcastManager.getInstance(this).unregisterReceiver(
                mMessageReceiver);
    }

broadcast receiver 코드

// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        // Get extra data included in the Intent
        String message = intent.getStringExtra("message");
        Log.d("receiver", "Got message: " + message);
    }
};

Service 코드

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        sendMessage();
        return super.onStartCommand(intent, flags, startId);
    }

    private void sendMessage(){
        Log.d("messageService", "Broadcasting message");
        Intent intent = new Intent("custom-event-name");
        intent.putExtra("message", "This is my first message!");
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

실행 결과

실행 결과는 아래와 같이 버튼을 누르면 service가 실행되고 해당 service에서 local broadcast manager를 통해서sendBroadcast를 하면 activity에 등록된 receiver에서 이것을 수신해서 처리하는 방식으로 동작한다.

04-06 16:16:34.110 18075-18075/com.example.user.examactivityservicecommnunication D/messageService: Broadcasting message
04-06 16:16:34.110 18075-18075/com.example.user.examactivityservicecommnunication D/receiver: Got message: This is my first message!

Github 전체코드

Local Bind Service를 이용한 양방향 통신

전통적인 방법인 Bind Service를 이용한 방법을 다룬다.
Started Service까지도 같이 구현하면 Music Player에서 종종 쓰이는 패턴이 된다.
왜냐하면 백그라운드에서 음악이 재생되고 해당 엑티비티가 다시 살아나면 현재 재생되고 있는 음악이 리스트업 되어야 한다. 따라서Bind service로 데이터 통신도 되어야 하기 때문이다.

아래 코드는 간단히 엑티비티에서 서비스의 특정 callback을 호출하고 해당 서비스내의 콜백 함수는 다시 엑티비티의 callback함수를 호출하는식으로 작성 되었다.

main acitivity

   public void onClick(View v) {
        switch (v.getId()){
            case R.id.button:
                Intent Service = new Intent(this, BindService.class);
                bindService(Service, mConnection, Context.BIND_AUTO_CREATE);
                break;
            case R.id.button2:
                mBindService.myServiceFunc();
        }
    }

    // service connection definition
    private ServiceConnection mConnection = new ServiceConnection() {

        // Called when the connection with the service is established
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            BindService.BindServiceBinder binder = (BindService.BindServiceBinder) service;
            mBindService = binder.getService(); // get service.
            mBindService.registerCallback(mCallback); // callback registration
        }
        // Called when the connection with the service disconnects unexpectedly
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBindService = null;
        }
    };

    // call below callback in service. it is running in Activity.
    private BindService.ICallback mCallback = new BindService.ICallback() {
        @Override
        public void remoteCall() {
            Log.d("MainActivity","called by service");
        }
    };

service

    private final IBinder mBinder = new BindServiceBinder();
    private ICallback mCallback;

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

    // declare callback function
    public interface ICallback {
        public void remoteCall();
    }

    // for registration in activity
    public void registerCallback(ICallback cb){
        mCallback = cb;
    }

    // service contents
    public void myServiceFunc(){
        Log.d("BindService","called by Activity");

        // call callback in Activity
        mCallback.remoteCall();
    }

    // Declare inner class
    public class BindServiceBinder extends Binder {
        BindService getService(){
            return BindService.this; // return current service
        }
    }

실행결과

04-09 21:26:18.351 26984-26984/com.example.user.localbindservice D/BindService: called by Activity
04-09 21:26:18.351 26984-26984/com.example.user.localbindservice D/MainActivity: called by service

Github 전체 코드

ResultReceiver로 단방향 메시지 전달

ResultReceiver란 결국 Binder의 wrapper 함수를 의미한다.
따라서 ResultReceiver를 전달 함으로써 Service Activity가 서로 통신할 수 있다.

Activity 쪽 코드

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

        progressBar = (ProgressBar)findViewById(R.id.progressBar);
        syncMessage = (TextView)findViewById(R.id.textview);
        syncButton = (Button)findViewById(R.id.button);
        syncButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) { // (1)
        progressBar.setVisibility(View.VISIBLE);
        syncMessage.setText("sync start");

        Intent intent = new Intent(this, SyncService.class);
        intent.putExtra("RECEIVER", resultReceiver); //(2)
        startService(intent);
    }

    private Handler handler = new Handler();

    private ResultReceiver resultReceiver = new ResultReceiver(handler){ // (3)

        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            super.onReceiveResult(resultCode, resultData);

            // SYNC_COMPLETED CODE = 1 
            if (resultCode == 1){  // (4)
                String msg = resultData.getString("msg");
                progressBar.setVisibility(View.GONE);
                syncMessage.setText("Done "+msg);
            }

        }
    };
  • (1): 동기화 버튼을 클릭 할 때를 가정 한다.
  • (3): ResultReceiver를 생성하고 onReceiveResult() 메서드를 오버라이드한다. ResultReceiver 생성자에는 handler 인스턴스를 넣을 수도, null로 할 수도 있다. Service의 백그라운드 스레드에서 ResultReceiver의 send()메시지를 호출하는데, 결과를 받는 쪽에서 UI를 업데이트 하기 때문에 Handler를 거쳐 메인 Looper의 MessageQueue에 Message를 넣은 것이다.
  • (4): SYNC_COMPLETED resultCode를 받으면 ProgressBar를 숨기고, 텍스트 메시지는 R.string.sync_ended로 변경한다.
  • (2): Intent Extra에 ResultReceiver 인스턴스를 전달한다.

서비스 쪽 코드

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mIntent = intent;

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("SyncService","SyncService started");

                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Bundle bundle = new Bundle();
                final ResultReceiver receiver = mIntent.getParcelableExtra("RECEIVER"); // (1)
                bundle.putString("msg","Succeed !");
                receiver.send(1,bundle); // (2)
            }
        }).start();

        return START_NOT_STICKY;
    }
  • (1): Intent에서 ResultReceiver를 꺼내오고 작업을 마친후에
  • (2): send() 메서드로 SYNC_COMPLETED라는 결과를 보낸다.

실행 결과

아래의 실행결과는 network 싱크를 가정해서 service에서 5초 thread sleep한 후에 다시 그 결과를 bundle에 넣어서 반환해서 activity에서 그 결과를 표시하는 예제의 결과이다.

작성한 Github 코드

다른 예제 코드: ProAndroidDev: Using ResultReceiver to communicate with IntentService

참고자료

다양한 방법을 설명하고 있음

2012 05 31

말로만 간단히 설명

바인드된 서비스

Android pub, 2011.09.29

Android pub, 2011.10.05

AIDL을 이용한 Service <-> Activity간 Callback 통신

안드로이드 로컬서비스를 이용한 액티비티와 서비스간 통신

Local bind service를 이용한 방법


Android 6.0 이상에서의 백그라운드 서비스 생존 방법에 대한 고찰


개요

문제점

  • Android 6.0에서의 Doze 모드
  • 삼성 스마트 매니저

전략

  • Wakelock
  • Alarm Manager 방식
  • Google Cloud Message 방식
  • Screen ON

화면 항상 켜기

<RelativeLayout 
    ....
    android:keepScreenOn="true"
    ....
>
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

Wakelock

<uses-permission android:name="android.permission.WAKE_LOCK" />

Wakelock 생성 방법

PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
        "MyWakelockTag");
wakeLock.acquire();

Broadcast Receiver Wakelok 방법

 WakefulBroadcastReceiver,

https://developer.android.com/training/scheduling/wakelock.html#wakeful

Scheduling Repeating Alarms

삼성 스마트 매니저 극복 방법

http://gun0912.tistory.com/64
http://blog.soundl.ly/2016/04/blog-post_12.html


SQLite 탐색기 및 기초 쿼리


sqlite-browser는 쉽게 구할 수 있지만 생각보다 query 실행시 멈침 현상이 심하다.
검색 도중 python flask로 구현된 web-based 도구가 있어서 이것을 사용 한다.
sqlite-web이며 설치화 실행 모습은 아래와 같다.

sqlite-web tool

$ sudo apt-get install python-pip ## pip를 이미 설치하였다면 skip 합니다.
$ sudo pip install --upgrade pip  ## pip 업그레이드 합니다.
$ sudo apt install python-setuptools ## sqlite-web설치를 위해 필요
$ sudo pip install sqlite-web     ## sqlite-web 설치 합니다.
## 포트번호 3000(기본값은 8080)으로 현재 디렉토리의 test.db 파일을 웹으로 보고싶은 경우 아래 명령과 같이 입력합니다.
## 만약 test.db파일이 없으면 새로 생성하게 됩니다.
$ sqlite_web -p 3000 ./test.db ## 하이픈 아니고 언더바임에 주의하세요. 
 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
 127.0.0.1 - - [20/Sep/2016 16:25:24] "GET / HTTP/1.1" 200 -
 Created new window in existing browser session.

실행

select

select * from log oder by _id desc limit 20
SELECT Region, count(*)
FROM item
WHERE Region is not null
GROUP BY Region

실행 결과

Region, count
Denmark, 4
Sweden, 1
USA, 10

하위 20개 선택

SELECT *
FROM "log"
order by _id DESC limit 20

NotificationListenerService 이상 종료 문제 및 디버깅 방법


Debugging 방법

notification access를 끈다음 debug rerun을 하고 다시 enable하면 잘된다.

원글: https://stackoverflow.com/questions/37774499/cant-use-debugger-with-notificationlistenerservice

NotificationListenerService가 호출되지 않는 문제

Android caching 문제라고 한다.
즉, 장치에 app을 업로드 할 때 OS는 해당 서비스를 notification manager에 connection하고 있으므로 다시 앱을 재실행 한다고해서 그 서비시를 재 연결하지 않는다. 따라서 그 후로 이상하게 onPosted나 onRemoved 같은 callback 함수가 호출이 안되는 것이다.

해결 방법: 서비스 이름을 변경 한다. 앱을 pushing하기 전에

onListenerConnected 해당 API를 이용하면 해당 Listener가 연결 되었는지를 알 수 있다.

원글: https://stackoverflow.com/questions/33530807/why-is-this-notificationlistenerservice-not-working

Cannot get the NotificationListenerService class to work

If your APK/binary changes and your NotificationListenerService stops:

  • Rebooting fixes it.
  • Going back in to Notification access and disabling and re-enabling your app it fixes it.

원글: https://stackoverflow.com/questions/17911883/cannot-get-the-notificationlistenerservice-class-to-work

Ask the user to enable the setting (Notification Access) if needed

if (!Utilities.hasNotificationAccess(this)) 
{
     Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
     startActivity(intent);
     Log.i(TAG,"hasNotificationAccess NO");
}
else
{
    Log.i(TAG,"hasNotificationAccess YES");

    // without this startService, it never works with Android 4.4...
    // but this is not needed in Android 6... 
    Intent mServiceIntent = new Intent(this, NLService.class);
    startService(mServiceIntent);
}


Firebase 설정 방법


홈페이지에 워낙 잘 나와있지만 그냥 정리 차원에서 다뤄 본다.

앱등록

debug key 값 알아내기

  • Gradle-> Tasks-> android -> signingReport -> SHA1 key 값

그 다음 아래와 같이 등록한다.

구성 파일 다운로드

  • google-services.json 다운로드 한다음 프로젝트에다가 넣어준다.

주의할 것은 app폴더안에다가 넣어야 한다는 것이다. build가 아니다. 스크린샷을 보고 잘 해야 한다.

Firebase SDK 추가

Gradle Scripts의 첫 번째 build.gradle dependencies라는 항목안에다가 classpath 추가한다.

  • 'com.google.gms:google-services:3.1.0'의 내용을 추가 한다.

두 번째 build.gradle에서는 맨 하단에다가 apply plugin: 'com.google.gms.google-services'을 추가한다.

사용 가능한 라이브러리 추가

원하는 Firebase 기능 구현에 따라 아래의 라이브러리를 gradle에 추가한다.

  • com.google.firebase:firebase-core:11.0.4 애널리틱스
  • com.google.firebase:firebase-database:11.0.4 실시간 데이터베이스
  • com.google.firebase:firebase-storage:11.0.4 저장소
  • com.google.firebase:firebase-crash:11.0.4 오류 보고
  • com.google.firebase:firebase-auth:11.0.4 인증
  • com.google.firebase:firebase-messaging:11.0.4 클라우드 메시징
  • com.google.firebase:firebase-config:11.0.4 원격 구성
  • com.google.firebase:firebase-invites:11.0.4 초대 및 동적 링크
  • com.google.firebase:firebase-ads:11.0.4 AdMob
  • com.google.firebase:firebase-appindexing:11.0.4 앱 색인 생성
  • com.google.firebase:firebase-perf:11.0.4 성능 모니터링


Handler를 이용한 시간제한 기능 구현


BLE Scanning 주기 조절

private static final long SCAN_PERIOD = 1000

private void scanLeDevice(final boolean enable){
	if(enable){
		mHandler.postDelyaed(new Runnable(){
			@Override
			public void run(){
				mScanning = false;
				mBluetoothAdapter.stopLeScan(mLeScanCallback);				
			}
		}, SCAN_PERIOD);
	mScanning = true;
}

백키 두번 이상 연속해서 누를 때만 액티비티 종료

private boolean isBackPressedOnce = false;
private static final long BACKKEY_DELAY = 5000;

@Override
public void onBackPressed(){
	if (isBackPressedOnce){
		super.onBackPressed(); // 종료한다.
	} else {
		Toast.makeText(this, R.string.backpressed_message, Toast.LENGTH_SHORT).show();
		isBackPressedOnce = true; // 한 번 백키 누른것을 저장한다.
		mHandler.postDelayed(timerTask, BACKKEY_DELAY); // 5초후 작업을 지정한다.
	}
}

// 5초가 지나면 한 번 누른 백키를 초기화 한다.
private final Runnable timerTask = new Runnable(){
	@Override
	public void run(){
		isBackPressedOnce = false;
	}

}


'Computer Science > Android Application' 카테고리의 다른 글

NotificationListenerService 이상 종료 문제 및 디버깅 방법  (0) 2017.10.31
Firebase 설정 방법  (0) 2017.10.31
AIDL과 Remote Service  (1) 2017.08.22
NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20

AIDL과 Remote Service


AIDL(Android Interface Definition Language)은 전에 다뤄본 다른 IDL과 유사합니다. 클라이언트와 서비스가 프로세스간 통신(IPC)을 사용하여 서로 소통하는 데 동의한 프로그래밍 인터페이스를 정의할 수 있습니다. Android에서는 한 프로세스가 다른 프로세스의 메모리에 정상적으로 액세스할 수 없습니다. 따라서 객체들을 운영 체제가 이해할 수 있는 원시 유형으로 해체하고 해당 경계에 걸쳐 마샬링해야 합니다. 이 마샬링을 위해 코드를 작성하는 일은 상당히 지루한 작업인데, Android는 AIDL을 이용해 그 일을 대신 해줍니다.

마샬링: 한 객체의 메모리에서의 표현 방식을 저장 또는 전송에 적합한 다른 데이터 형식으로 변환하는 과정이다. 마샬링은 직렬화와 유사하며 한 오브젝트 여기서는 직렬화 된 오브젝트로 멀리 떨어진 오브젝트와 통신하기 위해 사용된다.

AIDL 인터페이스 정의

AIDL을 사용하여 바인드된 서비스를 생성하려면 다음 단계를 따라야 한다.

  1. aidl 파일 생성

    • 이 파일은 메서드 서명으로 프로그래밍 인터페이스를 정의한다.
  2. 인터페이스 구현

    • Android SDK 도구는 .aidl파일을 기반으로 Java 프로그래밍 언어로 인터페이스를 생성한다. 이 인터페이스는Binder를 확장하고 AIDL 인터페이스로부터 메서드를 구현하는 Stub라는 내부 추상 클래스를 가지고 있다. Stub클래스를 확장하고 메서드를 구현해야 한다.
  3. 클라이언트에게 인터페이스 노출

    • Service를 구현하고 onBind()를 재정의하여 Stub 클래스의 구현을 반환한다.

리모트 바인딩

  • 리모트 바인딩 서비스는 다른 프로세스에서 접근하는 것을 전제로 만들어진다. 따라서 로컬에서만 사용하는 서비스라면 리모트 바인딩 서비스를 굳이 만들 필요가 없다.

생성방법

  • 바인딩한 클라이언트에 제공하는 메서드를 aidl 인터페이스로 작성한 다음에 서비스에서 stub클래스의 추상 메서드를 구현해 주면된다.

aidl 인터페이스와 생성 클래스

package com.cnu.eslab.suite;

interface ITrainingService{
	boolean setServiceMode(String filename);
}

이렇게하면 Android Studio에서는 자동으로build/generated/source/aidl디렉터리에IRemoteService.java가 생성된다.

Service에 Stub 구현

  • Service에서는 추상 클래스인 Stub 구현체를 만든다.
public class RemoteService extends Service{
    @Override
    public IBinder onBind(Intent intent) {
            return mBinder;
    }
	
	ITrainingService.Stub mBinder = new ITrainingService.Stub() {
		
		@Override
		public boolean setServiceMode(String filename) throws RemoteException {
			// TODO Auto-generated method stub
			ResultFileName = filename;
			return true;
		}
	};

위와 같이 Stub의 내부 기능을 구현하면 된다.

클라이언트에서 서비스 바인딩

Activity에서 바인딩해서 사용해야 한다.
bindService()는 바인딩 결과를 비동기로 받기 때문에, 콜백으로 사용할 Service Connection인스턴스를 bindService()메서드에 파라미터로 전달한다.

리모트 서비스 바인딩

ITrainingService counterService;

@Override
pulbic void onCreate(Bundle savedInstanceState){
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	
	conn = new TrainingConnection();
}

private class TrainingConnection implements ServiceConnection {

	public void onServiceConnected(ComponentName className, 
                                   IBinder boundService) {
      counterService = ITrainingService.Stub.asInterface((IBinder)boundService);
    }
	
    //bind service가 연결 해지 됬을 때 실행 된다.
    public void onServiceDisconnected(ComponentName className) {
      counterService = null;
    }
  } 
    @Override
	protected void onResume() {
		// TODO Auto-generated method stub
    	//restorePrefs();
	   	super.onResume();
	   	lock.disableKeyguard();
	   	bindService(serviceIntent, conn, 0);
	}
  • connectionCall: ServiceConnection을 생성한다.
  • Stub.asInterface() 메서드를 통해서 로컬인 경우는 Stub 인스턴스, 리모트인 경우 Proxy 인스턴스가 mIRemoteService에 대입된다.
  • 연결이 끊길 때는 mIRemoteService를 null로 만든다.
  • bindService()에 ServiceConnection을 전달한다.
  • mIRemoteService의 메서드를 호출할 때는 먼저 null인지 체크한다.


'Computer Science > Android Application' 카테고리의 다른 글

Firebase 설정 방법  (0) 2017.10.31
Handler를 이용한 시간제한 기능 구현  (0) 2017.08.22
NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16

NDK 사용


NDK를 이용해서 간단한 예제를 개발해 본다.

  • NDK (Native Developement Kit): C/C++로 안드로이드 앱을 개발할 수 있도록 도와주는 도구이다.

  • JNI (Java Native Interface): Java와 Native 언어들 간에 서로 호출하고 호출될 수 있도록 인터페이스를 제공한다.

1. NDK 설치

  • SDK Manager를 통해서 3가지를 설치한다.

    • Android NDK (Native Development Kit): Android에서 C 및 C++ 코드를 사용할 수 있도록 지원하고 네이티브 액티비티를 관리하고 물리적 구성 요소 (예: 센서 및 터치 입력)에 엑세스할 수 있는 플랫폼 라이브러리를 제공하는 툴바이다.
    • CMake: Gradle과 함께 작동하여 네이티브 라이브러리를 빌드하는 외부 빌드 도구이다.ndk-build만 사용하려는 경우에는 이 구성 요소가 필요하지는 않다.
    • LLDB: Android Studio가 네이티브 코드를 디버그하는데 사용하는 디버거이다.
  • File -> Project Structure -> SDK Location -> Android NDK location에 설정 되었는지 확인 한다.

2. Project 생성

평상시와 같이 생성하고 단지 옵션에서 include C/C++항목을 체크해 준다.

  • 그냥 자동으로 샘플 코드를 생성해 준다.

샘플 예제를 실행하면 아래와 같다.

빌드과정

  • cpp: 해당 파일은 stringFromJNI()함수를 제공해서 문자열을 반환하게 된다.
  • External Build Files: 해당 그룹에서 CMake또는 ndk-build용 빌드 스크립트를 확인 할 수 있다. build.gradle 파일이 Gradle에 앱을 빌드하는 방법을 알리는 방법과 유사하게 CMake ndk-build에서 네이티브 라이브러리를 빌드하는 방법을 파악하려면 빌드 스크립트가 필요하게 된다. 새로운 프로젝트의 경우 Android Studio CMake 빌드 스크립트 CMakeLists.txt를 생성하여 모듈의 루트 디렉토리에 배치한다.

샘플 앱 빌드 및 실행

  • Gradle이 외부 빌드 스크립트 CMakeLists.txt를 호출
  • CMake가 이 빌드 스크립트에 포함된 명령을 따라 C++소스 파일 native-lib.cpp를 공유 객체 라이브러리로 컴파일하고 이를 libnative-lib.so로 명명한다. 그러면 Gradle이 이를 APK로 패키징한다.
  • 런타임에 앱의 MainActivity System.loadLibrary()를 사용하여 네이티브 라이브러리를 로드한다. 이제 앱에서 라이브러리의 네이티브 함수 stringFromJNI()를 사용할 수 있다.
  • MainActivity.onCreate()가 Hello from C++를 반환하는 stringFromJNI()를 호출하고 이를 사용하여 TextView를 업데이트 한다.

기존 프로젝트에 C/C++ 코드 추가 (Eclipse Migration 포함)

네이티브 코드를 기존 프로젝트에 추가할 수 있다.

  • 새로운 네이티브 소스 파일 생성

이미 있으면 생략

  • CMake 빌드 스크립트 생성

이미 있으면 생략, ndk-build를 사용하기 위해서 Android.mk가 있는 경우에도 생략

  • CMake 또는 ndk-build 스크립트 파일의 경로를 제공하여 Gradle에 네이티브 라이브러리를 링크 시킨다. 방법은 아래와 같다.
    • Android Studio UI를 이용할 경우 ANdroid View에서 앱 모듈에 마우스 오른쪽 버튼을 클릭한 후 메뉴에서 Link C++ Project with Gradle을 선택한다. CMake또는ndk-build를 선택한다. CMake의 경우 CMakeLists.txt 스크립트 파일을 경로에 설정하고 ndk-build의 경우 Android.mk파일을 지정한다. 설정창은 아래와 같다.

참고문헌

http://yucaroll.tistory.com/1
공식사이트


'Computer Science > Android Application' 카테고리의 다른 글

Handler를 이용한 시간제한 기능 구현  (0) 2017.08.22
AIDL과 Remote Service  (1) 2017.08.22
Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16

Preference


프리퍼런스를 관리하는 클래스는 SharedPreferences이다.

  • SharedPreferences getSharedPreferences (String name, int mode)

    • 첫 번째 인수는 프레퍼런스를 저장할 XML 파일의 이름
    • mode 인수는 이 파일의 공유 모드로 0이면 읽기 쓰기가 가능 하다.
  • SharedPreferences getPreferences (int mode)

    • 파일 인수가 생략되어 있는데 이 경우 액티비티의 이름과 같은 xml 파일이 생성된다.
  • int getInt (String key, int defValue)

  • String getString (String key, String defValue)

  • boolean getBoolean (String key, boolean defValue)

값을 기록하는 메서드는 내부 클래스인 SharedPreferences.Editor가 제공된다.

  • SharedPreferences.Editor putInt(String key, int value)
  • SharedPreferences.Editor putBoolean(String key, boolean value)
  • SharedPreferences.Editor putstring(String key, String value)
  • SharedPreferences.Editor remove(String key)
  • boolean commit()
  • SharedPreferences.Editor clear()

문자열과 정수를 저장하는 간단한 예제

public class PrefTest extends Activity {
	TextView textName;
	TextView textStNum;
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.preftest);

		textName = (TextView)findViewById(R.id.name);
		textStNum = (TextView)findViewById(R.id.stnum);

		SharedPreferences pref = getSharedPreferences("PrefTest",0);
		String Name = pref.getString("Name", "이름없음");
		textName.setText(Name);

		int StNum = pref.getInt("StNum",20101234);
		textStNum.setText("" + StNum);
	}

	public void onPause() {
		super.onPause();

		SharedPreferences pref = getSharedPreferences("PrefTest",0);
		SharedPreferences.Editor edit = pref.edit();

		String Name = textName.getText().toString();
		int StNum = 0;
		try {
			StNum = Integer.parseInt(textStNum.getText().toString());
		}
		catch (Exception e) {}

		edit.putString("Name", Name);
		edit.putInt("StNum", StNum);

		edit.commit();
	}
}
  • getSharedPreferences메서드로 프레퍼런스 객체를 얻는다.
  • onPause에서 edit메서드로 기록할 데이터를 프레퍼런스에 기록 한다.
  • onCreate에서 객체를 읽어온다.

PreferenceActivity

사용자가 설정값을 입력하고 불러 올 수 있는 UI를 미리 만들어서 제공하는 자동화된 방법이다.
xml만 잘 작성하고 Activity에서 PreferenceActivity만 잘 상속해서 사용하면 된다.

<PreferenceScreen  xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
	android:key="age"
	android:title="나이"
	android:summary="너 도대체 몇 살이니?"
	android:defaultValue="19" 
/>
<CheckBoxPreference
	android:key="male"
	android:title="성별"
	android:summary="남자면 체크"
	android:defaultValue="true" 
/>
</PreferenceScreen>
public class PrefActivity extends PreferenceActivity {
	@SuppressWarnings("deprecation")
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.layout.prefactivity);
	}
}

TextPref

xml을 이용한 방법은 parser를 이용해야 하기 때문에 속도가 느리다.
빈번하게 onPause에서 현재 상태를 저장해야 한다면 문제가 발생 한다.

그래서 그냥 단순히 text에 기록하고 불러오는 방법을 사용한다.

테스트 코드

public class TextLogTest extends Activity {
	LinearLayout mLinear;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.textlogtest);

		// onCreate에서 로그 유틸리티 초기화
		TextLog.init(this);
		TextLog.mAppendTime = true;
		TextLog.mReverseReport = true;

		mLinear = (LinearLayout)findViewById(R.id.linear);
		mLinear.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				switch (event.getAction()) {
				case MotionEvent.ACTION_DOWN:
					// 필요할 때 로그 기록
					lg.o("down. x = " + (int)event.getX() + 
							", y = " + (int)event.getY());
					return true;
				case MotionEvent.ACTION_MOVE:
					lg.o("move. x = " + (int)event.getX() + 
							", y = " + (int)event.getY());
					return true;
				}
				return false;
			}
		});
	}

	// 다음 두 메서드를 디버깅 프로젝트의 엑티비티에 추가한다.
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		TextLog.addMenu(menu);
		return true;
	}

	public boolean onOptionsItemSelected(MenuItem item) {
		if (TextLog.execMenu(item) == true) {
			return true;
		}
		return false;
	}
}
//텍스트 파일에 설정 정보를 저장하는 클래스. 안드로이드의 프레프런스가 너무 느려 새로 만듬
//Ready()를 호출하여 입출력 준비하고 기록할 때는 CommitWrite, 읽기만 했을 때는 EndReady를 호출한다.
class TextPref {
	String mPath;
	StringBuilder mBuf;
	static final String HEADER = "__Text Preference File__\n"; 

	// 생성자로 프레퍼런스의 완전 경로를 전달한다. 
	public TextPref(String Path) throws Exception {
		mPath = Path;
		File file = new File(mPath);
		if (file.exists() == false) {
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(HEADER.getBytes());
			fos.close();
		}
	}

	// 설정 파일을 삭제한다.
	public void Reset() {
		File file = new File(mPath);
		file.delete();
	}

	// 버퍼를 준비하여 읽기 및 쓰기 준비를 한다.
	public boolean Ready() {
		try {
			FileInputStream fis = new FileInputStream(mPath);
			int avail = fis.available();
			byte[] data = new byte[avail];
			while (fis.read(data) != -1) {;}
			fis.close();
			mBuf = new StringBuilder(avail);
			mBuf.append(new String(data));
		}
		catch (Exception e) {
			return false;
		}
		return true;
	}

	// 버퍼의 내용을 파일로 기록한다.
	public boolean CommitWrite() {
		File file = new File(mPath);
		try {
			FileOutputStream fos = new FileOutputStream(file);
			fos.write(mBuf.toString().getBytes());
			fos.close();
		} 
		catch (Exception e) {
			return false;
		}
		mBuf = null;
		return true;
	}

	// 버퍼를 해제하고 읽기를 종료한다. 변경한 내용은 모두 취소된다.
	public void EndReady() {
		mBuf = null;
	}
	
	// name키의 위치를 검색하여 = 다음 위치를 리턴한다. 없으면 -1을 리턴한다.
	// 우연한 중복 방지를 위해 키 이름앞에 __를 붙인다.
	int FindIdx(String name) {
		String key = "__" + name + "=";
		int idx = mBuf.indexOf(key);
		if (idx == -1) {
			return -1;
		} else {
			return idx + key.length();
		}
	}

	// 문자열 키를 기록한다. 이미 있으면 대체한다.
	public void WriteString(String name, String value) {
		int idx = FindIdx(name);
		if (idx == -1) {
			mBuf.append("__");
			mBuf.append(name);
			mBuf.append("=");
			mBuf.append(value);
			mBuf.append("\n");
		} else {
			int end = mBuf.indexOf("\n", idx);
			mBuf.delete(idx, end);
			mBuf.insert(idx, value);
		}
	}

	// 문자열 키를 읽는다. 없으면 디폴트를 리턴한다.
	public String ReadString(String name, String def) {
		int idx = FindIdx(name);
		if (idx == -1) {
			return def;
		} else {
			int end = mBuf.indexOf("\n", idx);
			return mBuf.substring(idx, end);
		}
	}

	// 정수를 읽는다. 일단 문자열 형태로 읽은 후 변환한다.
	public void WriteInt(String name, int value) {
		WriteString(name, Integer.toString(value));
	}

	// 정수를 기록한다. 문자열 형태로 변환하여 기록한다.
	public int ReadInt(String name, int def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Integer.parseInt(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	public void WriteLong(String name, long value) {
		WriteString(name, Long.toString(value));
	}
	
	public long ReadLong(String name, long def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Long.parseLong(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	// 진위값은 true, false가 아닌 1, 0으로 기록한다.
	public void WriteBoolean(String name, boolean value) {
		WriteString(name, value ? "1":"0");
	}

	public boolean ReadBoolean(String name, boolean def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return s.equals("1") ? true:false;
		}
		catch (Exception e) {
			return def;
		}
	}
	
	public void WriteFloat(String name, float value) {
		WriteString(name, Float.toString(value));
	}

	public float ReadFloat(String name, float def) {
		String s = ReadString(name, "__none");
		if (s.equals("__none")) {
			return def;
		}
		try {
			return Float.parseFloat(s);
		}
		catch (Exception e) {
			return def;
		}
	}
	
	// 한꺼번에 값을 삽입하기 위해 준비한다. 헤더 작성하고 충분한 버퍼를 할당한다.
	void BulkWriteReady(int length) {
		mBuf = new StringBuilder(length);
		mBuf.append(HEADER);
		mBuf.append("\n");
	}

	// 문자열 형태로 받은 값을 무조건 뒤에 덧붙인다.
	void BulkWrite(String name, String value) {
		mBuf.append("__");
		mBuf.append(name);
		mBuf.append("=");
		mBuf.append(value);
		mBuf.append("\n");
	}

	// 키를 삭제한다. 
	void DeleteKey(String name) {
		int idx = FindIdx(name);
		if (idx != -1) {
			int end = mBuf.indexOf("\n", idx);
			mBuf.delete(idx - (name.length() + 3), end + 1);
		}
	}
}

출처

[1] 안드로이드 정복 4판, 김상형


'Computer Science > Android Application' 카테고리의 다른 글

AIDL과 Remote Service  (1) 2017.08.22
NDK 사용  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16
Activity  (0) 2017.08.16

Broadcast Receiver


보내는 쪽은 하나이고 받는 쪽은 다수인 개념으로 Broadcast라는 용어를 통상 쓴다.

BroadcastReceiver클래스를 재정하여 사용 한다.

  • void onReceive (Context context, Intent intent)

메인 스레드에서 동작하기 때문에 간결하게 작성하고 신속하게 리턴해야한다.

시스템에서 응용프로그램으로 방송하는 것이 일반적이지만 응용 프로그램도 특별한 변화를 유발시켰다거나 특이한 변화를 발견했다면
다른 응용 프로그램에게 방송을 보낼 수 있다.
그래서 방송은 응용 프로그램끼리 통신하는 공식적인 수단으로 활용된다. 응용 프로그램이 방송할 때는 다음 메서드를 호출한다.

  • void sendBroadcast (Intent intent [, String receiverPermission])
  • void sendOrderdBroadcast (Intent intent, String receiverPermission)

일반 방송은 비동기적으로 동작하여 호출시 즉시 리턴한다. 수신자가 방송을 수신했는지의 여부는 관여하지 않으며 누가 먼저 방송을 받을지 알 수 없다. 비동기적이며 비순서적이므로 효율이 좋다.

BroadCast 발생과 수신

임의로 응용프로그램에서 BR을 발생시키는 코드이다.

public class DetectFree extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.detectfree);
	}

	public void mOnClick(View v) {
		Intent intent = new Intent();
		intent.setAction("andexam.ver6.FREEWIFI");
		sendBroadcast(intent);
	}
}

수신하는 코드는 다음과 같다.

public class FreeBR extends BroadcastReceiver {
	public void onReceive(Context context, Intent intent) {
		Intent intent2 = new Intent(context, 
				andexam.ver6.c28_network.AsyncDownHtml.class);
		intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		context.startActivity(intent2);
	}
}

매니패시트

<receiver android:name=".c29_br.FreeBR">
    <intent-filter>
        <action android:name="andexam.ver6.FREEWIFI" />
    </intent-filter>
</receiver>

BR이 수신되면 html을 다운받는 새로운 Activity를 실행하는 코드를 작성해 두었다.

동적 BR 등록

매니페스트에 등록하지 않고 코드상에서도 할 수 있다.
즉 필요할 때만 등록하고 해지 시킬 수 있다.

  • Intent registerReceiver (BroadcastReceiver receiver, IntentFilter filter)
  • void unregisterReceiver (BroadcastReceiver receiver)

등록 메서드로 BR 객체와 인텐트 필터를 직접 전달한다.

  • onResume() 등록
  • onPause() 해제

코드는 아래와 같다.

public class OnSaveZone extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.onsavezone);
	}
	
	public void onResume() {
		super.onResume();
		IntentFilter filter = new IntentFilter();
		filter.addAction("andexam.ver6.SAVEZONE");
		registerReceiver(mSaveZoneBR, filter);
	}
	
	public void onPause() {
		super.onPause();
		unregisterReceiver(mSaveZoneBR);
	}

	BroadcastReceiver mSaveZoneBR = new BroadcastReceiver() {
		public void onReceive(Context context, Intent intent) {
			Toast.makeText(context, "아싸! 공짜다.", 
					Toast.LENGTH_LONG).show();
		}
	};
}

배터리 감시용 코드

public class WatchBattery extends Activity {
	TextView mStatus;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.watchbattery);

		mStatus = (TextView)findViewById(R.id.status);
	}
	
	public void onResume() {
		super.onResume();
		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_BATTERY_CHANGED);
		filter.addAction(Intent.ACTION_BATTERY_LOW);
		filter.addAction(Intent.ACTION_BATTERY_OKAY);
		filter.addAction(Intent.ACTION_POWER_CONNECTED);
		filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
		registerReceiver(mBRBattery, filter);
	}	

	public void onPause() {
		super.onPause();        
		unregisterReceiver(mBRBattery);
	}

	BroadcastReceiver mBRBattery = new BroadcastReceiver() {
		int Count = 0;
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			Count++;
			if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
				onBatteryChanged(intent);
			}
			if (action.equals(Intent.ACTION_BATTERY_LOW)) {
				Toast.makeText(context, "배터리 위험 수준", Toast.LENGTH_LONG).show();
			}
			if (action.equals(Intent.ACTION_BATTERY_OKAY)) {
				Toast.makeText(context, "배터리 양호", Toast.LENGTH_LONG).show();
			}
			if (action.equals(Intent.ACTION_POWER_CONNECTED)) {
				Toast.makeText(context, "전원 연결됨", Toast.LENGTH_LONG).show();
			}
			if (action.equals(Intent.ACTION_POWER_DISCONNECTED)) {
				Toast.makeText(context, "전원 분리됨", Toast.LENGTH_LONG).show();
			}
		}

		public void onBatteryChanged(Intent intent) {
			int plug, status, scale, level, ratio;
			String sPlug = "";
			String sStatus = "";

			if (intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false) == false){
				mStatus.setText("배터리 없음");
				return;
			}

			plug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
			status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 
					BatteryManager.BATTERY_STATUS_UNKNOWN);
			scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
			level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
			ratio = level * 100 / scale;

			switch (plug) {
			case BatteryManager.BATTERY_PLUGGED_AC:
				sPlug = "AC";
				break;
			case BatteryManager.BATTERY_PLUGGED_USB:
				sPlug = "USB";
				break;
			default:
				sPlug = "Battery";
				break;
			}

			switch (status) {
			case BatteryManager.BATTERY_STATUS_CHARGING:
				sStatus = "충전중";
				break;
			case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
				sStatus = "충전중 아님";
				break;
			case BatteryManager.BATTERY_STATUS_DISCHARGING:
				sStatus = "방전중";
				break;
			case BatteryManager.BATTERY_STATUS_FULL:
				sStatus = "만충전";
				break;
			default:
			case BatteryManager.BATTERY_STATUS_UNKNOWN:
				sStatus = "알 수가 없어";
				break;
			}

			String str = String.format("수신 회수:%d\n연결: %s\n상태:%s\n레벨:%d%%", 
					Count, sPlug, sStatus, ratio);
			mStatus.setText(str);
		}
	};
}


'Computer Science > Android Application' 카테고리의 다른 글

NDK 사용  (0) 2017.08.20
Preference  (0) 2017.08.20
Service  (0) 2017.08.16
Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04

Service


  • 백그라운드 데몬: 배경에서 계속 실행되는 프로세스이다. 클라이언트가 가동시켜 놓기만 하면 사용자의 명령이 없이도 지속적으로 실행된다. MP3 플레이어가 대표적인 예이다.

  • 바운드 서비스: 클라이언트를 위해 특정한 기능을 제공하는 역할을 한다. 자신의 기능을 메서드로 노출시키며 클라이언트는 메서드를 호출함으로써 서비스를 이용한다. 다른 운영체제의 COM, CORBAR에 대응되는 개념이다.

두 개의 서로다른 서비스는 Life Cycle에서 차이가 존재 한다.

백그라운드 데몬 형태 예제

백그라운드에서 동작하며 뉴스 데이터를 출력하는 예제

public class NewsService extends Service {
    boolean mQuit;

    public void onCreate() {
        super.onCreate();
    }

    public void onDestroy() {
        super.onDestroy();

        Toast.makeText(this, "Service End", Toast.LENGTH_SHORT).show();
        mQuit = true;
    }

    public int onStartCommand (Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);

        mQuit = false;
        NewsThread thread = new NewsThread(this, mHandler);
        thread.start();
        return START_STICKY;
    }

    public IBinder onBind(Intent intent) {
        return null;
    }

    class NewsThread extends Thread {
        NewsService mParent;
        Handler mHandler;
        String[] arNews = {
                "일본, 독도는 한국땅으로 인정",
                "번데기 맛 쵸코파이 출시",
                "춘천 지역에 초거대 유전 발견",
                "한국 월드컵 결승 진출",
                "국민 소득 6만불 돌파",
                "학교 폭력 완전 근절된 것으로 조사",
                "안드로이드 점유율 아이폰을 앞질렀다",
        };
        public NewsThread(NewsService parent, Handler handler) {
            mParent = parent;
            mHandler = handler;
        }
        public void run() {
            for (int idx = 0;mQuit == false;idx++) {
                Message msg = new Message();
                msg.what = 0;
                msg.obj = arNews[idx % arNews.length];
                mHandler.sendMessage(msg);
                try { Thread.sleep(5000);} catch (Exception e) { ; }
            }
        }
    }

    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            if (msg.what == 0) {
                String news = (String)msg.obj;
                Toast.makeText(NewsService.this, news, Toast.LENGTH_SHORT).show();
            }
        }
    };
}

매니페스트 등록 내용

<service android:name=".c30_service.NewsService" android:enabled="true">
    <intent-filter>
        <action android:name="andexam.ver6.NEWS" />
    </intent-filter>
</service>

주요 착안점

  • Service는 메인 스레드에서 실행되므로 긴 작업을 위해서 스레드를 별도로 생성함
  • Thread는 toast update가 불가능 하므로 handler를 이용해서 UI를 업데이트함
  • 원격 호출을 지원하지 않으므로 onBind는 null을 리턴한다.

서비스의 시작과 종료 방법
같은 패키지에 있는 서비스의 경우 서비스 클래스명으로 지정하는 것이 가장 간편하다.

  • ComponentName startService (Intent service)
  • boolean stopService (Intent service)

호출은 중첩되지 않으므로 몇 번을 시작했건 간에 stopService를 한 번만 호출해도 즉시 종료된다.

서비스는 유일한 이름이 있으므로 외부 패키지나 외부 프로그램에서도 호출 가능하다. 외부에서 다른 패키지에 속한 클래스를 직접 참조할 수 없으므로 이름을 사용하여 다음과 같이 호출한다.
itent = new Intent(“packageName.className”);
startService(intent);
하지만 5.0 이후로는 보안상 문제가 있어서 금지 되었다. Service intent must be explicit예외가 발생된다.

IntentService

비동기적으로 기능을 해결해주는 컴포넌트이다.
startService(Intent)로 요청하면 작업 스레드를 생성해서 해당 작업을 실행시켜 준다.

work queue processor패턴으로 일반적으로 이용되는 것이 IntentService이다.
즉, main thread에서 수행해야할 작업을 쉽게 offloading 하는 역할을 한다.

요약

  • The IntentService runs on a separate worker thread.
  • The IntentService is triggered using an Intent, it spawns a new worker thread and the method onHandleIntent() is called on this thread.
  • The IntentService cannot run tasks in parallel. Hence all the consecutive Intents will go into the message queue for the worker thread and will execute sequentially.
  • The IntentService stops the service after all start requests have been handled, so you never have to call stopSelf().
public class NewsService2 extends IntentService {
    public NewsService2() {
        super("NewsThread");
    }

    protected void onHandleIntent(Intent intent) {
        String[] arNews = {
                "4T SSD 10만원대 진입",
                "갤럭시 S8 판매 호조",
                "핵융합 발전소 건설 완료",
        };
        for (int idx = 0;idx < arNews.length;idx++) {
            Message msg = new Message();
            msg.what = 0;
            msg.obj = arNews[idx % arNews.length];
            mHandler.sendMessage(msg);
            try { Thread.sleep(5000);} catch (Exception e) { ; }
        }
    }

    Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            if (msg.what == 0) {
                String news = (String)msg.obj;
                Toast.makeText(NewsService2.this, news, Toast.LENGTH_SHORT).show();
            }
        }
    };
}

Bind Service

Bind service는 특정 기능을 제공하는 메서드를 클라이언트에게 노출한다.
클라이언트는 서비스에 연결하여 메서드를 호출함으로써 통신을 수행하며 자신이 직접 구현하지 않은 기능을 사용한다.

연산을 처리하는 부분

public class CalcService extends Service {
    public class CalcBinder extends Binder {
        CalcService getService() { return CalcService.this; }
    }

    CalcBinder mBinder = new CalcBinder();

    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public int getLCM(int a, int b) throws RemoteException {
        int i;
        for (i = 1; ;i++) {
            if (i % a == 0 && i % b == 0) break;
        }
        return i;
    }

    public boolean isPrime(int n) throws RemoteException {
        int i;
        for (i = 2;i < n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }
}

onBind를 호출하는 부분

public class CalcClient extends Activity {
    CalcService mCalc;
    TextView mResult;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.calcclient);

        mResult = (TextView)findViewById(R.id.result);
    }

    public void mOnClick(View v) {
        switch (v.getId()) {
            case R.id.btnLCM:
                int LCM = 0;
                try {
                    LCM = mCalc.getLCM(6, 8);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mResult.setText("6과 8의 최소 공배수 = " + LCM);
                break;
            case R.id.btnPrime:
                boolean prime = false;
                try {
                    prime = mCalc.isPrime(7);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mResult.setText("7의 소수 여부 = " + prime);
                break;
        }
    }

    public void onResume() {
        super.onResume();
        Intent intent = new Intent(this, CalcService.class);
        bindService(intent, srvConn, BIND_AUTO_CREATE);
    }

    public void onPause() {
        super.onPause();
        unbindService(srvConn);
    }

    ServiceConnection srvConn = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder binder) {
            mCalc = ((CalcService.CalcBinder)binder).getService();
        }

        public void onServiceDisconnected(ComponentName className) {
            mCalc = null;
        }
    };
}


클라이언트에서 서비스에 연결하거나 해제할 때 다음 메서드를 호출하여 바인딩한다. 바인딩이란 클라이언트와 서비스를 연결하는 동작이다.

  • boolean bindService(Intent service, ServiceConnection conn, int flags)
  • void unbindService (ServiceConnection conn)

bindService의 첫 번째 인수는 같은 패키지에 있으면 클래스명으로 지정하고 외부에 있다면 서비스의 액션명을 사용한다.
두 번째 인수 conn은 서비스가 연결, 해제될 때의 동작을 정의하는 연결 객체이다.
서비스를 사용하는 클라이언트는 ServiceConnection 인터페이스를 구현하는데 클라이언트와 서비스가 연결되거나 해제될 때 호출되는 콜백 메서드를 정의한다. 아래의 코드가 이에 해당한다.

    ServiceConnection srvConn = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder binder) {
            mCalc = ((CalcService.CalcBinder)binder).getService();
        }

        public void onServiceDisconnected(ComponentName className) {
            mCalc = null;
        }
    };

onResume()에서 bindService(intent,srvConn,BIND_AUTO_CREATE)를 하기 때문에 해당 callback method를 구현해야 한다.
만약 그냥 별도의 anonymous class 생성 없이 activity에 구현 했다고 하면 this가 된다.

마지막 인수 flag는 서비스 바인딩 바익을 지정하는데 통상 BIND_AUTO_CREATE로 지정하여 서비스를 자동으로 가동시킨다.

아주 복잡한 기능들을 공통으로 서비스에 작성해두고 이것을 원격으로 콜하는 방식으로 구현할 수 있다.

완전 원격 서비스

서비스는 로컬에서뿐만 아니라 원격으로도 호출할 수 있는데 그렇게 하려면 자신의 메서드 목록을 인터페이스로 정의해야 한다.
이는 단순히 메서드의 원형을 선언하는 것과 수준이 다른데 원격에서 호출되는 메서드는 응용 프로그램의 경계를 넘어서 인수를 전달해야 하는 어려움이 있다. 각 응용프로그램이 사용하는 메모리가 완전히 분리되어 있어 통상의 방법으로는 인수를 넘기기 어렵다.

따라서 전달할 수 있는 인수의 타입은 자바기본 타입이나 Parcelable정도로 제한되며 그 외에도 몇 가지 제약이 존재한다.

자바 수준에서 인터페이스를 직접 정의하기는 대단히 어려워 원격 인터페이스를 정의하는 AIDL이라는 별도의 언어를 제공한다.

interface A{
    int getLCM();
    boolean isPrime();
}

위와 같이 인터페이스안에 노출할 메서드의 선언문만 작성해 두면 AIDL 컴파일러가 이 인터페이스를 구현하는 자바 파일을 생성해 준다.

예전에 구현했던 내용
네이버 블로그 포스트


'Computer Science > Android Application' 카테고리의 다른 글

Preference  (0) 2017.08.20
Broadcast Receiver  (0) 2017.08.16
Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
이벤트 (Event)  (0) 2017.08.04

Activity


엑티비티 생성과 호출

코드

public class CallActivity extends Activity {