본문 바로가기

수업 정리

85~86일차 수업정리(Android - Material Design,

**Bottom Sheet

    => Material Design의 일부분으로 하단에서 위로 밀려오면서 출력되는 View

    => View 내에서 메시지를 출력하거나 보조적인 입력이 필요할 경우 주로 사용(In-App)

    => 안드로이드에서는 BottomSheet를 만들기 위한 별도의 레이아웃을 생성

 

**Pull To Refresh

    => AdapterView의 상단에서 하단으로 스크롤할 때 업데이트

    => 안드로이드에서는 SwipeRefreshLayout으로 구현 - 별도 라이브러리 형태로 제공

        - SwipeRefreshLayout 안에 ListView나 RecyclerView(각 항목 뷰를 재사용 - 이때 사용하는 자료구조가 Deque)를 배치하여 사용

    => OnRefreshListener 인터페이스를 구현한 객체를 설정해주어야 함

        - OnRefresh 메소드 : 당겨서 놓았을 때 호출되는 메소드

        - 이 메소드에서 새로운 데이터를 읽거나 기존 데이터에서 추가할 데이터를 찾아오고 ListView나 RecyclerView를 업데이트

        - 내부에서 setRefreshing(false)를 호출해야 함

        - 그래야 RefreshView가 화면에서 제거됨

    => RefreshView의 색상변경은 setColorSchemeResource 메소드에 4가지 색상을 설정하면 됨

1. 프로젝트 1개 생성

 

2. 필요한 의존성을 설정

    => gradle 기반의 프로젝트 : build.gradle(app - module)

    => recyclerview를 검색해서 추가

 

3. 기본 레이아웃 수정

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/swipeLayout">
        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/listView"/>
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</LinearLayout>

 

4. Activity.java에 인스턴스 변수 선언

    SwipeRefreshLayout swipeLayout;
    //ListView나 RecyclerView의 데이터를 업데이트할 의도가 있다면 
    //Adapter와 데이터는 인스턴스 변수로 같이 선언
    ListView listView;
    ArrayAdapter<String> adapter;
    ArrayList<String> list;

 

5. Activity.java 파일의 onCreate 메소드에서 초기화 작업 - onCreate에서 하는 작업은 onResume에서 해도 됨

   (onResume은 활성화 될때마다 호출되는 메소드)

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

        //데이터 생성
        list = new ArrayList<>();
        list.add("java.lang");
        list.add("java.util");
        list.add("java.io");
        list.add("java.net");

        //어댑터 생성
        adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, list);
        //ListView에 설정
        //최근의 안드로이드 스튜디오에서는 강제 형변환을 하지 않아도 됨
        //코드 최적화를 이용하여 안드로이드 스튜디오가 자동 형변환 수행
        listView = (ListView)findViewById(R.id.listView);
        listView.setAdapter(adapter);

        swipeLayout = (SwipeRefreshLayout)findViewById(R.id.swipeLayout);
        //아래로 swipe했을 때를 처리하는 리스너 연결
        swipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                //데이터를 업데이트하고 ListView나 RecyclerView를 업데이트
                //pull to refresh는 앞쪽에 추가하는 것이 일반적
                list.add(0, "패키지의 개념");
                list.add("java.sql");
                adapter.notifyDataSetChanged();
                //RefreshView를 화면에서 제거
                swipeLayout.setRefreshing(false);
            }
        });
    }

 

6. 서버에서 데이터나 로컬에서 가져오는 데이터의 업데이트

    => 새로운 데이터를 가져오는 경우 Pull To Refresh를 이용하여 가장 위에 출력하는 것이 좋음

        - 페이징을 구현한 경우 이전 데이터 로드시 스크롤 뷰의 마지막 부분에서 스크롤 할 때 데이터를 가져와서 뒤에 출력하는 것이 좋음

    => 모든 데이터를 전부 출력하는 경우는 거의 없으므로 특정 개수 단위로 데이터를 출력하도록 해주는 것이 중요

        - 웹은 개수를 정할 수 있도록 해주어야 하고, 스마트폰은 개수를 설정하지 않는 것이 일반적

        - 애플리케이션을 구현하여 디바이스에서 확인한 후, 최적의 개수를 개발자가 찾아야 함

 

**Broadcast Receiver

    => 안드로이드의 컴포넌트

    => 방송 수신을 대기하고 있다가 방송이 오면 작업을 수행하는 컴포넌트

    => UI가 없는 컴포넌트

    => 방송은 Activity에서 sendBroadcast 또는 sendStickBroadcst, sendOrderedBroadcast 메소드를 호출

    => Activity 와 다르게 동작, Activity는 호출시 없으면 예외 발생, Broadcast는 호출시 없으면 반응하지 않음

    => 호출했을 때 2개 이상이 존재한다면 Activity는 그 중에 하나만 선택해서 실행하지만 Broadcast는 전부 실행

 

1. Broadcast 인텐트 생성

    => 액션 문자열을 가지고 생성

    => 데이터와 카테고리 문자열을 선택적으로 가질수 있음

    => 액션 문자열이나 카테고리 문자열은 대부분 자반의 패키지 이름의 형태를 가짐

    => 설치를 하고 한번도 실행하지 안흐염 사용정지 상태가 되서 호출되지 않는상태가 됨

    => Intent를 만들 때 addFlags라는 메소드를 호출해서 매개변수로 FLAG_INCLUDE_STOPPED_PACKAGES를 추가하면 실행되지 않았어도 사용정지 상태는 되지 않아서 호출이 가능해짐

 

2. Broadcast Receiver

    => Broadcast Intent에 의해 방송이오면 호출되는 클래스 또는 객체

    => BroadcastReceiver 클래스로부터 상속 받아서 onReceive를 Overriding해야 함

    => AndroidManifest.xml 파일에 등록하는 데 이때 액션 문자열을 함께 등록

    => 인텐트가 방송시 onReceive 메소드가 호출되어 실행됨

    => 여기에 작성하는 내용은 5초 이내에 작업이 완료되도록 만드는 것을 권장

    => 이 컴포넌트를 사용한다는 것은 대부분 방송을 여러 애플리케이션에서 수신하도록 하기 위한 경우가 많음

    => 여러 애플리케이션에서 이 방송을 처리해야 하므로 긴 시간이 걸리는 작업은 비추천

 

