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 {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.callactivity);
	}

	public void mOnClick(View v) {
		Intent intent = new Intent(this, SubActivity.class);
		startActivity(intent);
	}
}
<activity android:name=".c17_activity.CallActivity" android:label="CallActivity" />
  • name의 경우 액티비티의 패키지명을 포함한 풀 경로를 지정하되 같은 패키지에 속해 있을 때는 앞에 . 을 찍거나 클래스명만 적어도 무방하다.

Intent

생성자의 종류

  • 1 Intent (): 잘 사용 안함
  • 2 Intent (Intent o)
  • 3 Intent (String action [, Uri uri])
  • 4 Intent (Context packageContext, Class<?> cls): 서브 액티비티 호출할 때 사용
  • 5 Intent (String action, Uri uri, Context packageContext, Class<?> cls)

4 번째 구조로 만든 intent이다. Explicit Intent라고 한다.

Intent intent = new Intent(this, SubActivity.class)
startActivity(intent);

Implicit Intent

  • Action
    • ACTION_CALL // Activity // 통화를 시작한다.
    • ACTION_BATTERY_LOW // BR // 배터리가 부족하다.

거의 왠만한 액션들은 모두 정의가 되어 있다. String 타입으로 define 되어 있는 것이다.
사용자가 개인적으로 정의해서 사용할 수도 있다. 그 경우 중복을 막기위해서 package name을 앞에 붙인다.
액션을 조사하거나 변경 할때는 gecAction setAction을 사용 한다.

  • Data: 목적어에 해당 한다.
  • Type: 자동으로 판별이 가능하다.
    • http:// 웹페이지
    • te: 전화번호
    • jpg 이미지
  • Category: 여러개를 줄 수 있다.
  • Component: 이 속성이 지정되면 Explicit로 바뀐다.
  • Extras: 추가적인 정보를 전달 할 때 사용 한다. 키와 값의 쌍으로 저장되어 컴포넌트에게 전달 되며 리턴 용도로도 사용된다. 이름만 중복되지 않으면 얼마든지 많은 정보를 전달할 수 있다.
    • putExtra로 여러 벌 오버로딩 되어 있으므로 저장되는 정보 타입을 그냥 넣어주면 알아서 맞게 동적 로딩 된다.
    • getIntExtra getStringExtra등으로 제공된다.
  • Flags: 액티비티를 띄울 방법이나 액티비티를 관리하는 방법 등에 대한 옵션 정보가 저장된다.

액티비티간의 통신

값의 전달

  • Intent putExtra (String name, int value)
  • Intent putExtra (String name, String value)
  • Intent putExtra (String name, boolean value)

값 읽기

  • int getIntExtra (String name, int deafultValue)
  • String getStringExtra (String name)
  • boolean getBooleanExtra (String name, boolean defaultValue)

리턴값을 돌려줘야 할 경우 다음의 메서드를 사용 한다.

  • void startActivityForResult (Intent intent, int requestCode)
    • 두 번째 인수가 하나 더 추가되는데 호출한 대상을 나타내는 식별자이며 리턴 시에 누구에 대한 리턴인지 구분할 때 사용한다.

호출된 액티비티가 종료되면 다음의 메서드가 호출 된다.

  • void onActivityResult (int requestCode, int resultCode, Intent data)
    • requestCode는 액티비티를 호출할 때 전달한 요청코드이며 requestCode는 액티비티의 실행 결과이다.

예제코드
호출하는 부분

public class CommActivity extends Activity {
	TextView mText;
	final static int ACT_EDIT = 0;
	
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.commactivity);

		mText = (TextView)findViewById(R.id.text);
	}
	
	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnedit:
			Intent intent = new Intent(this, ActEdit.class);
			intent.putExtra("TextIn", mText.getText().toString());
			startActivityForResult(intent,ACT_EDIT);
			break;
		}
	}
	
	protected void onActivityResult (int requestCode, int resultCode, Intent data) {
		switch (requestCode) {
		case ACT_EDIT:
			if (resultCode == RESULT_OK) {
				mText.setText(data.getStringExtra("TextOut"));
			}
			break;
		}
	}
}

처리하는 부분

public class ActEdit extends Activity {
	EditText mEdit;

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

		mEdit = (EditText)findViewById(R.id.stredit);

		Intent intent = getIntent();
		String text = intent.getStringExtra("TextIn");
		if (text != null) {
			mEdit.setText(text);
		}
	}

	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnok:
			Intent intent = new Intent();
			intent.putExtra("TextOut", mEdit.getText().toString());
			setResult(RESULT_OK,intent);
			finish();
			break;
		case R.id.btncancel:
			setResult(RESULT_CANCELED);
			finish();
			break;
		}
	}
}
  • getIntent
  • void setResult(int resultCode, Intent data)
  • getStringExtra

Implicit Intent

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

	public void mOnClick(View v) {
		Intent intent;
		switch (v.getId()) {
		case R.id.web:
			intent = new Intent(Intent.ACTION_VIEW, 
					Uri.parse("http://www.google.com"));
			startActivity(intent); 
			break;
		case R.id.dial:
			intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:015-123-4567"));
			startActivity(intent);
			break;
		case R.id.picture:
			intent = new Intent(Intent.ACTION_VIEW);
			String sd = Environment.getExternalStorageDirectory().getAbsolutePath();
			Uri uri = Uri.fromFile(new File(sd + "/test.jpg"));
			intent.setDataAndType(uri, "image/jpeg");
			startActivity(intent);
			break;
		case R.id.other:
			intent = new Intent(Intent.ACTION_MAIN);
			intent.setComponent(new ComponentName("exam.memo", "exam.memo.Memo"));
			//intent.setClassName("exam.memo", "exam.memo.Memo");
			startActivity(intent);
			break;
		}
	}
}

액티비티 라이프 사이클

엑티비티의 라이프 사이클은 아래와 같다.


  • onCreate: 액티비티를 초기화한다. 중지했다가 재시작하는 경우라면 액티비티의 이전 상태 정보인 Bundle이 전달되며 이 정보대로 재초기화한다.

  • onRestart: 재시작될 때 호출된다. 특별히 할일은 없다.

  • OnResume: 사용자와 상호작용을 하기 직전에 호출된다. 이 단계에서 스택의 제일 위로 올라온다.

  • onPause: another activity comes into the foreground. 단순히 Pause고 곧 있다가 실행이 될 때를 의미한다. 요즘 멀티 엑티비티가 되면서 더 빈번하게 차이점이 생기는 문제이다. 포커스는 잃었지만 사용자에게 보이는 상태이다. 위쪽에 다른 엑티비티가 있지만 화면 전체를 다 가리지 않았거나 반투명한 경우가 이에 해당한다. 즉 뭔가 다이얼로그 창 같은게 위에 있는 상태이다. 살아 있는 상태와 같지만 시스템에 의해 강제 종료될 수 있다. 그리고 다른 액티비티가 실행 될 때도 onPause 이후에 onCreate가 수행 되므로 너무 오랜시간을 작업해서는 안된다. 즉, 이 단계에서 미저장한 데이터가 있으면 저장하고 애니메이션은 중지해야 한다. 이 메서드가 리턴되어야 새 액티비티가 활성화되므로 시간을 너무 오래 끌어서는 안 된다.

  • onStop: The activity is no longer visible. 이 상태에서는 액티비티가 완전히 가려져서 사용자에게 보이지 않는 상태이다. 이 상태에서는 액티비티가 백그라운드에 있는것으로 간주 된다. 정지되어 있는 동안 액티비티 인스턴스 및 멤버 변수와 같은 모든 상태 정보가 유지되지만, 어떠한 코드도 실행할 수 없다.

  • onDestory: 액티비티가 파괴될 때 호출된다. 시스템에 의해 강제로 종료되는 것인지 아니면 finish 메서드 호출에 의해 스스로 종료하는 것인지 isFinishing 메서드로 조사할 수 있다. 대부분의 앱은 이 메서드를 구현할 필요가 없다. 왜냐하면 액티비티와 함께 지역 클래스 참조가 소멸되고 액티비티가 onPause() 및 onStop 중에 대부분 정리 작업을 수행하기 때문이다. 하지만 액티비티가 onCreate 중에 생성한 백그라운드 스레드 또는 제대로 닫지 않으면 메모리 누수를 야기할 수 있는 다른 장시간 실행되는 리소스를 포함하는 경우 onDestroy중에 액티비티를 중단시켜야 한다. finish()를 호출한 경우 onPuase onStop을 거치지 않고 즉시 onDestroy를 호출 한다.

호출 순서 분석
각각의 callback 함수에 logcat으로 메시지를 출력하게 한후 새로운 액티비티를 실행해서 lifecycle을 분석 한다.

public class ActParent extends Activity {
	static final String TAG = "ActParent"; 