3. System Broadcast 이용

    => 부팅 완료 : android.intent.action.BOOT_COMPLETED

    => 화면 On/Off : android.intent.action.ACTION_SCREEN_ON

    => 전화 수신 : android.intent.action.NEW_OUTGOING_CALL

    => 배터리 : android.intent.action.BATTERY_LOW, BATTERY_CHANGED, ACTION_POWER_CONNETED

        - 배터리는 화면에 동영상을 출력하거나 위치정보, 블루투스를 사용할 때 많이 이용하는 시스템 브로드캐스트 리시버

 

4. Android에서 주의할 버전

  1) 6.0(마시멜로우) : 동적 권한 설정이 추가됨

    => 이전 : 설치시 모든 권한을 묻고, 권한 설정

        - 한번 권한설정시 변경이 어려워지는 문제가 발생

    => 6.0부터 기능 수행시 권한을 설정하는 것으로 변경

  2) 8.0(오레오) : 백그라운드에 대한 제한

    => Broadcast를 실행할 때 백그라운드에서 하는 것은 안됨 - 다른 Applicatoin에서 작성 및 호출은 금지

        - 액션 문자열을 이용하지 말고, 직접 클래스명을 명시하여 실행

    => 안드로이드에서 다른 애플리케이션의 컴포넌트를 사용하고자 하는 경우 컴포넌트 등록시 액션 문자열을 등록하면 됨

  3) 10.0 : AI 도입

    => 기존의 Support 패키지가 androidx로 변환

 

5. 작성법

  1) Receiver 클래스 생성

class MyReceiver extends BroadcastReceiver{
	@Override
    public void onReceive(){
    	방송 수신시 수행할 내용
    }
}

 

  2) Receiver 클래스를 AndroidManifest.xml 파일에 등록

<receiver android:name="클래스 경로">
</receiver>

 

  3) 방송

Intent intent = new Intent(Context context, 클래스 명);
sendBroadcast(intent);

 

6. 실습 - 다른 모듈의 수신자를 호출

  1) 현재 프로젝트에 Module(Application의 개념)을 추가

 

  2) 새로 추가한 모듈에 Receiver 역할을 수행할 클래스를 추가

    => 직접 일반 클래스를 추가해서 Receiver를 만들고 등록하는 방법이 있고 메뉴에서 Receiver를 추가하는 방법이 있음

public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //토스트를 하나 출력
        Toast.makeText(context, "방송을 수신했습니다.",
                Toast.LENGTH_LONG).show();
        //MainActivity를 출력 
        //다른 Application에서 이 Application을 실행 
        Intent mainIntent = new Intent(
                context, ReceiverActivity.class);
        //실행을 하지 않았더라도 설치만 되어 있으면 호출될 수 있도록 설정
        mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        //액티비티를 호출
        context.startActivity(mainIntent);
    }
}

 

  3) 컴포넌트는 사용할 수 있도록 AndroidManifest 파일에 등록

    => AndroidManifest 파일에 등록

<receiver
	android:name=".MyReceiver"
	android:enabled="true"
	android:exported="true">
	<intent-filter>
		<!-- 다른 앱에서 아래 문자열을 가지고 방송을 하면 MyReceiver 클래스의 onReceive 가 호출 -->
		<action android:name="com.example.sendbroadcast" />
		<!--시스템 브로드 캐스트 설정 전원연결이 해제되면 호출 -->
		<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
	</intent-filter>
</receiver>

 

  4) 

//리시버 등록
registerReceiver(new MyReceiver(), new IntentFilter("com.example.sendbroadcast"));

 

 

  6) 버튼의 클릭이벤트 처리를 위한 click메소드를 Activity에 생성

    //버튼의 클릭이벤트 처리를 위한 메소드
    public void click(View view){
        //방송을 송신
        Intent intent = new Intent();
        intent.setAction("com.example.sendbroadcast");
        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        sendBroadcast(intent);
    }

 

  7) 실행 확인

    => 추가한 모듈을 실행 - 설치때문에 수행

    => 기존 모듈을 실행해서 버튼 클릭

 

  8) 실제 사용은 대부분 시스템 브로드캐스트가 발생했을 때 수행 할 내용을 작성하는 경우가 많음

    => 배터리 양이 LOW이면 화면을 어둡게 만들거나 토스트, 스낵바를 출력하여 알람 표시 형태로 많이 사용

 

**모듈을 추가하면서 학습을 할 때 주의할 점은 빌드 파일은 공유하는 파일이 1개 있고, 각 모듈의 build.gradle파일이 있음

    - 자신의 gradle파일을 잘 찾아서 설정해야 함

 

**Local Notification(알림)

    => 앱의 각종 상황을 사용자에게 알릴 목적으로 이용

    => 외부에서 푸시 메시지가 왔을 때 알려주는 것

    => 이전에는 메시지 형태로 출력되었으나 지금은 상단에 잠깐 출력되고 쓸어내려서 확인도 가능

    => 오레오 버전을 기준으로 생성하는 방법이 변경