	public void onCreate(Bundle savedInstanceState) {
		Log.i(TAG, "onCreate");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.actparent);
	}
	
	public void mOnClick(View v) {
		Log.i(TAG, "startActivity");
		Intent intent = new Intent(this, ActChild.class);
		startActivity(intent);
	}
	
	public void onStart() {
		super.onStart();
		Log.i(TAG, "onStart");
	}

	public void onResume() {
		super.onResume();
		Log.i(TAG, "onResume");
	}

	public void onPause() {
		super.onPause();
		Log.i(TAG, "onPause");
	}

	public void onRestart() {
		super.onRestart();
		Log.i(TAG, "onRestart");
	}

	public void onStop() {
		super.onStop();
		Log.i(TAG, "onStop");
	}

	public void onDestroy() {
		super.onDestroy();
		Log.i(TAG, "onDestroy");
	}
}
public class ActChild extends Activity {
	static final String TAG = "ActChild"; 

	public void onCreate(Bundle savedInstanceState) {
		Log.i(TAG, "onCreate");
		super.onCreate(savedInstanceState);
		setContentView(R.layout.actchild);
	}
	
	public void onStart() {
		super.onStart();
		Log.i(TAG, "onStart");
	}

	public void onResume() {
		super.onResume();
		Log.i(TAG, "onResume");
	}

	public void onPause() {
		super.onPause();
		Log.i(TAG, "onPause");
	}

	public void onRestart() {
		super.onRestart();
		Log.i(TAG, "onRestart");
	}

	public void onStop() {
		super.onStop();
		Log.i(TAG, "onStop");
	}

	public void onDestroy() {
		super.onDestroy();
		Log.i(TAG, "onDestroy");
	}
}

실행 결과 분석

또 다른 예제로 Rotation했을 때를 분석 한다.

나중에 핸들러, 스레드, 연기된 러너블등이 개입하면 좀 더 복잡해 진다.

상태 저장

Rotation으로 테스트 한다. 쉽게 강제 종료를 시뮬레이션 할 수 있다.

시스템은 액티비티를 종료하기 직전에 onSaveInstanceState 메서드를 호출하여 정보를 저장할 기회를 제공한다.
인수로 Bundle객체를 전달하는데 Bundle은 문자열로 된 이름과 임의 타입의 값을 저장하는 일종의 맵이다.

인텐트의 Extras 멤버가 바로 Bundle 타입이며 임의의 정보를 원하는 만큼 저장할 수 있다.
따라서 onSaveInstanceState를 재정의하여 저장하고자 하는 값을 번들에 저장해 두고 종료한다.

아무리 강제종료 되어도 onSaveInstanceState는 한번 호출되니 이곳에서 Bundle에 key와 value 쌍으로 데이터를 저장하면 된다.

아래 그림은 공식 홈페이지에서 발췌 한 것이다. 이것만으론 이해하기 어려워서 추가적인 실험도 아래와 같이 진행 했다.

시스템은 액티비티 중지 작업을 시작할 때 onSaveInstanceState()(1)를 호출한다. 따라서 Activity 인스턴스가 재생성되어야 하는 경우 저장하고자 하는 추가 상태 데이터를 지정할 수 있다. 액티비티가 소멸되고 동일한 인스턴스가 재생성되어야 하는 경우, 시스템은 onCreate() 메서드(2) 및 onRestoreInstanceState() 메서드(3) 모두에 (1)에서 정의된 상태 데이터를 전달한다.

onSaveInstanceState 호출 시점

rotation을 할 경우 onPause이후에 호출 된다.

08-20 14:04:58.340 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onSaveInstanceState
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onStop
08-20 14:04:58.341 10849-10849/andexam.ver6 I/SaveState2: onDestroy

home버튼을 누를 경우 onPause이후에 호출 된다.

08-20 14:09:00.965 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:09:00.996 10849-10849/andexam.ver6 I/SaveState2: onSaveInstanceState
08-20 14:09:00.996 10849-10849/andexam.ver6 I/SaveState2: onStop

back버턴을 눌러서 destropy를 호출시키면 실행 되지 않는다.

08-20 14:08:39.397 10849-10849/andexam.ver6 I/SaveState2: onPause
08-20 14:08:39.709 10849-10849/andexam.ver6 I/SaveState2: onStop
08-20 14:08:39.709 10849-10849/andexam.ver6 I/SaveState2: onDestroy

phone call일 때도 호출되지 않는다.

  • onPause-> onResume

onRestoreInstance 호출 시점

rotation할 때는 onStart이후에 호출된다.

08-20 14:04:58.352 10849-10849/andexam.ver6 I/SaveState2: onStart
08-20 14:04:58.352 10849-10849/andexam.ver6 I/SaveState2: onRestoreInstanceState
08-20 14:04:58.353 10849-10849/andexam.ver6 I/SaveState2: onResume

home버튼을 누르고 다시 시작 했을 때는 실행 되지 않는다.

08-20 14:10:34.761 10849-10849/andexam.ver6 I/SaveState2: onRestart
08-20 14:10:34.762 10849-10849/andexam.ver6 I/SaveState2: onStart
08-20 14:10:34.762 10849-10849/andexam.ver6 I/SaveState2: onResume

phone call일 때도 호출되지 않는다.

  • onPause-> onResume

뭔가 애매한대 reference문서의 가이드 라인은 아래와 같다.

void onRestoreInstanceState (Bundle savedInstanceState)

  • This method is called between onStart() and onPostCreate(Bundle).

void onSaveInstanceState (Bundle outState)

  • If called, this method will occur before onStop(). There are no guarantees about whether it will occur before or after onPause().

저장 복구 패턴 1

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if (savedInstanceState == null) {
			x = 50;
		} else {
			x = savedInstanceState.getInt("x");
		}
		y = 50;
		vw = new MyView(this);
		vw.setFocusable(true);
		vw.setFocusableInTouchMode(true);
		setContentView(vw);
	}

	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putInt("x", x);
	}

onCreate에서 하지 않고 onRestoreInstanceState에서 수행 해도 된다.
onRestoreInstanceState는 내부 적으로 복원할 저장 상태가 있을 경우에만 호출되므로 Bundle null인지 확인할 필요는 없다.

	public void onRestoreInstanceState(Bundle outState) {
		super.onRestoreInstanceState(outState);
		x = outState.getInt("x");
	}

앱이 종료 되었다가 실행되어도 값을 저장하고 있으려면, 실행 시간 동안만 유지되는 Bundle에 저장해서는 안된다.
이 때는 Preference를 이용해서 저장한다.

public class SaveState3 extends Activity {
	private MyView vw;
	int x;
	int y;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if (savedInstanceState == null) {
			x = 50;
		} else {
			x = savedInstanceState.getInt("x");
		}
		SharedPreferences pref = getSharedPreferences("SaveState",0);
		y = pref.getInt("y", 50);
		vw = new MyView(this);
		vw.setFocusable(true);
		vw.setFocusableInTouchMode(true);
		setContentView(vw);
	}

	protected void onPause() {
		super.onPause();
		SharedPreferences pref = getSharedPreferences("SaveState",0);
		SharedPreferences.Editor edit = pref.edit();
		edit.putInt("y", y);
		edit.commit();
	}

	public void onSaveInstanceState(Bundle outState) {
		outState.putInt("x", x);
	}
}

객체 저장

객체를 신속하게 저장 할 때는 자바의 Serialization을 사용 한다.
사용하는 메서드는 다음과 같다.

  • void putSerializable (String key, Serializable value)
  • Serializable getSerializable (String key)

이름과 객체만 전달하면 저장 및 복구가 자동으로 수행된다.
저장 하려고 하는 객체는 interface Serializable 키워드만 상속 받으면 된다.
하지만 interface인대도 구현할 매서드가 존재하지 않는다. 이러한 interface Marker Interface라고 부른다.
이렇게 하는 이유는 다중상속을 가능하게하고 추후에 instanceof에 의해서 해당 class가 Serilalizable에서 파생되었는지 알아내고 그에 대한 처리를 하기 위함이다.

코드

	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putSerializable("Curve", arVertex);
	}

	public class Vertex implements Serializable {
		private static final long serialVersionUID = 100L;
		Vertex(float ax, float ay, boolean ad) {
			x = ax;
			y = ay;
			draw = ad;
		}
		float x;
		float y;
		boolean draw;
	}
	

또 다른 방법 Parcelable인터페이스를 구현하는 방법
Parcel은 원래 프로세스간의 통신을 위한 메시지 저장 장치인데 번들이 꾸러미 입출력 기능을 제공하므로 같은 프로세스의 세션간 데이터 저장 및 복구에도 사용 가능하다.

저장 대상인 클래스는 Parcelable 인터페이스를 상속받아야 하며,
describeContents() writeToParcel을 구현 해야한다.

class Vertex implements Parcelable {
	Vertex(float ax, float ay, boolean ad) {
		x = ax;
		y = ay;
		draw = ad;
	}
	float x;
	float y;
	boolean draw;

	public int describeContents() {
		return 0;
	}

	public void writeToParcel(Parcel dest, int flags) {
		dest.writeFloat(x);
		dest.writeFloat(y);
		dest.writeBooleanArray( new boolean[] {draw} );
	}
	