1. 관련된 클래스

  1) NotificationManager : 알림을 시스템에 발생시키는 SystemService

    => 안드로이드에서는 시스템 서비스의 경우 getSystemService(가져올 서비스 상수)를 대입하여 서비스를 위한 객체를 생성

    => 시스템이 가지고 있는 서비스는 직접 객체를 생성하도록 하면 충돌 가능성 존재

        - 싱글톤으로 제작 후 요청시 싱글톤의 참조만 넘겨주는 형태로 사용

    => NotificationManager는 getSystemService(NOTIFICATION_SERVICE)를 이용하여 가져옴

  2) Notification : 알림 구성정보를 가지는 객체

  3) NotificationCompat.Builder : 알림을 생성하기 위한 객체

  4) 전에는 3)번 클래스를 직접 이용하여 알림을 생성, 현재는 NotificationChannel이 추가되어 Builder를 NotificationChannel로 생성

    (오레오에서 변경됨)

  5) 오레오 하위버전까지 고려하여 작성

 //Oreo 버전 이상인 경우
 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
 	NotificationChannel channel = 
    	new NotificationChannel(String id, String name, NotificationManager.상수);
    //각종 설정 - 진동, 사운드, 화면 밝기 조정등의 옵션을 설정
    NotificationManager.createNotificationChannel(channel);
 }else{
 	//Oreo 버전보다 하위 버전인 경우
    new NotificationCompat.Builder(Context context).설정메소드 호출.설정메소드 호출...
 }
 

  

  6) 알림을 눌렀을때 호출되는 메소드 작성

Builder.addAction(new NotificationCompat.Action.Builder(아이콘, String name, PendingIntent intent).build()

    => 알림 클릭시 PendingIntent가 호출

  7) PendingIntent 생성

PendingIntent.getActivity(Context context, 구분하기 위한 상수, PendingIntent 옵션)

    => 작성 내용은 context자리에 알림의 출력할 액티비티를 출력 : 데이터 출력 X, 화면만 출력

        - 이 경우 onResume에서 데이터를 업데이트하면 업데이트 된 데이터가 출력됨

        - 신문 앱의 경우 속보를 푸시 메시지로 보내주고 푸시 메시지 도착시 알림창으로 출력

        - 알림창 클릭시 액티비티로 이동하여 기사들을 선택할수 있게 해줌

    => 액티비티 대신 Receiver를 이용하여 Activity를 출력하는 형태를 작성하기도 함

        - Receiver 사용 이유 : Receiver에서 새로운 Intent를 만들어 필요한 데이터를 intent에 저장하여 Activity를 출력하기 위해

        - 카톡등의 메신저 서비스의 경우 알림이 오면 그 알림의 내용만 액티비티 하단에 업데이트 하기 위해서

  8) Local Notification과 알람의 차이

    => 알람 : 현재 App내에서의 이벤트에 의해 동작

    => Local Notification : 현재 앱 내의 이벤트가 아닌 외부 이벤트로부터 받은 내용을 알려주기 위한 것이 목적

 

**Service

    => 백그라운드에서 실행되고 사용자의 상호작용에는 사용하지 않는 컴포넌트

    => 안드로이드에서의 스레드와의 차이점

        - 안드로이드에서 스레드 : 앱이 백그라운드로 진입시 종료됨

        - 서비스 : 앱이 백그라운드로 진입하더라도 계속 작업을 수행

    => 서비스 or 스레드가 고민될 경우 : 작업 수행중 전화, 타 이벤트 발생시 계속 수행하도록 하려면 서비스, 종료되게 하려면 스레드

        - 서비스를 사용하는 대표적인 애플리케이션이 음악재생 앱 

 

1. Background Daemon

    => 백그라운드에서 동적인 프로세스로 서비스에 가장 가까운 형태의 스레드

    => 다른 스레드가 동작중이 아니면 종료되는 스레드

        - 스마트 폰에서는 메인 스레드를 강제로 종료하지는 않음

        - 스마트 폰에서는 메인 스레드를 종료하려면 앱을 화면에 보이지 않게 하는 방법 대신 직접 선택하여 종료

 

2. 원격 호출 인터페이스 : 다른 클라이언트를 위해서 특정한 기능을 제공하는 역할을 수행

    => 자신의 기능을 메소드로 노출시켜서 다른 클라이언트가 이 메소드를 호출함으로 서비스를 이용하는 방법

    => 명칭 : 윈도우즈 - COM, 표준용어 - CORBA, Java - RMI(Remote Method Invocation), Android - AIDL, 바운드 서비스

 

3. 서비스 생성과 시작

  1) Service 클래스로부터 상속받는 클래스를 생성하여 필요한 메소드를 재정의

  2) AndroidManifest.xml 파일에 서비스 등록

  3) 스타트 서비스 시작과 종료

    => Intent intent = new Intent(Context context, 서비스 클래스명.class);

    => startService(intent); //서비스 시작

    => stopService(intent); //서비스 종료

  4) 바운드 서비스

    => ServiceConnection 객체(인터페이스이기 때문에 Anonymous 객체를 생성)를 추가

Intent intent = new Intent(Context context, 서비스클래스명.class);
bindService(intent, ServiceConnection객체, 옵션); //서비스 시작

unbindService(ServiceConnection객체); //서비스 종료

 

4. 데이터 공유

  1) StartService의 경우 BroadcastReceiver를 이용

  2) BindService의 경우 Bind객체를 이용

 

5. 스타트 서비스

    => 다른 애플리케이션에 의해서 실행되는 서비스

    => 자신을 시작시킨 애플리케이션이 더이상 포그라운드에 존재하지 않아도 계속 실행

    => 메인 스레드에서 호출 - 다른 스레드에서 호출시 예외가 발생 : 오레오부터 적용

    => 종료시킬때 외부에서는 stopService를 호출하고, 자신의 서비스 내에서 종료시킬경우 stopSelf()를 호출

 

6. 백그라운드에서도 노래를 재생할 수 있는 앱을 작성

    => 백그라운드에서 음악만 재생할 거라면 BroadcastReceiver와 Service가 1개면 가능

    => 음악을 재생할 때 Activity에서 재생율을 프로그래스바로 출력 하려고 하면 스레드 1개와 BroadcastReceiver가 1개 필요

  1) 필요한 이미지를 drawable에 복사

  2) 재생할 음원파일을 raw디렉토리에 복사

    => res 디렉토리에 raw 디렉토리를 생성

    => 생성된 디렉토리에 복사

  3) 실행 가능한 Activity 추가(SoundPlayActivity)

  4) Service 클래스 만들기 - PlayService