	public static final Parcelable.Creator<Vertex> CREATOR = new Creator<Vertex>() {
		public Vertex createFromParcel(Parcel source) {
			int x = source.readInt();
			int y = source.readInt();
			boolean[] td = new boolean[1];
			source.readBooleanArray(td);
			return new Vertex(x, y, td[0]);
		}

		public Vertex[] newArray(int size) {
			return new Vertex[size];
		}
		
	};
}
  • describeContents() 별다른 기능이 없으믈 0을 리턴한다.
  • writeToParcel이 부분을 구현 해야 한다.
    boolean타입에 대해서는 단일값 입출력 메서드를 제공하지 않으므로 배열 형태로 포장해서 출력 해야 한다.

CREATOR라는 이름의 정적 객체는 저장된 정보를 Parcel로 부터 읽어들여 객체를 생성하는 역할을 한다.

생성 방법

  • publci static final Parcelable.Creator CREATOR = new Creator()
  • T createFromParcel (Parcel source)
    • 해당 메서드는 source로 부터 정수 두 개와 논리값 하나를 순서대로 읽어 Vertex 객체 하나를 생성한다.
  • Vertex[] newArray(int size)
    • newArray 메서드는 전달된 크기대로 배열을 생성하여 리턴한다.

참고문헌

  1. d.android: 액티비티 재생성
  2. d.android: 액티비티 시작
  3. 안드로이드 정복 4판, 김상형


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

Broadcast Receiver  (0) 2017.08.16
Service  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
이벤트 (Event)  (0) 2017.08.04
Gradle  (0) 2016.06.03

쓰레드 (Thread)


Thread 생성 기법

Thread를 생성하는 방법은 아래 2가지 이다.

Thread()
Thread(Runnable runnable)

첫 번째 방법인 Thread의 경우 run에 해당 기능을 구현한다.

// Activity

BackThread thread = new BackThread():
thread.setDaemon(true);
thread.start();

class BackThread extends Thread {
	public void run(){
		while(true){
			mValue++;
			try {Thread.sleep(1000);} catch {InterruptedException e){;}
		}
	}
}

두 번째 방법인 Runnable은 interface 내부에 run을 구현 하는 방식이다.

Back Runnable runnable = new BackRunnable();
Thread thread = new Thread(runnable);
thread.setDeamon(true); // main thread가 종료 될 때만 종료된다 라는 의미이다.
thread.start();

class BackRunnable implements Runnable {
	public void run(){
		while(true){
			mBackValue ++;
			try {Thread.sleep(1000);} catch (interruptedException e) {;}
		}
	}
}

구지 복잡하게 자바에서 Runnable Interface를 지원하는 이유는 다중상속을 지원하지 않는 특성상
Thread만 구현 하고 싶을 때 Runnable을 이용하라는 의미이다.

Thread간의 통신 Handler

Handler 클래스로 표현되는 핸들러는 Tread간에 메시지 Runnable 객체를 통해 메시지를 주고 받는 장치이다.

  • 핸들러는 생성하는 Thread에 부착 된다. 부착된 Thread의 메시지큐를 통해서 다른 Thread와 통신 한다.
  • Handler는 Message를 MessageQueue에 넣는 기능과 MessageQueue에서 꺼내 처리하는 기능을 함께 제공한다.

Handler 생성자 이해

  • Handler()
  • Handler(Handler.Callback callback)
  • Handler(Looper looper)
  • Handler(Looper looper, Handler.Callback callback)

메시지 수신에 사용되는 메서드

  • void handleMessage (Message msg)
    인수 Message는 아래와 같은 추가 필드를 가진다.
  • int what: 메시지의 의미를 설명한다. 의미가 정해져 있지는 않으며 핸들러별로 지역적이므로 다른 핸들러와 충돌할 위험은 없다.
  • int arg1: 메시지의 추가 정보이다.
  • int arg2: 메시지의 추가 정보이다.
  • Object obj: 정수만으로 메시지를 기술할 수 없을 때 임의의 객체를 보낸다.
  • Messenger replyTo: 메시지에 대한 응답을 받을 객체를 지정한다.

메시지를 보내는 쪽에서는 전달하고자 하는 내용을 Message 객체에 저장하여 핸들러로 전송한다.
사용되는 메서드는 다음과 같다.

  • boolean Handler.sendEmptyMessage (int what)
  • boolean Handler.sendMessage (Message msg)
  • boolean Handler.sendEmptyMessageAtTime(int what, long uptimeMillis)
  • boolean Handler.sendMessageAtTime(Message msg, long uptimeMillis)
  • boolean sendMessageAtFrontOfQueue (Message msg)

실행 코드인 Runnable object를 전송하는 메서드는 다음과 같다.

  • boolean Handler.post(Runnable r)
  • boolean Handler.postDelayed(Runnable r, long delayMillis)
  • boolean Handler.postAtTime(Runnable r, Object token, long uptimeMillis)
  • boolean Handler.postAtTime(Runnable r, long uptimeMillis)
  • boolean Handler.postAtFrontofQueue(Runnable r)

메시지 전송 예제

public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

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

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				mHandler.sendEmptyMessage(0);
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.what == 0) {
				mBackText.setText("BackValue : " + mBackValue);
			}
		}
	};
}

Runnable 객체 전송 예제
위 예제와 다르게 실행 객체를 전송하므로 mHandler는 구체적으로 정의할 필요 없이 생성만 해두면
내부 Runnable Code가 실행 된다.

public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

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

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				mHandler.post(new Runnable() {
					public void run() {
						mBackText.setText("BackValue : " + mBackValue);
					}
				});
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}

	Handler mHandler = new Handler();
}

위 예제에서는 Anonymous class 기법이 사용 되었다.

// non-anonymous class
Runnable runUpdate = new Runnable(){
	public void run() {
		mBackText.setText();
	}
};
mHandler.post(runUpdate);

// anonymous class
mHandler.post(new Runnable(){
	public void run() {
		mBackText.setText();
	}
};

runOnUiThread를 통한 Main UI Thread와의 통신

  • runOnUiThread 메서드 호출 위치가 UI 스레드라면 Runnable은 즉시 실행된다.

  • 작업 스레드에서 runOnUiThread가 호출 되었다면 자동으로 UI 스레드 큐를 찾아서 Runnable을 그 뒤에 붙여 준다. 즉, RunnableHandler.post를 하지 않아도 내부적으로 이와 동일한 작업을 해주는 것이다. mHandler가 없어도 상관 없기 때문에 훨씬 간편하다.

익명 class를 runnable로 runOnUiThread로 전달하는 예제

//* runOnUiThread 메서드로 익명 임시 객체 전달
public class HandlerTest extends Activity {
	int mMainValue = 0;
	int mBackValue = 0;
	TextView mMainText;
	TextView mBackText;

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

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);

		BackThread thread = new BackThread();
		thread.setDaemon(true);
		thread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	class BackThread extends Thread {
		public void run() {
			while (true) {
				mBackValue++;
				runOnUiThread(new Runnable() {
					public void run() {
						mBackText.setText("BackValue : " + mBackValue);
					}
				});
				try { Thread.sleep(1000); } catch (InterruptedException e) {;}
			}
		}
	}
}

Activity 외부에 Thread가 선언된 좀 더 일반적인 경우

public class HandlerTest extends Activity {
	int mMainValue = 0;
	TextView mMainText;
	TextView mBackText;
	BackThread mThread;

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

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);
		mThread = new BackThread(mHandler);
		mThread.setDaemon(true);
		mThread.start();
	}

	public void mOnClick(View v) {
		mMainValue++;
		mMainText.setText("MainValue : " + mMainValue);
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			if (msg.what == 0) {
				mBackText.setText("BackValue : " + msg.arg1);
			}
		}
	};
}

class BackThread extends Thread {
	int mBackValue = 0;
	Handler mHandler;

	BackThread(Handler handler) {
		mHandler = handler;
	}

	public void run() {
		while (true) {
			mBackValue++;
			Message msg = new Message();
			msg.what = 0;
			msg.arg1 = mBackValue;
			mHandler.sendMessage(msg);
			try { Thread.sleep(1000); } catch (InterruptedException e) {;}
		}
	}
}

지금 까지 예제와 다른점은 분리된 파일에 각각의 코드가 정의 되었다고 가정하므로
BackThread Class의 생성자를 통해서 main activity handler를 넘겨 주어야 한다는 점이다.
Message에도 좀 더 구체적으로 값을 넣어서 보내줘야 Main Activity Handler에서 처리가 가능하다.

성능향상을 위해서 obtain함수를 이용해서 message를 생성 할 수 있다.
Cache에 있는 이전에 생성한 Message를 재사용하는 방식이다.

class BackThread extends Thread {
	int mBackValue = 0;
	Handler mHandler;

	BackThread(Handler handler) {
		mHandler = handler;
	}

	public void run() {
		while (true) {
			mBackValue++;
			Message msg = Message.obtain(mHandler, 0, mBackValue, 0);
			mHandler.sendMessage(msg);
			try { Thread.sleep(1000); } catch (InterruptedException e) {;}
		}
	}
}

Looper (루퍼)