public class PlayService extends Service
implements MediaPlayer.OnCompletionListener{

    //음원 재생 가능한 클래스의 참조형 변수
    MediaPlayer player;

    //서비스와의 데이터 공유에 사용할 Broadcast Receiver
    BroadcastReceiver receiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    //전송해 준 데이터 읽기
                    //stop 이나 start 라는 문자열을 전송 - mode
                    String mode = intent.getStringExtra("mode");
                    if(mode != null){
                        if(mode.equals("start")){
                            try{
                                //재생 중이라면
                                if(player != null && player.isPlaying()){
                                    //재생을 중지하고 메모리 정리
                                    player.stop();
                                    //메모리 해제
                                    player.release();
                                    //가베지 컬렉터를 호출할 수 있도록 해주는 구문
                                    player = null;
                                }
                                //새로 생성
                                player = MediaPlayer.create(
                                        getApplicationContext(), R.raw.test);
                                player.start();

                                //리시버를 호출
                                Intent aIntent = new Intent(
                                        "com.example.PLAY_TO_ACTIVITY");

                                //음원이 재생 중인지 확인해 줄 수 있도록 해주기 위한 값
                                aIntent.putExtra("mode", "start");

                                //재생 중인 위치를 알 수 있도록 해주기 위한 값
                                aIntent.putExtra("duration", player.getDuration());
                                sendBroadcast(aIntent);
                            }catch(Exception e){
                                Log.e("음원 재생 예외", e.getMessage());
                                e.printStackTrace();
                            }
                        }else if(mode.equals("stop")){
                            if(player!= null && player.isPlaying()){
                                player.stop();
                            }
                            player.release();
                            player = null;
                        }
                    }
                }
            };

    //MediaPlayer.OnCompletionListener 인터페이스의
    //재생이 종료되었을 때 호출되는 메소드
    @Override
    public void onCompletion(MediaPlayer mp){
        //종료 되었으므로 종료 되었다고 방송을 하고 서비스를 중지
        Intent intent = new Intent("com.example.PLAY_TO_ACTIVITY");
        intent.putExtra("mode", "stop");
        sendBroadcast(intent);
        //서비스를 중지 - 이 메소드를 호출하지 않으면 서비스는 계속 살아있음
        stopSelf();
    }

    //서비스가 만들어질 때 호출되는 메소드
    @Override
    public void onCreate(){
        super.onCreate();
        //리시버 등록
        registerReceiver(receiver,
                new IntentFilter("com.example.PLAY_TO_SERVICE"));
    }

    //서비스가 종료될 때 호출되는 메소드
    @Override
    public void onDestroy(){
        //리시버 등록 해제
        unregisterReceiver(receiver);
        //파괴할 때는 상위 클래스의 메소드를 뒤에서 호출 - 소멸자
        super.onDestroy();
    }

    //서비스가 중지되었다가 재시작 되었을 때 호출되는 메소드
    @Override
    public int onStartCommand(
            Intent intent, int flags, int startId){
        if(player != null){
            Intent aIntent = new Intent("com.example.PLAY_TO_ACTIVITY");
            aIntent.putExtra("mode", "restart");
            aIntent.putExtra("duration",player.getDuration());
            aIntent.putExtra("current",player.getCurrentPosition());
            sendBroadcast(aIntent);
        }

        //리턴이 있는 메소드를 오버라이딩 할 때 메소드의 역할을 잘 모르겠으면
        //상위 클래스의 메소드를 호출해서 리턴하면 됩니다.
        return super.onStartCommand(intent, flags, startId);
    }

    public PlayService() {
    }

    @Override
    //스타트 서비스 일 때는 필요가 없고 바운드 서비스에서만
    //구현
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

 

  5) 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SoundPlayActivity">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/background"
        android:layout_alignParentTop="true"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_play"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="36dp"
        android:layout_marginLeft="24dp"
        android:id="@+id/play"
        android:clickable="true"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Service Test"
        android:textSize="30sp"
        android:textColor="@android:color/white"
        android:layout_alignTop="@id/play"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="16dp"
        android:layout_centerHorizontal="true"
        android:layout_centerInParent="true"
        android:layout_centerVertical="true"
        android:id="@+id/title"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_stop"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="36dp"
        android:layout_marginRight="24dp"
        android:id="@+id/stop"
        android:clickable="true"/>

    <ProgressBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/progress"
        android:layout_above="@id/title"
        android:layout_marginBottom="24dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>

</RelativeLayout>

--------

  6) Activity 작성

    => 필요한 인스턴스 변수를 선언

//화면에 보여지는 뷰 변수
ImageView playBtn, stopBtn;
TextView titleView;
ProgressBar progressBar;

//스레드 동작 여부
boolean runThread;

 

    => onCreate 메소드에서 위에서 만든 4개의 뷰 변수 찾아오기