서로 다른 thread는 message를 이용해서 통신한다.

  • Message Queue: 메시지가 차곡차곡 쌓이는 것을 말한다.
  • Looper: 큐에서 메시지를 꺼내 핸들러로 전달하는 것을 담당한다. 단, MainThread는 기본적으로 Looper를 가지고 있기 때문에 따로 구현할 필요는 없다. WorkingThread의 경우에만 직접 구현하면 된다.
  • Handler: 메시지를 받기도하고 보내기도 한다.

응용프로그램을 시작할 때 생성되는 메인 스레드는 루퍼를 가지고 있으며 이미 동작하므로 이 경우는 루퍼나 메시지 큐의 존재에 대해 상세히 몰라도 상관없다. 핸들러를 만들어 놓고 전달되는 메시지를 처리하기만 하면 된다.

루퍼를 직접 프로그래밍해야 하는 경우는 작업 스레드가 메시지를 받아야 할 때이다.

백그라운드 스레드는 Looper가 없다. 백그라운드 스레드에서 기본 핸들러 생성자 Handler()를 사용하면 따라서 에러가 발생한다.
Can't create handler inside thread that has not called Looper.prepare라는 메시지가 나온다.

Looper가 실행하는 관련 함수는 다음과 같다.

static void prepare()
static void loop()
void quit()
  • prepare는 현재 스레드를 위한 루퍼를 준비한다. 내부적으로 메시지를 저장하는 MessageQueue를 생성하고 그외 메시지 전송에 필요한 조치를 한다.
  • loop는 큐에서 메시지를 꺼내 핸들러로 전달하는 루프를 생성 한다.
  • quit는 루프의 종료롤 나타낸다.

다음은 관련된 스레드와 루퍼를 구하는 메서드

Thread getThread()  
static Looper getMainLooper()  
static Looper myLooper() 
  • getThread: 루퍼와 연결된 스레드를 구한다.
  • getMainLooper: 응용프로그램 수준에서의 주 루퍼를 반환
  • myLooper: 현재 스레드의 루퍼를 구한다. 모든 스레드가 루퍼가 있는것은 아니므로 null이 리턴될 수도 있다.

다음 예제 코드는 메인 스레드와 작업 스레드가 메시지를 통해 양방향으로 통신하는 예를 보여준다.
메인에서 메시지를 보내 연산을 시키고 스레드가 연산 결과를 리턴할 때도 메시지를 사용한다.

Main Thread와 Working Thread 모두 Looper가 존재하는 코드

public class LooperTest extends Activity {
	int mMainValue = 0;
	TextView mMainText;
	TextView mBackText;
	EditText mNumEdit;
	CalcThread mThread;

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

		mMainText = (TextView)findViewById(R.id.mainvalue);
		mBackText = (TextView)findViewById(R.id.backvalue);
		mNumEdit = (EditText)findViewById(R.id.number);

		mThread = new CalcThread(mHandler);
		mThread.setDaemon(true);
		mThread.start();
	}

	public void mOnClick(View v) {
		Message msg;
		switch (v.getId()) {
		case R.id.increase:
			mMainValue++;
			mMainText.setText("MainValue : " + mMainValue);
			break;
		case R.id.square:
			msg = new Message();
			msg.what = 0;
			msg.arg1 = Integer.parseInt(mNumEdit.getText().toString());
			mThread.mBackHandler.sendMessage(msg);
			break;
		case R.id.root:
			msg = new Message();
			msg.what = 1;
			msg.arg1 = Integer.parseInt(mNumEdit.getText().toString());
			mThread.mBackHandler.sendMessage(msg);
			break;
		}
	}

	Handler mHandler = new Handler() {
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case 0:
				mBackText.setText("Square Result : " + msg.arg1);
				break;
			case 1:
				mBackText.setText("Root Result : " + ((Double)msg.obj).doubleValue());
				break;
			}
		}
	};
}

class CalcThread extends Thread {
	Handler mMainHandler;
	Handler mBackHandler;

	CalcThread(Handler handler) {
		mMainHandler = handler;
	}

	public void run() {
		Looper.prepare();
		mBackHandler = new Handler() {
			public void handleMessage(Message msg) {
				Message retmsg = new Message();
				switch (msg.what) {
				case 0:
					try { Thread.sleep(200); } catch (InterruptedException e) {;}
					retmsg.what = 0;
					retmsg.arg1 = msg.arg1 * msg.arg1;
					break;
				case 1:
					try { Thread.sleep(200); } catch (InterruptedException e) {;}
					retmsg.what = 1;
					retmsg.obj = new Double(Math.sqrt((double)msg.arg1));
					break;
				}
				mMainHandler.sendMessage(retmsg);
			}
		};
		Looper.loop();
	}
}
  • 메인스레드 작업스레드의 핸들러를 객체를 통해서 바로 호출한다.
  • 작업스레드 메인스레드의 핸들러를 객체 생성시 생성자를 통해서 받아서 그것을 통해서 호출한다.

중요한점은 이러한 메시지기반 통신 방법은 스레드 간의 통신 장치일 뿐이다. 그 이상인 응용프로그램간의 통신에는 사용할 수 없다.
공식적으로 응용프로그램 레벨에서의 통신은 Intent BR을 사용해야 한다.

작업 스케줄링

메시지큐

메시지큐에 메시지를 전달하고 원하는 시간에 꺼내서 처리 될 수 있도록 한다.

// 절대 시간
boolean sendMessageAtTime (Message msg, long uptimeMillis)
boolean postAtTime (Runnable r, long uptimeMillis)

// 상대 시간
boolean sendMessageDelayed (Message msg, long delayMillis)
boolean postDelayed (Runnable r, long delayMillis)

아래와 같은 예제를 만들 수 있다.

// 러너블 전달 
public class Post extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.upload);
	}

	public void mOnClick(View v) {
		new AlertDialog.Builder(this)
		.setTitle("질문")
		.setMessage("업로드 하시겠습니까?")
		.setPositiveButton("", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				mHandler.postDelayed(new Runnable() {
					public void run() {
						doUpload();
					}
				},10);
			}
		})
		.setNegativeButton("아니오", null)
		.show();
	}

	Handler mHandler = new Handler();

	void doUpload() {
		for (int i = 0; i < 20; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
		Toast.makeText(this, "업로드를 완료했습니다.", 0).show();
	}
}

위 예제는 Handler의 메시지 큐에 메시지를 전달 하는 것이다.
Runnable로 전달 하기 때문에 따로 Handler내부를 구현할 필요는 없다.

//* 뷰의 postDelayed 호출
public class Post extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.upload);
	}

	public void mOnClick(View v) {
		new AlertDialog.Builder(this)
		.setTitle("질문")
		.setMessage("업로드 하시겠습니까?")
		.setPositiveButton("", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				Button btnUpload = (Button)findViewById(R.id.upload);
				btnUpload.postDelayed(new Runnable() {
					public void run() {
						doUpload();
					}
				},10);
			}
		})
		.setNegativeButton("아니오", null)
		.show();
	}

	void doUpload() {
		for (int i = 0; i < 20; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
		Toast.makeText(this, "업로드를 완료했습니다.", 0).show();
	}
}

위 예제는 더 재밌는 것으로 View에 있는 스레드 큐를 사용 한다.
메인 View에 메시지큐가 있다는 것이다.

ANR (Application Not Responding)

Application Not Reponsding (ANR)이 발생하는 조건은 아래 두 가지이다.

  • 응용프로그램이 5초 이상 사용자의 입력에 반응하지 않을 때
  • 브로드캐스트 리시버 (BR)가 10초 내로 리턴하지 않을 때
public class ANR2 extends Activity {
	boolean bUploading = false;
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.anr);
	}
	
	public void mOnClick(View v) {
		switch (v.getId()) {
		case R.id.btnincrease:
			TextView textCounter=(TextView)findViewById(R.id.txtcounter);
			int count = Integer.parseInt(textCounter.getText().toString());
			textCounter.setText(Integer.toString(count + 1));
			break;
		case R.id.btnupload:
			if (bUploading) return;
			Thread uploadThread = new Thread() {
				public void run() {
					doUpload();
					mCompleteHandler.sendEmptyMessage(0);
				}
			};
			bUploading = true;
			uploadThread.start();
			break;
		}
	}

	public Handler mCompleteHandler = new Handler() {
		public void handleMessage(Message msg) {
			bUploading = false;
			Toast.makeText(ANR2.this, "업로드를 완료했습니다.", 0).show();
		}
	};
	
	void doUpload() {
		for (int i = 0; i < 100; i++) {
			try { Thread.sleep(100); } catch (InterruptedException e) {;}
		}
	}
}

위 코드는 두 가지로 나눠 지는데
첫 번째는 장기간 걸리는 작업을 Thread를 생성해서 doUpload()를 호출 했다는 점이다.
두 번째는 최종적인 결과 메시지 반환을 main thread의 handler호출로 처리 했다는 점이다.

StricMode

ANR 디버깅을 위해서 사용 하는 것으로 재미있는 기능 같다.

LogTime working을 Progress bar로 표현 하기