playBtn = (ImageView)findViewById(R.id.play);
stopBtn = (ImageView)findViewById(R.id.stop);
progressBar = (ProgressBar)findViewById(R.id.progress);
titleView = (TextView)findViewById(R.id.title);

 

    => 스레드 클래스 만들기 - 1초마다 프로그래스 바의 진행율을 재설정하고 끝까지 도달시 스레드를 멈춤

    //프로그래스 바의 진행율을 표시할 스레드를 생성
    class ProgressThread extends Thread{
        //스레드로 동작할 메소드
        public void run(){
            while (runThread) {
                progressBar.incrementProgressBy(1000);
                SystemClock.sleep(1000);
                if (progressBar.getProgress() == progressBar.getMax()) {
                    runThread = false;
                }
            }
        }
    }

 

    => 리시버 만들기 : start, stop, restart 메시지에 따라 프로그래스 바의 설정을 변경하는 리시버

    //서비스와 통신하기위한 Broadcast Receiver 생성
    //상대방이 mode에 start와 stop이라는 글자를 전송해주고
    //duration에 전체 재생 시간을 전송해줌
    BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String mode = intent.getStringExtra("mode");
            if(mode != null){
                if("start".equals(mode)){
                    //duration의 값을 정수로 가져오고 없으면 0
                    //Map은 없는 값을 가져오면 null
                    int duration = intent.getIntExtra("duration", 0);
                    progressBar.setMax(duration);
                    progressBar.setProgress(0);
                }else if("stop".equals(mode)){
                    runThread = false;
                }else if("restart".equals(mode)){
                    //재시작하는 경우 전체 재생 시간과 현재 재생시간을
                    int duration = intent.getIntExtra("duration", 0);
                    int current = intent.getIntExtra("current", 0);
                    progressBar.setMax(duration);
                    progressBar.setProgress(current);
                    //스레드는 재시작이 안되므로 새로 생성하여 시작
                    runThread = true;
                    ProgressThread thread = new ProgressThread();
                    thread.start();
                    playBtn.setEnabled(false);
                    stopBtn.setEnabled(true);


                }
            }
        }
    };

 

    => onCreate 메소드에서 이미지의 클릭 이벤트 처리 코드를 작성

        //리시버 등록
        //리시버는 사용하는 반대편에서 등록
        registerReceiver(receiver, new IntentFilter("com.example.PLAY_TO_ACTIVITY"));

        //서비스 시작
        Intent intent = new Intent(this, PlayService.class);
        startService(intent);

        //이미지 클릭 처리
        playBtn.setOnClickListener(new ImageView.OnClickListener() {
            @Override
            public void onClick(View view) {
                //com.example.PLAY_TO_SERVICE에게 방송
                Intent intent = new Intent("com.example.PLAY_TO_SERVICE");

                //필요한 데이터 작성
                intent.putExtra("mode", "start");
                //방송 시작
                sendBroadcast(intent);

                //진행율을 표시하기 위한 스레드 시작
                runThread = true;
                ProgressThread thread = new ProgressThread();
                thread.start();

                //UI에서 고려
                //토글 형태로 동작해야하는 요소가 있다면 각각의 동작을 구분해줄 필요가 있음
                //숨기기, 동작하지 않게, 색상 변경
                playBtn.setEnabled(false);
                stopBtn.setEnabled(true);
            }
        });

        //중지버튼 클릭 처리
        stopBtn.setOnClickListener(new ImageView.OnClickListener() {
            @Override
            public void onClick(View view) {
                //com.example.PLAY_TO_SERVICE에게 방송
                Intent intent = new Intent("com.example.PLAY_TO_SERVICE");

                //필요한 데이터 작성
                intent.putExtra("mode", "stop");
                //방송 시작
                sendBroadcast(intent);

                //진행율을 표시하기 위한 스레드 시작
                runThread = false;
                progressBar.setProgress(0);

                //UI에서 고려
                //토글 형태로 동작해야하는 요소가 있다면 각각의 동작을 구분해줄 필요가 있음
                //숨기기, 동작하지 않게, 색상 변경
                playBtn.setEnabled(true);
                stopBtn.setEnabled(false);
            }
        });

 

  7) 실행

    => 시작 버튼 클릭 : com.example.PLAY_TO_SERVICE라는 Broadcast에게 메시지를 전송

        - mode라는 키로 start라는 데이터를 같이 전송

        - 진행율을 표시하는 스레드로 시작

    => stop 버튼 클릭 : com.example.PLAY_TO_SERVICE라는 Broadcast에게 메시지를 전송

        - mode라는 키로 stop이라는 데이터를 같이 전송

        - 진행율을 표시하는 스레드를 중지

    => 이 원리를 이용하여 다운로드 받는 과정을 출력해줄 수 있음

        - 음악을 재생하는 부분을 다운로드 받는 코드로 변경

        - duration에 음악의 전체 재생 시간을 전송했는데 다운받는 스트림의 전체사이즈를 전송

        - available() 이라는 메소드를 이용시 읽을수 있는 전체크기(다운받는 데이터 전체 크기)를 가져올수 있음

        - 백분율로 표시하고자 할 경우 progress 현재값 / progress 최대값 * 100 하면 됨

 

**Intent Service

    => CPU를 오랜시간 사용하는 작업을 서비스로 수행하고자 할 경우 사용

    => 새로운 스레드를 만들어서 작업을 수행하여 작업이 다른 애플리케이션의 성능에 영향을 미치는 것을 방지 할 목적으로 만드는 서비스

    => 비동기식으로 동작하기 때문에 여러개의 작업 수행시 수행순서를 알수 없음

        - 토렌트의 다운로드와 유사

    => IntentService 클래스로부터 상속받아 만들고, onHandleIntent 메소드만 재정의 하면 됨

    => StartService는 반드시 종료를 직접해야 하지만 Intent 서비스는 자신의 작업이 전부 완료되면 자동으로 종료

 

실습 - 로그를 출력하는 작업을 IntentService로 실행

1. 실행가능한 Activity - IntentServiceActivity

 

2. 백그라운드 작업을 위한 IntentService 클래스를 생성

public class MyIntentService extends IntentService {

    public MyIntentService(String name) {
        super(name);
    }

    //백그라운드에서 수행할 내용 작성
    //게임 같은 경우 하나의 필드를 가져와서 게임을 진행하고 있는 경우
    //근처의 다른 필드를 다운로드
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        for(int i=0; i<10; i+=1){
            SystemClock.sleep(1000);
            Log.e("TAG", "Intent Service : " + i);
        }
    }
}

 

3. Activity 클래스의 onCreate 메소드에서 서비스를 실행하는 코드를 작성

//서비스를 시작
Intent intent = new Intent(this, MyIntentService.class);
startService(intent);

 

**시스템 서비스

    => 안드로이드 시스템이 제공하는 서비스

    => 자신만의 별도의 방법으로 서비스를 제공받아서 사용