public class LongTime4 extends Activity {
	int mValue;
	TextView mText;
	ProgressDialog mProgress;
	boolean mQuit;
	UpdateThread mThread;

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

		mText=(TextView)findViewById(R.id.text);
	}
	
	@SuppressWarnings("deprecation")
	public void mOnClick(View v) {
		mValue = 0;
		showDialog(0);
		mQuit = false;
		mThread = new UpdateThread();
		mThread.start();
	}

	@SuppressWarnings("deprecation")
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case 0:
			mProgress = new ProgressDialog(this);
			mProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			mProgress.setTitle("Updating");
			mProgress.setMessage("Wait...");
			mProgress.setCancelable(false);
			mProgress.setButton("Cancel", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					mQuit = true;
					dismissDialog(0);
				}
			});
			return mProgress;
		}
		return null;
	}

	Handler mHandler = new Handler() {
		@SuppressWarnings("deprecation")
		public void handleMessage(Message msg) {
			mValue = msg.arg1;
			mText.setText(Integer.toString(mValue));
			if (mValue < 100) {
				mProgress.setProgress(mValue);
			} else {
				mQuit = true;
				dismissDialog(0);
			}
		}
	};

	class UpdateThread extends Thread {
		public void run() {
			while (mQuit == false) {
				mValue++;
				Message msg = mHandler.obtainMessage();
				msg.arg1 = mValue;
				mHandler.sendMessage(msg);
				try { Thread.sleep(50); } catch (InterruptedException e) {;}
			}
		}
	}
}

AsyncTask

백그라운드 작업을 하기 위해서는 스레드, 핸들러 등을 각각 만들어야 하고 작업 중에 핸들러를 주기적으로
호출해야 하는 번거로움이 있다.

이것을 간편하게 해주는 Helper 함수 AsyncTask이다.
AsyncTask는 내부적으로 작업 스레드를 생성하며 필요할 때마다 UI 스레드에서 실행되는 콜백 메서드를 호출한다.
콜백 메서드에서 정해진 작업만 처리하면 간단하게 백그라운드 작업을 수행할 수 있다.

AsyncTask자체는 추상 클래스이므로 파생 클래스를 생성하고 콜백 메서드를 구현해야 한다.

Generic parameters를 사용한다.

  • Params: 실행할 때 전달할 인수의 타입이다. 즉 배경 작업거리이다.
  • Progress: 매 작업 단계마다 진행 상태를 표기하는 타입이다.
  • Result: 작업의 결과로 리턴될 타입이다.

콜백 함수들은 아래와 같다.
doInBackground 메서드만 wroking thread에서 수행된다.
나머지 모든 메서드들은 Main UI 스레드에서 실행 된다.

  • void onPreExecute()
    • 작업이 시작되기 전에 호출되며 UI 스레드에서 실행 된다.
    • 계산을 위한 초기화나 프로그래스 대화상자를 준비하는 등의 작업을 수행한다.
  • Result doInBackground(Params... params)
    • 배경 작업을 수행하며 분리된 스레드에서 실행된다.
    • execute 메서드로 전달한 작업거리가 params 인수로 전달되는데 여러 개의 인수를 전달할 수 있으므로 배열이다.
    • 하나의 인수만 필요하다면 params[0]만 사용하면 된다.
    • 작업중에 publishProgress 메서드를 호출하여 작업 경과를 UI 스레드로 보고한다.
    • 최종적으로 작업된 결과를 Result 타입으로 리턴한다.
  • void onProgressUpdate(Progress... values)
    • doInBackground에서 publishProgress 메서드를 호출할 때 작업 경과 표시를 위해 호출되며 UI 스레드에서 실행된다. 프로그래스바에 진행 상태를 표시하는 역할을 한다.
  • void onPostExecute(Result result)
    • 백그라운드 작업이 끝난 후 UI 스레드에서 실행된다. 인수로 작업의 결과가 전달되는데 취소되었거나 예외가 발생했으면 null을 리턴 한다.
  • void onCancelled()
    • cancel 메서드로 작업을 취소했을 때 호출되며 UI 스레드에서 실행 된다.

아래의 메서드들은 만드시 UI 스레드에서 호출해야 하는 것들이다.

  • AsyncTask <Params, Progress, Result> execute (Params... params)
    • 인수로 작업거리에 해당하는 값을 넘겨주되 같은 타입의 인수 여러 개를 동시에 넘길 수 있다.
    • 이 호출에 의해 AsyncTask의 각 메서드가 순서대로 호출되어 배경 작업을 수행한다.
    • 스레드 내에서 적당한 때에 콜백 메서드를 호출하므로 UI 스레드에서 콜백 메서드를 직접 호출해서는 안 된다.
    • 리턴되는 AsyncTask 객체는 메인 스레드에서 참조 가능하며 다음 메서드로 작업을 관리한다.
  • boolean cancel (boolean mayIntteruptIfRunning)
  • boolean isCancelled()
    • 작업을 취소하라고 지시한다.
    • 작업 취소가 항상 성공적이지 않으며 실패할 수도 있는데 이미 완료되었거나 취소되었을 때 또는 기타의 이유로 취소할 수 없는 경우도 있다. 만약 작업을 아직 시작하기 전이라면 취소는 성공한 것으로 평가된다.
    • 인수는 작업을 실행중인 스레드를 중지할 것인가 지정한다.
    • isCancelled 메서드는 정상 종료되지 않고 취소되었는지 조사한다.
  • Result get ([long timeout, TimeUnit unit])
    • 작업이 완료되기까지 대기하며 작업 결과를 돌려 받는다.
    • 필요할 경우 대기할 타임아웃값을 지정한다.
  • AsyncTask.Status getStatus()
    • 작업의 현재 상태를 조사한다.
    • 아직 시작하지 않은 상태이면 PENDING이 리턴되며 이미 실행중이면 RUNNING, 작업이 완료되었으면 FINISHED가 리턴된다.

전체적인 메서드 실행 흐름은 아래와 같다.

AsyncTask의 작성 코드는 아래와 같다.

package andexam.ver6.c19_thread;

import andexam.ver6.*;
import andexam.ver6.c19_thread.LongTime4.*;
import android.app.*;
import android.content.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class LongTime5 extends Activity {
	int mValue;
	TextView mText;
	ProgressDialog mProgress;

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

		mText = (TextView)findViewById(R.id.text);
	}
	
	public void mOnClick(View v) {
		new AccumulateTask().execute(100);
	}

	class AccumulateTask extends AsyncTask<Integer, Integer, Integer> {
		@SuppressWarnings("deprecation")
		protected void onPreExecute() {
			mValue = 0;
			mProgress = new ProgressDialog(LongTime5.this);
			mProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
			mProgress.setTitle("Updating");
			mProgress.setMessage("Wait...");
			mProgress.setCancelable(false);
			mProgress.setProgress(0);
			mProgress.setButton("Cancel", new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int whichButton) {
					cancel(true);
				}
			});
			mProgress.show();
		}
		
		protected Integer doInBackground(Integer... arg0) {
			while (isCancelled() == false) { 
				mValue++;
				if (mValue <= 100) {
					publishProgress(mValue);
				} else {
					break;
				}
				try { Thread.sleep(50); } catch (InterruptedException e) {;}
			}
			return mValue;
		}
		
		protected void onProgressUpdate(Integer... progress) {		 
			mProgress.setProgress(progress[0]);	 
			mText.setText(Integer.toString(progress[0]));	 
		}
		
		protected void onPostExecute(Integer result) { 
			mProgress.dismiss();
		}
		
		protected void onCancelled() {
			mProgress.dismiss();
		}
	}
}

결국 실행 흐름과 코드를 가지고 이해해 보면,
메인 스레드와 작업 스레드를 알아서 안드로이드 시스템이 스위칭하면서 백그라운드 작업과 UI 갱신을 순서대로 실행하는 구조이다.

결국 기존 방식과 유사하되 각 단계에서 해당하는 부분이 AsyncTask의 콜백 메서드로 정형화되어 있다는 점만 다르다.
doInBackground메서드는 작업 스레드에 해당하며 onProgressUpdate는 핸들러에 해당하는 셈이다.
doInBackground내부에서 publishProgress를 실행함으로써 Progress Bar Update한다.

대화상자를 클래스 내부에서 Cancel버튼을 추가하여 생성 한다.

스레드와 핸들러를 직접 만들지 않아도 된다는 점에서 구조가 깔끔해 보이지만 약간 이해하는데 어려움이 있을 수도 있다.
전달 가능한 인수의 개수는 제한이 없지만 타입이 하나로 고정되어 범용성에 약간 제약이 있다.

하지만 많은 Open app code에서 해당 기능으로 구현을 많이 하므로 남의 코드를 이해하기 위해서는 익숙해질 필요가 있다.

참고문헌

안드로이드 정복 4판, 김상


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

Service  (0) 2017.08.16
Activity  (0) 2017.08.16
이벤트 (Event)  (0) 2017.08.04
Gradle  (0) 2016.06.03
Android Plot Libraries  (0) 2016.01.22

이벤트 (Event)


이벤트를 받아서 처리하는 각각의 방법들에 대해서 다룬다.

1. 콜백 메서드 재정의

가장 쉬운 방법으로 상속을 받아서 해당 callback함수를 재정의해서 사용하는 방식이다.

단점

  • 메서드 재정의하기 위해 반드시 슈퍼 클래스를 상속받아야 한다. MyButton등을 매번 만들어야 하는 번거로움이 있다.
  • 모든 이벤트에 대해서 콜백 메서드가 존재하는 것은 아니다.

Code

//* 1.핸들러 메소드 재정의 - 상속을 받아야만 재정의 가능하다.
public class HandleEvent extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		View vw = new MyView(this);
		setContentView(vw);
	}

	class MyView extends View {
		public MyView(Context context) {
			super(context);
		}

		public boolean onTouchEvent(MotionEvent event) {
			super.onTouchEvent(event);
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				Toast.makeText(HandleEvent.this,"Touch Event Received",
						Toast.LENGTH_SHORT).show();
				return true;
			}
			return false;
		}
	}
}

2. 리스너 인터페이스 구현

3. 액티비티가 리스너 구현

4. 뷰가 리스너 구현

5. 익명 내부 클래스 사용

2번 째 방법에서 파생된 기법으로 리스너 하나를 위해 클래스를 일일이 선언하기 번거롭다는 점을 해결한 것이다.
TouchListenerClass onTouch 메서드 구현을 위한 것이며 오로지 TouchListener객체 하나만을 위해 선언한 것이다.
더 이상의 메서드를 추가할 필요도 없고 객체를 두 개 이상 만들 필요도 없다.

객체 지향 언어이다 보니 메서드가 홀로 존재할 수 없으며 클래스 안에 넣어야 하고 객체를 생성한 후 등록하는 번거러운 과정을 거쳐야 한다.
자바는 이런 경우를 위해 언어 차원에서 익명 내부 클래스라는 문법을 제공 한다.
상위 클래스나 인터페이스의 메서드 하나를 재정의하기 위해 클래스를 선언하는 경우, 그리고 그 클래스의 객체가 단하나만 필요한 경우는 굳이 클래스를 선언할 필요 없이 상속과 재정의를 동시에 할 수 있다.

익명 클래스의 구현 예

public class HandleEvent extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		View vw = new View(this);
		vw.setOnTouchListener(TouchListener);
		setContentView(vw);
	}

	View.OnTouchListener TouchListener = new View.OnTouchListener() {
		public boolean onTouch(View v, MotionEvent event) {
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				Toast.makeText(HandleEvent.this,"Touch Event Received",
						Toast.LENGTH_SHORT).show();
				return true;
			}
			return false;
		}
	};
}

인터페이스의 인스턴스인 것 처럼 보이지만 실제로는 그렇지 않다.
인터페이스 자체는 구현이 없으므로 객체를 생성하지 못하기 때문이다.

익명 내부 클래스를 정의하는 문법을 간략하게 소개하면 다음과 같다.

# 축약된 코드
Interface obj = new Interface(){
	메서드 구현
};

6. 익명 내부 클래스의 임시 객체 사용

public class HandleEvent extends Activity {
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		View vw = new View(this);
		vw.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				if (event.getAction() == MotionEvent.ACTION_DOWN) {
					Toast.makeText(HandleEvent.this,"Touch Event Received",
							Toast.LENGTH_SHORT).show();
					return true;
				}
				return false;
			}
		});
		setContentView(vw);
	}
}

5번과의 개념적 차이는 아래와 같다.

# 이름 있는 객체 사용
Class obj = new Class();
Method(obj);

# 임시 객체 사용
Method (new Class());

임시 객체를 사용하면 클래스 선언, 객체 생성문이 필요 없으므로 액티비티가 생성될 때인 onCreate에서 이벤트 등록 및 구현이 모두 가능하여 코드가 아주 짧아진다.

7. Java8에서의 Lambda를 이용한 방법

https://mayojava.github.io/android/java/using-java8-lambda-expressions-in-android/


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

Activity  (0) 2017.08.16
쓰레드 (Thread)  (0) 2017.08.04
Gradle  (0) 2016.06.03
Android Plot Libraries  (0) 2016.01.22
Android wear app  (0) 2015.08.25

Gradle Build System


현재의 안드로이드 앱 빌드 환경은 
Ant to gradle로 변경됨.

The Android build system was originally written in Ant
It was based on a rather flat project structure that did not offer much support for things such as build variationsdependency,management, and publishing

Ant has an XML tag-based programming model that is simple and extensible, though mnay developers find it cumbersome.

In addtion, Ant uses a declarative model.
However, most modern programming languages prefer imperative model

Gradle
written by Groovy programming language, which builds on top of Java's core runtime and API.

Groovy loosely follows Java's syntax which, when combined with its syntax, lowers the learning curve.

Modern software development involves following steps:
- linking
- compilation
- testing
- packaging
- distribution of end product

supporting variations of the end product
- a debug version
- a release version
- a paid version
- a free version

managing thrid-party software libraries

Gradle Syntax

The basic structure of gradle build script comprises configuration and task blocks.

Task blocks
Define code that is executed at various points during the build.

Configuration blocks
special Groovy closures that add properties and methods to underlying objects at runtime.

Task block은 안드로이드에서 이미 정의 되어 있기 때문에
Configuration block을 설정 하게 된다.

Configuration block의 형태

Gradle Build Concepts

The Gradle build system is a general tool for building software packages from a collection of source files.

Gradle Android Strcture

Gradle은 Hierarchical strcutrue를 지원한다. 즉 sub-project들 또는 module이 무한으로 겹쳐서 생성 될 수 있다.

사용되는 파일 리스트들은 아래와 같다.

.gradle: Temporary Gradle output, caches, and other supporting metadata are stored under this folder.

app: the simplest Android Project

gradle: This folder contains the Gradle wrapper. The Gradle wrapper is a JAR file that contains a version of the Gradle runtime compatible with the current project.

build.gradle: The overall project build logic lives in this file. It is reponsible for including any required subporjects and triggering the build of each one.

gradle.properties: Gradle and JVM properties are stored in this file. You can use it to configure the Gradle daemon and manage how gradle spawns JVM processes during the buld. You can also use this file to help Gradle communicate when on a network with a web proxy.

gradlew/gradlew.bat These files are the operating system-specific files used to execute Gradle via the wrapper. If Gradle is not installed on your system, or if you don't have a version compatible with your build, then it is recommended to use one of these files to invoke Gradle.

local.properties: This file is used to define properties specific to the local machine, such as the location of the Android SDK or NDK.

setting.gradle: This file is required with multiproject builds, or any project defining a subproject. It define which subproject. It defines which subprojects are included in the overall build.

Project.iml,.idea,.gitignore: While these files (with the exception of .gitignore discussed in Chapther 7) are not part of the Gradle build system, they are constantly updated as you make changes to your Gradle files.

Project Dependencies

대부분 필요한 기능을 이곳에 구현하게 된다.

Case Study: The Gradle Weather Project


실습 코드 다운

git clone https://bitbucket.org/csgerber/gradlewather.git

Gradle Weather라는 project는 다양한 build script를 포함하고 있으므로 이것을 이용해서 이해도를 높이도록 하겠다.

이것은 fake weather를 보여주게 된다.

본격적으로 case study를 수행하기전에 new branch를 생성해준다.
과정은 아래와 같다.

  • git log
  • step1 -> new branch -> name = mylocal

Build.gradle (project: gradleweather

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

buildscript를 포함하고 있다.
buildscript는 현재의 build 파일들을 설정 한다.

여기에 설정한 내용들은 모든 subprojects에 영향을 미치게 된다.

jcenter()의 내용은 internet-accessible Maven repository이다. 이것은 많은 Android dependecies를 가지고 있으며
open source project를 포함 한다.

그다음으로 dependencies를 설정 하는데 Gradle 0.12 이상이라고 설정 한다.
마지막으로 child project들은 모두 같은 JCenter repository로 설정 된다.

local.properties

sdk.dir=/root/Android_Application_Tools/android-sdk-linux_r24

It includes only a setting for the location of the Android SDK.

app/build.gradle

apply plugin: 'com.android.application'

The first line engages the Android Gradle plug-in for use in the current build.

android {
    compileSdkVersion 21
    buildToolsVersion '21.0.0'

Set SDK version and build tools version
SDK version은 Android SDK APis를 말하는 것으로 컴파일해야 되는 target을 의미한다.
Build tools version은 build에 사용될 도구의 버전으로 Dalvik Executable conversion, ZIP algnment 등과 같은 것들을 결정 한다.

    defaultConfig {
        applicationId "com.apress.gerber.gradleweather"
        minSdkVersion 14
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }

Defines the application ID (which is used when you submit to the Google Play Store),
the minimum SDK version that your app is compatible with,
the SDK that you are targeting, the app version, and version name.

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Google play store에 출시할때 obfuscation을 하기 위한 proguard사용에 관한 내용이다.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:support-v4:20.+'
}

의존성 lib들을 설정해 준다.

Android Library Dependencies

Gradle's roboust repository system allows you to easily locate and use code from other companies, 
open source libraries, or libraries from others in your own organizatoin.

In this section, you will evolve our app by using an Android library dependency that makes the network request for weather data.

사실 실제로 network data를 가져오는 것은 아니지 
existing Android app code를 어떻게 재활용 하는지에 대해서 알아보는 것이다.

과정

  • File
  • New Moudle to open the New Module Wizard
  • Android Library in the first dialog box
  • WeatherRequest, minimum SDK settings
  • Add No Activity from the next page of the wizard

이렇게 생성을 하고 WeatherRequest의 build.gradle 내용을 살펴 보면 아래와 같다.

apply plugin: 'com.android.library'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.1'
}