1. 생성

    => 시스템 서비스명 변수 = (시스템서비스명)getSystemService(서비스에 해당하는 상수);

    => LayoutInflater(xml로 만든 레이아웃을 자바의 View로 변경해주는 서비스)

    => 알림을 만들어주는 NotificationManager

    => 최상위 액티비티를 확인하고자 할 경우 ActivityManager를 생성

    => 앱 설치 여부 확인시 PackageManager를 생성

 

2. AlarmManager

    => 미리 지정해 놓은 시간에 이벤트를 발생시키는 장치

    => 장래의 특정 시점이나 일정 시간 경과 후에 할 작업을 등록하고 싶은 경우 사용

  1) 생성

    => AlarmManager al = (AlarmManager)getSystemService(Context.ALARM_SERVICE);

  2) 알람 등록

    => set(int type, long triggerAtTime, PendingIntent intent)

    => setRepeating(int type, long triggerAtTime, long interval PendingIntent intent) : interval 단위로 계속 동작

    => type

        - RTC(1970년 1월 1일 00:00을 기준으로 1/1000초 단위로 시간을 표시

        - RTC_WAKEUP  - 장비를 깨움

        - ELAPSED_REALTIME: 부팅된 시간으로 부터 지나온 시간

        - ELAPSED_REALTIME_WAKEUP

 

  3) PendingIntent 생성

    => Intent intent = new Intent(Context context, 서비스 클래스.class);

    => PendingIntent pIntent = PendingIntent.getService(Context context, 구분할 정수, intent, PendingIntent 옵션);

    => 서비스 클래스 대신에 BroadcastReceiver를 사용해도 됨

 

 

3. 버튼을 누르고 10초 후에 토스트 출력

  1) 실행 가능한 Activity 1개 생성

  2) 알람시간이 되면 동작할 BroadcastReceiver 클래스 생성

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "알람", Toast.LENGTH_LONG).show();
    }
}

 

  3) layout에 버튼을 1개 배치

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".AlarmActivity">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="알람"
        android:id="@+id/btn"/>

</LinearLayout>

 

  4) Activity.java 파일의 onCreate메소드에 버튼을 클릭했을 때 수행 할 동작을 작성

 

**Content Provider

    => 안드로이드의 컴포넌트로 2개의 애플리케이션이 데이터를 공유하기 위한 개념

    => 안드로이드 자체에서 연락처나 이미지 갤러리등은 자체적으로 기능 제공

        - 우리가 만든 애플리케이션끼리의 데이터 공유를 위한 목적으로 사용

    => 실제 앱 개발에서는 서버를 먼저 구성하고, 업데이트된 데이터를 서버에 업로드

        - 다른 애플리케이션 처음 접속시 서버로부터 데이터를 다운받는 방식으로 구현하는 경우가 많음

        - 위 방식 구현시 서버와 연결 해야하므로 반드시 네트워크가 사용가능해야 함

        - 대안 : 서버 접속되었을 때 받아온 데이터를 로컬에 저장, 네트워크가 되면 서버에서 받아오고, 안되면 로컬 데이터를 이용하게 구현

    => push server를 이용하여 데이터 변경을 로컬 Notification으로 알려주는 경우도 많음

    => 실제 애플리케이션 개발에서는 앱끼리 데이터를 공유하는 것보다는 기본 앱의 데이터를 가져와서 사용하는 것이 더 중요

1. 생성

    => ContentProvider로부터 상속받는 클래스를 생성

    => 메소드 재정의

  1) String getType

    => 잘못된 URI가 왔을 때 리턴 할 문자열만 설정

  2) Cursor query(Uri uri, String projection, String selection, String[] selectionArgs, String sortOrder){ return Cursor; }

    => select 구문을 수행해주는 메소드와 유사

 

    - uri : ContentProvider의 Uri/테이블명

    - projection : 가져올 컬럼의 이름 배열

    - selection : 가져올 조건 - SQL에서는 where절

    - selectionArgs : selection에 값을 직접 입력하지 않고 ?로 만들었을 때 실제 대입될 데이터의 배열

    - sortorder : 정렬할 컬럼 명

  3) Uri insert(Uri uri, ContentValues values) : 삽입할 때 사용하는 메소드

  4) int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) : 갱신할 때 사용하는 메소드

  5) int delete(Uri uri, Sstring selection, String[] selectionArgs) : 삭제에 사용할 메소드

  6) boolean onCreate() : 객체가 처음 만들어질 때 호출되는 메소드

    => 이 메소드에서 데이터베이스에 연결하고 데이터베이스 사용객체를 생성

 

2. AndroidManifest에 등록

    => 등록시(퍼미션을 만들어주어야 함)

        - Android:readPermission="프로바이더의 authorities.READ_DATABASE

        - Android:writePermission="프로바이더의 authorities.WRITE_DATABASE

      

3. 사용하고자 하는 경우 AndroidManifest에 권한 설정을 해주어야 함

<permission android:name="프로바이더의 authorities.READ_DATABASE"
	android:protectionLevel="nomal"/>
<permission android:name="프로바이더의 authorities.WRITE_DATABASE"
	android:protectionLevel="nomal"/>

 

**시스템 앱의 데이터 공유

    => 연락처나 이미지 갤러리의 데이터는 모든 앱에서 사용이 가능

        - 대신 권한 설정이 필요

    => 안드로이드 6.0 이상부터는 동적인 권한을 요청해야 함

        - 매우 번거로움

1. 퍼미션 요청을 쉽게하는 라이브러리

    => 표준 라이브러리가 아니므로 외부에서 다운로드

    => 중앙 저장소에도 없으므로 직접 github에서 다운로드 받아 사용

    => 별도의 저장소를 지정하여 다운로드

    => 해당 라이브러리에는 AutoPermisionListener 인터페이스가 존재하므로 이 인터페이스를 구현하면 됨

        - 재정의해야하는 메소드는 onRequestPermissionsResult, onDenied,  onGranted

        - 필요한 권한이 있으면 AutoPermissions.Companion.loadAllPermissions만 호출하면 됨

 