main build gradle과 주된 차이점은 Android library plug-in을 사용한다는 것이다.
이 plug-in은 특별한 Android archive file format인 AAR format을 모듈 소스로 부터 생성 하게 된다.

Step 3

NationalWeatherRequest class를 추가 한다.

적절한 기능을 추가 한다.

main app의 build.gradle dependencies 수정
compile project(':WeatherRequest')

Opening Older Projects

Gradle은 생각 보다 Version이 빠르다.
따라서 이전의 Project를 open할 경우 예상치 못한 error를 만날 수도 있다.

이경우 해당 지시사항을 이용해서 그대로 처리해 주면 된다.
ex) Fix plugin version and re-import project


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

쓰레드 (Thread)  (0) 2017.08.04
이벤트 (Event)  (0) 2017.08.04
Android Plot Libraries  (0) 2016.01.22
Android wear app  (0) 2015.08.25
Android Studio 자동 import 기능  (0) 2015.07.15

Android Plot Libraries


정식 API로는 그래프를 그려주는 기능은 없다.
기존의 open-source project를 살펴 보자.

크게 Client-side libraries와 web기반의 Google Charts로 나뉜다.

Client-side libraries
Pros
● Open source
● Self-contained
● Easy to integrate
Cons
● Limited features
● Older platform
versions
● Look & feel
● Customization hard
● Lack of support

Google Charts
Pros
● Feature rich
● Simple API
● Customizable
● Responsive design
● Interactive
Cons
● Network connection
● Load time
● Backward
compatibility

MPAndroidChart

가장 최근까지 활발히 업데이트 되는 프로젝트이다.
그래프도 나름 이쁘다.
MyphoneandMe에서 사용함

https://github.com/PhilJay/MPAndroidChart

GrapView

NotifyME에서 사용하는것
https://github.com/jjoe64/GraphView
http://www.android-graphview.org/

Google Charts

Hello Pizza Chart
https://github.com/ecgreb/hello-pizza-chart


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

이벤트 (Event)  (0) 2017.08.04
Gradle  (0) 2016.06.03
Android wear app  (0) 2015.08.25
Android Studio 자동 import 기능  (0) 2015.07.15
Android Wear 개발환경 구축  (0) 2015.07.15

Android wear app


환경설정


emulator


adb -d forward tcp:5601 tcp:5601


real device


Host: disconnected

Target: connected


adb forward tcp:4444 localabstract:/adb-hub

adb connect localhost:4444


Host: connected

Target: connected




Create a Project


mobile app과

wear app이 동시에 설치 되게 된다.


WatchViewStub을 이용해서 "Hello World"가 화면에 표시되어 진다.

WatchViewStub은 one of the UI widget으로 wearable support library로써 제공 된다.


appropriate module을 build.gradle 안에다가 넣어서 처리 해야 한다.


Notification

android v4 support library가 wearable을 위해서 화장된 것이다.

API level 20이라고 하면 처리가 가능하다.



Wearable Data Layer

watch와 smartphone이 서로 동기화 되어지기 위해서 Data Layer가 사용 된다.

이것을 위해서는 the latest version of Google Play services가 중요 하다


Wearable UI support library

비공식적인 UI 라이브러리를 말한다.



여기서는 단순히 Layout을 디자인 하는 정도 수준이다.



Sending and Syncing Data


이러한 data layer를 이용해서 Google Play service단에서 communication channel을 이용해서 상호 동기화를 수행 한다.


Data Items

어떠한 스토리지로써 hanheld와 wearable 사이에서 데이터를 동기화 시켜주기 위한 것이다.


Messages

MessageAPI로써 이것은 RPC를 위한 좋은 도구이다.

스마트폰의 media player와 같은 것들을 pause, resume 하는 등의 작업으로써 이용할 수 있다.


또한 단방향 통신 매커니즘으로도 좋다.

실행 결과는 둘사이의 연결 상태에 의존 한다.



Asset

image와 같은 데이터 전송을 위한 것이며,

이것을 이요할 경우 자동적으로 bluetooth power 소모를 줄여주며,

large asset들을 자동으로 cahcing 하여 re-transmission을 줄여 준다.



WearableListenerService (for services)

이것을 상속받아서 확장 함으로써 data layer에서의 중요한 이벤트들을 알아낼수 있다.

필요할때 binding 하고 필요 없을때 unbinding을 하면 된다.



DataListener (for foreground activities)

사용자가 해당 앱을 foregorund에만 사용 중일 때만 이벤트를 읽어 오는 기능을 한다



Channel

채널을 이용해서 큰 데이터 아이템을 전송 할 ㅅ 있다.

음악 파일, 영화와 같은 것이다.

이것을 handheld to wearable로 전송하게 된다.



아래와 같이 직접 싱크를 맞추던가 Cloud Node를 이용해서 싱크를 맞추게 된다.

결국 핵심은 여러 장치들이 모두 동기화 되어진다는 것이다.







































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

Gradle  (0) 2016.06.03
Android Plot Libraries  (0) 2016.01.22
Android Studio 자동 import 기능  (0) 2015.07.15
Android Wear 개발환경 구축  (0) 2015.07.15
Notifications API Guide  (0) 2015.06.29

Android Studio 자동 import 기능


단축키: Optimize imports CTRL + ALT + O

File -> Settings -> Editor -> General -> Auto Import -> Java and make the following changes:




이 두기능을 체크하면 자동으로 import가 진행 됨.



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

Android Plot Libraries  (0) 2016.01.22
Android wear app  (0) 2015.08.25
Android Wear 개발환경 구축  (0) 2015.07.15
Notifications API Guide  (0) 2015.06.29
Android Studio 특징 및 단축키  (0) 2015.05.24

Android Wear 개발환경 구축


Smart-Phone과 연결하기


아래명령어를 통해서 Watch Emulator와 연결 시켜 준다.


USB connection을 Phone과 Desktop이 연결되지 않고도 Watch Emulator와 연결하고 싶다면,

아래와 같이phone의 adb daemon port를 설정 한다음 연결 한다.


$ adb tcpip 5555

$ adb connect IP주소:5555

$ adb -s IP주소:5555 forward tcp:5601 tcp:5601



Watch Emulator는 그룸 모양의 icon이 사라지게 된다.




스마트폰에서 Notification을 발생 시킨다. 




정상적으로 Watch emulator로 전달 되는것을 볼 수 있다.








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

Android wear app  (0) 2015.08.25
Android Studio 자동 import 기능  (0) 2015.07.15
Notifications API Guide  (0) 2015.06.29
Android Studio 특징 및 단축키  (0) 2015.05.24
다중 스크린 사이즈를 지원하는 방법  (0) 2014.10.27



Support Library version 4를 이용해서 호환을 함.


NotificationCompat.Builder



5.0 material design의 영향으로 해당 버전에서는 약간 다르다.



Creating a Notification


NotificationCompat.Builder

NotificiationCOmpat.Builder.build() => notification object


NotificationManager.notify() // notification object will be passed to the system by calling that function.



Required notification contents


setSmallIcon() // A small icon 

setContentTitle() // a title

setContentText() // Detail text


그밖에도 많은 options들이 API를 보면 나와 있다.


Notification actions


하나 이상의 action은 반드시 정의 되어함.

특정 activity로 이동하는 action을 의미한다.


두개 이상의 action을 수행하기 위해서는 4.1 이상이 되어야함.

snoozing alarm 또는 send a text 등과 같은 것들이다.


PendingIntent

setContentIntent()


Notification Priority


priority를 통해서 notification이 언제 어떻게 보일지를 설정 할 수 있다.


NotificationCompat.Builder.setPriority()

PRIORITY_MIN (-2)

PRIORITY_MAX (2)

PRIORITY_DEFAULT (0)



Creating a simple notification


눌렀을 때 어떤 activity가 실행 되는 간단한 코드는 아래와 같다.

TaskStackBuilder는 간단히 PendingIntent를 생성 하는 것이다.


NotificationCompat.Builder mBuilder =
       
new NotificationCompat.Builder(this)
       
.setSmallIcon(R.drawable.notification_icon)
       
.setContentTitle("My notification")
       
.setContentText("Hello World!");

// Creates an explicit intent for an Activity in your app
Intent resultIntent = new Intent(this, ResultActivity.class);

// The stack builder object will contain an artificial back stack for the
// started Activity.
// This ensures that navigating backward from the Activity leads out of
// your application to the Home screen.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);

// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder
.addParentStack(ResultActivity.class);

// Adds the Intent that starts the Activity to the top of the stack
stackBuilder
.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
        stackBuilder
.getPendingIntent(
           
0,
           
PendingIntent.FLAG_UPDATE_CURRENT
       
);
mBuilder
.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
   
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify(mId, mBuilder.build());


1) 간단히 object를 생성

2) 눌렀을 때 시작되는 intent를 생성

3) Back forward 했을때 문제를 막기 위해서 home screen을 보장하는 intent를 생성

4) 시스템에 등록

5) notify를 통해서 최종적으로 시스템에 등록



Applying an expanded layout to a notification

4.1 이상에서만 지원



Handling Compatibility

파편화로 인해서 당연히 이러한 부분도 고려를 해야함.



Managing Notifications 

When you need to issue a notification multiple times for the same type of event, you should avoid making a completely new notification.
Instead, you should consider updating a previous notification, either by changing some of its values or by adding to it, or both.

-> updating notifications

-> removing notifications



Preserving Navigation when Starting an Activity







Notification priority



MAX 긴급하고 타임 크리티컬 하다. 지속적으로 계속 해결해야하는 테스크일때 사용 
HIGH 중요한 커뮤니케이셔이다. 메시지나 채팅 메시지 같은 것이다. 중요한 것들이라면 heads-up display를 만들어 내게 된다.
Default: 어디에도 속하지 않을 때 기본적으로 사용함.
LOW 그냥 보기는 원하지는 급하진 않은것들.
거의 bottom list에만 출력하게 된다.
MIN 날씨 정보나 상황 정보 같은 것드이다. status bar에 보이지 않는다. 
status bar를 펼쳐야 보이게 된다.

Notification  Manager에서 각각에 따라서 행동을 정의해서 동작해 주는것 같다.
이미 어느정도 고려하는 부분이 있는 것이다.





Android Studio 특징 및 단축키


앱 개발을 위해서 이제는 Android Studio를 사용 해야함.


Android Studio의 장점을 요약하면 아래와 같다.


1) interface design 지원이 강력하다.


2) Build 시스템을 Gradle로 변경 했다.

이부분이 문제이다. 많은 것들이 Gradle에 걸려있기 떄문에, Eclipse를 사용할 때와 다르게 Gradle을 새로 배워야 한다.

요즘 뜨는 강력한 Build 도구라고 하니 이참에 배워야 할것 같다.




단축키 모음


실행 , 환경설정

- Ctrl + Alt + S : 환경설정

- shift + F10 : Build & Run


찾기 관련 

- Ctrl + Shift + N : 전체 검색

- Ctrl + Alt + L : 정렬

- Shift * 2 (Shift 2번) : 전체 찾기


변경, 삽입, 삭제

- Shift + F6 : 태그 동시 변경 (빨간 네모)

- Ctrl + y : 한줄 삭제

- Alt + i : getter, setter 삽입

- Ctrl + i : 인터페이스 

- Ctrl + o : 메소드 오버라이딩


주석, 정렬

- Ctrl + / : 주석

- Ctrl + Alt + L : 코드라인 정렬


이동 

- Alt + 1,6,7 : 익스플로어, 로그켓

- Alt + 좌,우 : 탭 이동

- Ctrl + b : 선언부로 이동


디버그

- Shift + F9 : 디버그 모드

- Ctrl + F2 : 디버그 모드 종료

- F9 : 다음 중단점 까지 실행

- F7 : 멈춘 라인에 메소드 속으로 들어감

- F8 : 한줄만 실행

- Alt + F9 : 커서 있는 곳까지 실행

- Ctrl + Alt + F9 : 강제로 커서까지 실행

- Ctrl + Shift + F8 : 설치된 중단점 모두 보기 


사용하지 않는 Import 정리 

- Ctrl + Alt + o


자동 완성 / 수정 

- Alt + Enter


이름 바꾸기

- Shift + F6


코드 정렬

- Ctrl + Alt + L


메소드 오버라이드(Override method)

- 상위 클래스 내에 잇는 오버라이드 가능한 메소드의 목록 보여주고, 

 선택한 항목을 자동으로 코드에 추가한다. 

- Ctrl + O 


인터페이스 구현 

- Ctrl + I


문서보기 

- F1


매개변수 정보

- Ctrl + P


선언부로 이동

- Ctrl + B


통합검색

- Shift + Shift (Double Shift) 


Go to class CTRL + N
Go to file CTRL + Shift + N
Navigate open tabs ALT + Left-Arrow; ALT + Right-Arrow
Look up recent files CTRL + E
Go to line CTRL + G
Navigate to last edit location CTRL + SHIFT + BACKSPACE
Go to declaration CTRL + B
Go to implementation CTRL + ALT + B
Go to source F4
Go to super Class CTRL + U
Show Call hierarchy CTRL + ALT + H
Search in path/project CTRL + SHIFT + F

Programming Shortcuts:-

Reformat code CTRL + ALT + L
Optimize imports CTRL + ALT + O
Code Completion CTRL + SPACE
Issue quick fix ALT + ENTER
Surround code block CTRL + ALT + T
Rename and Refractor Shift + F6
Line Comment or Uncomment CTRL + /
Block Comment or Uncomment CTRL + SHIFT + /
Go to previous/next method ALT + UP/DOWN
Show parameters for method CTRL + P
Quick documentation lookup CTRL + Q
Delete a line CTRL + Y

View declaration in layout CTRL + B



참고사이트

http://www.developerphil.com/android-studio-tips-tricks-moving-around/

IntelliJ IDEA shortcut manual 



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

Android wear app  (0) 2015.08.25
Android Studio 자동 import 기능  (0) 2015.07.15
Android Wear 개발환경 구축  (0) 2015.07.15
Notifications API Guide  (0) 2015.06.29
다중 스크린 사이즈를 지원하는 방법  (0) 2014.10.27

다중 스크린 사이즈를 지원하는 방법


용어 정리

Screen size:  대각선 상의 실제 크기를 말한다.

안드로이드 시스템에선 간단히 4개의 그룹으로 표현 한다.

small

normal

large

extra-large


Screen density: 


Orientation: 


Resolution: 화소수, 항상 가로부터 표현.


Density-independent pixel (dp): 가장 중요한 정보이다. 인치당 점의 개수로, UI의 실제 길이가 결정 되는 부분이다.


aspect ratio: 종횡비로 가로와 세로의 비율을 나타냄. 

4:3, 16:9와 같은 것들이며, long, notlong과 같이 표현 하기도함.



안드로이드가 이와 같이 그룹을 이용하는 것은, 약간의 차이는 거의 무시해도 되기 때문이다. 따라서 일반화를 시켜서 계산하게 됨.


화소 vs 밀도 -> 비례

크기 vs 밀도 -> 반비례



---

해상도 제각각

밀도가 제각각


화면이 잘리거나, 검정색 여백을 드러내는 프로그램들이 실제로 존재함.


문제를 발생 시키는 몇가지 나쁜 습관들

1) 물리 단위를 쓰지 않고 논리 단위를 쓴다.

2) 이미지는 가급적 밀도별로 모두 제공함.

3) 레이아웃의 배치 기능을 십분 활용 한다.




논리 단위의 이용

물리 단위는 해당 장치에서만 정확하게 동작하므로, 빌트인 앱과 같은 특수한 경우를 제외 하고는 이용하지 않도록 해야함.

안드로이드에서 제공하는 노리적 단위는 다음과 같음.

px 물리적인 픽셀단위

in, mm, pt 인치, 밀미미터, 포인트

dpi (or dp), sip (or sp) 밀도에 독립적인 단위, 폰트에 가변 크기


dpi가 논리적인 단위 이므로 이것을 사용 해야한다.

160을 기준으고 1 픽셀

240 이면 240/160 = 1.5 배 커짐.

120 이면 120/160 = 0.75배 만큼 감소.






violation 나는 pattern을 분석해서, 나만의 향상된 알고리즘을 생성 한다.


영상처리에 기반한 것이 아닌, 패턴과 규칙에 기반한 접근 법이다.



이미지 리소스의 경우


이미지의 경우 다소 특별한 관리 기법을 따름.


운영체제는 장비의 밀도와 폴더의 밀도를 비교하여 이미지를 스케일링함.

모든 폴더에서 이미지가 발견되지 않으면 컴파일 에러로 처리함.


각각에 맞게,

drawable-ldpi

drawable-mdpi

drawable-hdpi


3개의 디렉터리에 이미지를 넣어 두면 좋은 효과를 볼 수 있다.




레이아웃 활용


절대적인 배치 레이아웃으론 맞추기가 어렵다.

따라서 렐러티브 레이아웃,



















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

Android wear app  (0) 2015.08.25
Android Studio 자동 import 기능  (0) 2015.07.15
Android Wear 개발환경 구축  (0) 2015.07.15
Notifications API Guide  (0) 2015.06.29
Android Studio 특징 및 단축키  (0) 2015.05.24

+ Recent posts