2. 연락처에서 이름과 전화번호를 가져와서 텍스트뷰에 출력하고 이미지 갤러리에서 이미지를 선택하여 이미지뷰에 출력하는 예제

  1) 퍼미션 설정

    => 연락처 사용을 위한 것(READ_CONTACTS)

    => 이미지가 외부 저장소에 있는 경우 외부 저장소 사용 권한(READ_EXTERNAL_STORAGE)

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

 

  2) 동적 퍼미션 확인을 쉽게 할 수 있는 라이브러리 의존성 설정

    => build.gradle에서 수행

allprojects {
    repositories {
        maven {url 'https://jitpack.io'}
    }
}

dependencies {
    implementation 'com.github.pedroSG94:AutoPermissions:1.0.3'

    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

 

  3) 실행가능한 Activity추가(BasicAppShareActivity)

 

  4) 레이아웃 수정

    => 전화번호를 출력할 TextView, 이미지를 출력할 ImageView, 버튼 2개 배치

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".BasicAppShareActivity">
    
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/lblContacts"/>
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/imgGallery"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="연락처"
            android:id="@+id/btnContacts"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="갤러리"
            android:id="@+id/btnGallery"/>
    </LinearLayout>

</LinearLayout>

 

  5) Activity 클래스의 권한 동적 요청 관련 작업 수행

    => Activity 클래스에 AutoPermissionsListener 인터페이스를 implements

public class BasicAppShareActivity extends AppCompatActivity implements AutoPermissionsListener

 

    => 필요 메소드 재정의

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

    //권한을 거부할 때 호출되는 메소드 : AutoPermissionsListener의 메소드
    @Override
    public void onDenied(int i, String[] strings) {
        Toast.makeText(this, "권한 사용하지 않으면 기능 사용 못함", Toast.LENGTH_LONG).show();
    }

    //권한 허가시 호출되는 메소드 : AutoPermissionsListener의 메소드
    @Override
    public void onGranted(int i, String[] strings) {
        Toast.makeText(this, "권한 사용을 허가 하였습니다.", Toast.LENGTH_LONG).show();
    }

    //권한 요청을 하고 권한에 대한 응답을 했을 때 호출되는 메소드
    //Activity의 메소드
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults){
        //상위 클래스의 메소드 호출
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //권한 요청 결과를 AutoPermission에 전송하여 메소드를 호출하도록 해줌
        AutoPermissions.Companion.parsePermissions(this, requestCode, permissions, this);
    }

 

    => onCreate 메소드에서 권한을 요청하는 코드를 추가

//시작하면 필요한 권한을 요청
AutoPermissions.Companion.loadAllPermissions(this, 101);

 

  6) 뷰들의 참조형 변수를 인스턴스 변수로 추가

TextView lblContacts;
ImageView imgGallery;
Button btnContacts, btnGallery;

 

  7) onCreate 메소드에서 변수와 실제 디자인 한 객체를 연결

//뷰 찾아오기 
lblContacts = (TextView)findViewById(R.id.lblContacts);
imgGallery = (ImageView)findViewById(R.id.imgGallery);
btnContacts = (Button)findViewById(R.id.btnContacts);
btnGallery = (Button)findViewById(R.id.btnGallery);

 

  8) onCreate 메소드에 연락처 버튼을 눌렀을 때 수행할 내용을 작성

//연락처 버튼을 눌렀을 때 수행 할 코드
btnContacts.setOnClickListener(new Button.OnClickListener() {
	@Override
	public void onClick(View view) {
		//연락처 인텐트 생성
		Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);

		//연락처 출력
		startActivityForResult(intent, 10);
	}
});

 

  9) onCreate메소드에 이미지 버튼을 눌렀을 때 수행 할 내용을 작성

//이미지 버튼을 눌렀을 때 수행 할 내용을 작성
btnGallery.setOnClickListener(new Button.OnClickListener() {
	@Override
	public void onClick(View view) {
		//사진 앱을 전부 호출
		Intent intent = new Intent();
		intent.setType("image/*");
		intent.setAction(Intent.ACTION_GET_CONTENT);
		startActivityForResult(intent, 20);
	}
});

 

  10) 호출한 인텐트가 없어질 때 호출되는 메소드를 재정의

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        //이미지를 선택했을 때 수행할 내용
        if(requestCode == 20 && resultCode == RESULT_OK){
            Uri fileUri = data.getData();
            ContentResolver resolver = getContentResolver();
            try{
                //파일 전송을 위해 필요
                InputStream inputStream = resolver.openInputStream(fileUri);
                Bitmap imgBitmap = BitmapFactory.decodeStream(inputStream);
                imgGallery.setImageBitmap(imgBitmap);
                inputStream.close();
            }catch (Exception e){
                Log.e("이미지 가져오기 예외", e.getMessage());
            }
        }
        //연락처가 없어졌을 때 수행할 내용
        if(requestCode == 10 && resultCode == RESULT_OK){
            try{
                //선택한 연락처 가져오기
                //선택한 항목의 id 찾아오기
                String id = Uri.parse(data.getDataString()).getLastPathSegment();
                //id를 가지고 연락처 가져오기
                Cursor cursor = getContentResolver().query(ContactsContract.Data.CONTENT_URI,
                        new String[]{ContactsContract.Contacts.DISPLAY_NAME},
                        ContactsContract.Data._ID + "=" + id, null, null);
                cursor.moveToNext();
                String name = cursor.getString(0);
                String phone = cursor.getString(1);
                lblContacts.setText(name + ":" + phone);
                lblContacts.setTextSize(20);
            }catch (Exception e){
                Log.e("연락처 예외", e.getMessage());
            }
        }
    }

 

 

Tip!

**대화상자(Dialog)의 종류

    => Modal : 화면위에 출력되면 제어권을 대화상자가 가지고 이 제어권을 자신이 종료되기 전까지 다른 View에게 뺏기지 않는 대화상자

        - MS-Windows 프로그래밍에서는 Modal 대화상자가 많이 사용됨

        - 대화상자에 적용 버튼이 없고 확인 버튼만 있으면 이 대화상자가 Modal

        - 모바일에서는 UI를 해치는 요소중 하나로 간주

        - 다운로드 받는 UI를 화면에 출력하고자 할 경우 많이 사용하던 것이 Android Progress Dialog인데 이 대화상자가 Modal

        - 다운로드 받는 비율을 화면에 출력할 때 ProgressDialog를 출력하면 다운로드가 끝날때까지 아무작업도 할수 없음

        - 위의 이유로 안드로이드 최신 API에서는 ProgressDialog가 deprecated

        - 최근에는 Progress View를 이용하여 스레드로 진행율을 보여주던가 아니면 시스템이 제공하는 Progress 이용 권장

    => Modaless(Non - Modal) : 화면에 출력된 상태에서도 다른 뷰로 제어권을 옮길수 있는 대화상자

        - Apple MacOS X이 대부분 Modeless 대화상자 사용

        - 대화상자에 적용버튼이 있으면 무조건 Modeless

    => Modal에 비해 Modeless 대화상자를 사용하는 것이 프로그래밍에서 조금 더 어려움

        - Modal은 자신이 출력되면 코드를 다음으로 진행시키지 않으므로 지역변수로 생성해도 됨

        - Modeless에서 지역변수로 생성시 잘못하면 메모리 누수(leak)가 발생할 수 있음

        - 메소드 내부에서 Modeless 대화상자를 지역변수로 생성했으나 다른 곳으로 제어권을 넘길경우 다른곳에서 닫을 수 없음

    => 스마트폰 API에서 일반적으로 Dialog는 Modal, Sheet는 Modeless

 

**테스트 버전

    => Alpha Test : 개발이후 개발자의 장소에서 사용자가 테스트

    => Beta Test : 개발이후 사용자의 장소에서 사용자가 테스트

    => rc(Release Candidate) : 베타 테스트 후에 버그 패치하고 기능 추가, 삭제, 갱신들을 수행한 출시직전의 버전

 

    => 오픈소스 라이브러리 이용할 때, 개발시에는 테스트 버전 사용 불가

        - 위의 단어가 붙지 않거나 RTM(Release To Manufacturing - 시장출시버전)이라는 단어가 붙은 버전만 사용

 

**Component

    => Activity : 화면

    => Broadcast : 방송을 보내서 특정 기능을 하도록 하는 것

    => Service : 백그라운드에서 작업

 

    => ContentProcider : 애플리케이션끼리 데이터를 공유

        - 안드로이드는 다른 애플리케이션의 컴포넌트를 사용가능

        - 사용가능하게 만들 경우 컴포넌트 등록시 별명을 붙여서 별명을 이용하여 실행 

    => RPC(Remote Procedure Call - 윈도우 프로그래밍에서는 마샬링, com Java에서는 RMI, CORBA등) : 원격프로시저 호출

    => 클라이언트 - 서버 : 클라이언트가 서버에 요청, 서버가 작업 처리하고 결과를 클라이언트에 전송

        (클라이언트가 서버의 자원을 이용하여 프로시저 수행 - 대표적인 시스템이 Web Server)

        - Point to Point : 일대 다로 통신하기 위한 개념

 

**서비스를 이용하는 애플리케이션

    => 백그라운드에서 음원 재생

    => 사이즈가 큰 데이터를 다운로드 받는 애플리케이션

    => 위치 정보를 계속해서 사용해야 하는 애플리케이션

    => 근거리 네트워크를 이용해야 하는 애플리케이션

 

**안드로이드, 아이폰에서 이미지뷰나 텍스트를 출력하는 Lable, TextView는 기본적으로 사용자의 인터랙션(상호작용 - 터치)이 안됨

    => 어떤 속성의 값을 변경 해주어야만 인터랙션이 가능해짐

    => 안드로이드는 Clickable이고, 아이폰은 userInteractionEnabled 속성

 

**작업 분류

  1) CPU를 많이 사용하는 작업 : 연산(계산)

    => 안드로이드에서는 IntentService를 이용하여 수행

    => 여러개의 작업시 서로간의 연관성이 없어야 함

    => Intent Service를 여러개 만들면 수행 종료 시간을 알수 없음

  2) CPU를 작게 사용하는 작업 : 입출력

    => 입출력하는 작업은 Service(백그라운드 스레드)를 만들어서 처리하는 것이 유용

  3) 속도를 가지고 분류

    => 주기억 장치의 데이터를 이용하는 작업

    => 보조기억장치(파일을 릭고 쓰기)나 외부와 연결(키보드, 모니터, 네트워크)에 의해 이루어지는 작업

 

**Android의 4대 컴포넌트

  1) Activity : 화면

  2) Service : 백그라운드 작업

  3) Broadcast : 통신 - Activity와 Service간의 데이터 전송에도 사용됨

  4) ContentProvider : 데이터 공유

    => 안드로이드의 4대 컴포넌트는 생성만으로는 사용할 수 없고 AndroidManifest 파일에 등록을 해야만 사용가능

        - 직접 클래스를 생성한 경우 등록하는 코드를 작성해야 함

        - Activity는 없는 Activity 호출시 예외가 발생하지만 Broadcasst, Service, ContentProvider는 아무일도 일어나지 않음

        - 호출부에서는 예외가 발생하지 않고 작업 도웆에 예외가 발생할 수는 있음

        - 디버깅이 어려워 Broadcast, Service, ContentProvider 사용시 시작 메소드에 로그 출력하는 것이 좋음

        - 로그 미출력시 설정 오류나 잘못된 이름을 사용하였을 가능성이 높음

    => 웹서비스 제작시 클래스를 만들때 생성자를 만들어 로그를 출력해두는 것이 좋음

        - 로그 미출력시 어노테이션 오류나 패키지명이 잘못되거나, servlet-context.xml파일의