안드로이드&IOS 앱 개발자 양성

안드로이드&iOS 앱 개발자 양성(84~85일차)

HRuler 2020. 8. 5. 14:54
더보기

** Bottom sheet

** Pull To Refresh
1. 프로젝트를 1개 생성
2. 필요한 의존성을 설정
3. 기본 레이아웃을 수정
4. Activity.java 파일에 인스턴스 변수 선언
5. Activity.java 파일의 onCreate 메소드에서 초기화 작성 - onCreate에서 하는 작업은 onResume에서 해도 됨(onResume은 활성화될 때마다 호출되는 메소드)
6. 서버에서 데이터나 로컬에서 가져오는 데이터의 없데이트

** Broadcast Receiver
1. Broadcast 인텐트 생성
2. Broadcast Receiver
3. System Broadcast 이용
4. Android에서 버전 주의
5. 작성법
6. 실습 - 다른 모듈이 수신자를 호출

** Local Notification(알림)
1. 관련된 클래스
2. Oreo 하위 버전까지 고려해서 작성하는 경우

** Service
1. Background Daemon
2. 원격 호출 인터페이스 : 다른 클라이언트를 위해서 특정 기능을 제공하는 역할을 수행하는데 자신의 기능을 메소드로 노출시켜서 다른 클라이언트가 이 노출된 메소드를 호출함으로 서비스를 이용하는 방법
3. 서비스 생성과 시작
4. 데이터 공유
5. 스타트 서비스
6. 백그라운드에서도 노래를 재생할 수 있는 앱 작성

** 실습 - 로그를 출력하는 작업을 IntentService로 실행
1. 실행 가능한 Activity 생성 - IntentServiceActivity
2. 백그라운드 작업을 위한 IntentService 클래스를 생성
3. Activity 클래스의 onCreate 메소드에 서비스를 실행하는 코드 작성

** 시스템 서비스
1. 생성
2. AlarmManager
3. 버튼을 누르고 10초 후에 토스트를 출력하기

** Content Provider
1. 생성
2. AndroidManifest에 등록
3. 사용하고자 하는 경우 AndroidManifest에 권한 설정해 주어야 한다.

** 시스템 앱의 데이터 공유
1. 퍼미션 요청을 쉽게하는 라이브러리
2. 연락처에서 이름과 전화번호를 가져와서 텍스트 뷰에 출력하고 이미지 갤러리에서 이미지를 선택해서 이미지 뷰에 출력하는 예제

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

** 테스트 버전의 종류

** Component

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

** 문자열 변수와 상수가 같은지 비교할 때

** 언어별 메모리 정리

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

** 안드로이드나 아이폰에서 이미지를 출력하는 이미지 뷰나 단순한 텍스를 출력하는 Label, TextView는 기본적으로 사용자와의 인터랙션(상호작용 - 터치)이 안된다.

** 작업을 분류

** 속도를 가지고 작업 분류

** Android의 4대 컴포넌트

** 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"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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:id="@+id/swipeLayout">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/listView"/>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

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.ner");

        //어댑터 생성
        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 또는 sendStickBroadcast, sendOrderedBroadcast 메소드를 호출하면 된다.

- Activity와 다르게 동작하는데 Activity는 호출했을 때 없으면 예외가 발생하지만 Broadcast는 호출했을 때 없으면 아무 일도 하지 않는다.

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

1. Broadcast 인텐트 생성

- 액션 문자열을 가지고 생성한다.

- 데이터와 카테고리 문자열을 선택적으로 가질 수 있다.

- 액션 문자열이나 카테고리 문자열은 대부분 자바의 패키지 이름의 형태를 갖는다.

- 설치를 하고 한번도 실행하지 않으면 사용정지 상태가 되서 호출되지 않는 상태가 된다.

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

2. Broadcast Receiver

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

- BroadcastReceiver 클래스로부터 상속받아서 onReceiver를 Overriding 해야 한다.

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

- 인텐트가 방송하면 onReceice 메소드가 호출되서 실행된다.

- 여기에 작성하는 내용은 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

  android.intent.action.BATTERY_CHANGED

  android.intent.action.ACTION_POWER_CONNECTED

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

4. Android 에서 버전 주의

1) 6.0 : 동적 권한 설정이 추가됨

- 이전에는 설치할 때 모든 권한을 전부 묻고 권한 설정을 함

   한번 권한 설정을 하면 변경이 어려운 문제가 발생했다.

- 6.0부터는 기능을 수행할 때 권한을 설정하는 것으로 변경됐다.

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

- Broadcast를 실행할 때 백그라운드에서 하는 것은 안된다. - 다른 Application에서 작성해서 호출하는 것은 안된다.

   액션 문자열을 이용해서 하지 말고 직접 클래스 이름을 명시해서 실행

- 안드로이드에서 다른 애플리케이션의 컴포넌트를 사용하고자 하는 경우에는 컴포넌트를 등록할 때 액션 문자열을 등록하면 된다.

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 파일에 등록

- 일반 클래스를 만들어서 상속을 받은 경우는 application 태그에 전부 작성

- 메뉴의 Receiver를 이용해서 작성한 경우는 action 태그만 추가

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

4) 추가한 모듈의 시작 Activity의 onCreate 메소드에 리시버를 등록

- 오레오 이후의 버전에서 사용할 수 있도록 하기 위해서 작성

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

5) 기존 모듈의 레이아웃 수정

- 상단에 버튼 1개 추가

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="브로드캐스트 송신"
        android:textSize="30sp"
        android:onClick="click"/>

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이면 화면을 어둡게 만든다던가 토스트나 스낵바를 출력해서 알림을 주는 형태로 많이 사용한다.

** Local Notification(알림)

- 앱의 각종 상황을 사용자에게 알리는 목적으로 사용한다.

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

- 예전에는 메시지 형태로 출력됐는데 지금은 상단에 잠깐 출력되고 쓸어내려서 확인하는 것도 가능하다.

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

1. 관련된 클래스

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

- 안드로이드에서는 시스템 서비스의 경우는 getSystemService(가져올 서비스 상수)를 대입해서 서비스를 위한 객체를 생성한다.

   시스템이 가지고 있는 서비스는 직접 객체를 생성하도록 하면 충돌의 문제가 발생할 수 있기 때문에 싱글톤으로 만들어두고 요청을 하면 싱글톤의 참조만 넘겨주는 형태로 사용된다.

   NotificationManager는 getSystemService(NOTIFICATION_SERVICE)를 이용해서 가져온다.

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

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

4) 이전에는 3) 클래스를 이용해서 알림을 생성했는데 지금은 NotificationChannel이 추가되어 Builder를 NotificationChannel로 생성 - 오레오에서 변경

2. 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).설정메소드 호출. 설정 메소드 호출....
}

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

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

- 알림을 클릭하면 PendingIntent가 호출 

4) PendingIntent 생성

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

- 여기에 작성하는 내용은 context 자리에 알림을 출력할 액티비티를 출력 : 데이터를 출력할 생각은 없고 화면만 출력하는 것인데 이 경우 onResume에서 데이터를 업데이트하면 업데이트된 데이터가 출력이 된다.

   시문 앱의 경우는 속보를 푸시 메시지로 보내주고 푸시 메시지가 도착하면 알림 창으로 출력을 한다.

   알림 창을 클릭하면 액티비티로 이동해서 기사들을 선택할 수 있도록 해준다.

- 액티비티 대신에 Receiver를 이용해서 Activity를 출력하는 형태를 작성하기도 한다.

   중간에 Receiver를 이용하는 이유는 Receiver에서 새로운 Intent를 만들어서 필요한 데이터를 Intent에 저장해서 Activity를 출력하기 위해서 이다.

   카카오톡 같은 메신저 서비스에서는 알림이 오면 그 알림의 내용만 액티비티 하단에 업데이트하기 위해서 이다.

5) Local Notification이 알람과 다른 점은 알람은 현재 App 내에서의 이벤트에 의해 동작을 하고 Local Notification은 현재 앱 내의 이벤트가 아니라 외부 이벤트로부터 받은 내용을 알려주기 위한 것이 목적이다.

 

** Service

- 백그라운드에서 실행되고 사용자와의 상호작용에서는 사용하지 않는 컴포넌트이다.

- 안드로이드에서의 스레드와의 차이점은 안드로이드에서 스레드는 앱이 백그라운드로 집입하면 종료가 되지만 서비스는 앱이 백그라운드로 진입하더라도 계속 작업을 수행한다.

- 서비스를 사용할지 스레드를 사용할지 고민되는 경우 작업을 수행하다 전화가 온다던가 하는 다른 이벤트가 발생했을 때 계속 수행하도록 하려면 서비스 그렇지 않고 종료되도록 하려면 스레드로 만들면 된다.

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

1. Background Daemon

- 백그라운드에서 동작하는 프로세스로 서비스에 가장 가까운 형태의 스레드

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

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

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

- 안드로이드에서는 스타트 서비스라고 한다.

2. 원격 호출 인터페이스 : 다른 클라이언트를 위해서 특정 기능을 제공하는 역할을 수행하는데 자신의 기능을 메소드로 노출시켜서 다른 클라이언트가 이 노출된 메소드를 호출함으로 서비스를 이용하는 방법

- 이를 윈도우즈에서는 COM이라고 불렀으며 표준 용어로는 CORBA라는 표현을 사용하고 Java에서는 RMI(Remote Method Invocation)라고 하고 안드로이드에서는 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를 추가 - SoundActivity

4) Service 클래스 만들기 - PlayService

package com.example.android0805;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.util.Log;

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 mediaPlayer) {
        //종료됐으므로 종료 되었다고 방송을 하고 서비스를 중지
        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) Activity 레이아웃을 수정

- android api에 익숙해지면 xml 대신에 자바나 코틀린 코드로 UI를 작성하는 것도 고려

<?xml version="1.0" encoding="utf-8"?>
<!-- RelativeLayout과 ConstraintLayout은 부모나 다른 뷰와의 관계를 이용해서 배치하는 레이아웃 -->
<!-- 안드로이드나 아이폰에서 권장하는 레이아웃         뷰들 사이의 여백 때문 -->
<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=".SoundActivity">

    <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:clickable="true"
        android:id="@+id/play"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Service Test"
        android:textSize="30sp"
        android:textColor="@android:color/white"
        android: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:clickable="true"
        android:id="@+id/stop"/>

    <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/Base.Widjet.AppCompat.ProgressBar.Horizontal"/>

</RelativeLayout>

6) Activity 작성

- 필요한 인스턴스 변수를 선언

    ImageView palyBbtn, stopBtn;
    TextView titleView;
    ProgressBar progressBar;

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

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

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

- 스레드 클래스 만들기 - 1초마다 ProgressBar의 진행율을 재설정하고 끝까지 도달하면 스레드를 멈춤

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

- 리시버 만들기 : start, stop, restart 메시지에 따라 프로그래스 바를 설정해주는 역할

    //Service와 통신하기 위한 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();
                    playBbtn.setEnabled(false);
                    stopBtn.setEnabled(true);
                }
            }
        }
    };

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

 

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를 오랜 시간 동안 사용하는 작업을 서비스로 수행하고자 할 때 사용한다.

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

- 비동기식으로 동작하기 때문에 여러 개의 작업을 수행할 때 수행 순서를 알 수 없다.

ex) 토렌트에서 영화 다운로드 받는 것과 유사

- IntentService 클래스로부터 상속받아서 만들고 onHandleIntent 메소드만 재정의 하면 된다.

- StartService는 반드시 종료를 직접해야 하지만 이 서비스는 작업이 전부 완료되면 자동으로 종료되기 때문에 직접 종료할 필요가 없다.

 

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

1. 실행 가능한 Activity 생성 - IntentServiceActivity

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

package com.example.android0805;

import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
import android.os.SystemClock;
import android.util.Log;

import androidx.annotation.Nullable;

/**
 * An {@link IntentService} subclass for handling asynchronous task requests in
 * a service on a separate handler thread.
 * <p>
 * TODO: Customize class - update intent actions, extra parameters and static
 * helper methods.
 */
public class MyIntentService extends IntentService {

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

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

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

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

        //서비스를 시작
        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일 자정을 0으로 해서 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, PendingIntet옵션);

- 서비스 클래스 대신에 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"
    tools:context=".AlarmActivity"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="알람"
        android:id="@+id/btn"/>

</LinearLayout>

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

public class AlarmActivity extends AppCompatActivity {
    Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_alarm);
        btn = (Button)findViewById(R.id.btn);

        btn.setOnClickListener(new Button.OnClickListener(){

            @Override
            public void onClick(View view) {
                //알람 시간 만들기
                Calendar calendar = Calendar.getInstance();
                calendar.add(Calendar.SECOND, 20);

                //알람 등록
                Intent intent = new Intent(AlarmActivity.this, AlarmReceiver.class);
                PendingIntent pItent = PendingIntent.getBroadcast(AlarmActivity.this, 0, intent, 0);
                AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
                am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pItent);
            }
        });
    }
}

 

** Content Provider

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

- 안드로이드 자체에서 연락처나 이미지 갤러리 등은 자체적으로 기능을 제공하고 있고 우리가 만든 애플리케이션끼리 데이터를 공유하기 위한 목적으로 사용된다.

- 실제 앱 개발에서는 서버를 먼저 구성하고 업데이트된 데이터를 서버에 업로드하고 다른 애플리케이션이 처음 접속할 때 데이터를 다운받는 방식으로 구현하는 경우가 많다.

   이렇게 구현하면 서버와 연결해야 하기 때문에 반드시 네트워크가 사용 가능해야 한다.

   이 대안으로 서버에 접속이 됐을 때 받아온 데이터를 로컬에 저장해서 네트워크가 되면 서버로부터 받아오고 그렇지 않으면 로컬의 데이터를 이용하는 방식으로 많이 구현한다.

- 여기 push server를 이용해서 데이터 변경을 로컬 노티피케이션으로 알려주는 경우도 많다.

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

1. 생성

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

- 메소드 재정의

1) String getType

- 잘못된 Uri가 왔을 때 리턴할 문자열만 설정

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

                retrun Cursor;

     }

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

   uri는 ContentProvider의 Uri/테이블 이름

   projection은 가져올 컬럼의 이름 배열 - SQL에서의 select 절

   selection이 가져올 조건 - SQL에서의 where 절

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

   sortorder는 정렬할 컬럼이름

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

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

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

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

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

2. AndroidManifest에 등록

- 등록할 때

android:readPermission="프로바이더의 authorities.READ_DATABASE
android:writhPermission="프로바이더의 authorities.WRITE_DATABASE

- 퍼미션을 만들어 주어야 한다.

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

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

 

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

- 연락처나 이미지 갤러리의 데이터는 모든 앱에서 사용이 가능하다.

   대신 권한 설정을 해야 사용 가능하다.

- 안드로이드 6.0 이상 부터는 동적 권한을 요청해야 한다.

   이 작업이 번거롭다.

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

- 이 라이브러리는 표준 라이브러리가 아니라서 외부에서 다운로드 받아야 한다.

- 중앙 저장소에도 없어서 직접 github에서 다운받아 사용해야 한다.

- 별도의 저장소를 지정해서 다운받아야 한다.

- 이 라이브러리에서는 AutoPermissionListener 인터페이스가 존재해서 이 인터페이스를 구현하면 된다.

   재정의해야 하는 메소드는 onRequestPermissionResult, 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) 동적인 퍼미션 확인을

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

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

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"
    tools:context=".BasicAppShareActivity"
    android:orientation="vertical">

    <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 클래스에 AutoPermissionListener 인터페이스를 implements

- 필요한 메소드 재정의

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

 

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_basic_app_share);
        //시작하자 마자 필요한 권한 요청
        AutoPermissions.Companion.loadAllPermissions(this, 101);
    }

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

    TextView lblContacts;
    ImageView imgGallery;
    Button btnGallery, btnContacts;

 

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(){
            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.CommonDataKinds.Phone.NUMBER},
                        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());
            }
        }
    }

 

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

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

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

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

   Modal은 모바일에서는 UI를 나쁘게 하는 요소 중 하나로 간주한다.

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

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

   이런 이유 때문에 안드로이드의 최신 API에서는 ProgressDialog가 deprecated

   최근에는 Progress View를 이용해서 스레드로 진행율을 보여주던지 시스템이 제공하는 Progress를 이용하는 것을 권장한다.

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

   Apple의 Mac OS X의 대부분 Modeless 대화상자를 이용한다.

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

- Modal에 비해서 Modeless 대화상자를 사용하는 것이 프로그래밍에서 조금 더 어렵다.

   Modal은 자신이 출력되면 코드를 다음으로 진행시키지 않기 때문에 지역 변수로 생성해도 된다.

   Modeless에서 지역변수로 생성하면 잘못하면 메모리 누수(leak)가 발생한다.

   메소드 안에서 Modeless 대화상자를 지역변수로 생성했는데 다른 곳으로 제어권을 넘겨버리면 다른 곳에서는 이 Modeless 대화상자를 닫을 수가 없다.

- 스마트 폰 API에서 일반적으로 Dialog라고 하면 모달이고 Sheet라고 하면 Modeless이다.

 

** 테스트 버전의 종류

- Alpha Test : 개발이후에 개발자의 장소에서 사용자가 테스트하는 것

- Beta Test : 개발이후에 사용자의 장소에서 사용자가 테스트하는 것

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

- 오픈 소스 라이브러리 이용할 때 개발을 할 대는 테스트 버전을 사용하면 안된다.

   저런 단어가 붙지 않거나 RTM(Release To Manufacturing - 시장 출시 버전)이라는 단어가 붙은 버전만 사용한다.

** Component

- Activity : 화면

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

- Service : 백그라운드에서 작업

- ContentProvider : 애플리케이션끼리 데이터를 공유

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

   이걸 사용 가능하도록 만들 때는 컴포넌트를 등록을 할 때 별명(name)을 붙여서 별명을 이용해서 실행한다.

   RPC(Remote Procedure Call - 윈도우 프로그래밍에서는 마샬링, com

   Java에서는 RMI, CORBA 등) : 원격 프로시저 호출

   클라이언트 - 서버 : 클라이언트가 서버에게 요청을 해서 서버가 작업을 처리하고 그 결과를 클라이언트에게 전송하는 개념, 클라이언트가 서버의 자원을 이용해서 프로시저를 수행 - 이 방식의 대표적인 시스템이 Web Server

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

 

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

   자신의 gradle 파일을 잘 찾아서 설정해야 한다.

 

** 문자열 변수와 상수가 같은지 비교할 때

if(mode.equals("start"))    //mode가 null이면 NullPointerException

if("start".equals(mode))   //mode가 null이면 false

 

** 언어별 메모리 정리

- 자바는 메모리 정리를 위해서는 null만 대입하면 된다.

- 멀티미디어는 시스템 자원을 사용하는 라이브러리이다.

- 안드로이드 시스템은 C언어이다.

 

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

- 백그라운드에서 음원 재생

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

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

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

 

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

- 어떤 속성의 값을 변경해 주어야만 인터랙션이 가능하다.

- 안드로이드는 clickable이고 아이폰은 userInteractionEnabled 속성이다.

 

** 작업을 분류

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

   안드로이드에서는 IntentService를 이용해서 수행해야 한다.

   여러 개의 작업을 할 때는 서로 간의 연관성이 없어야 한다.

   Intent Service를 여러 개 만들면 수행 종료 시간을 알 수 없다.

- CPU를 적게 사용하는 작업 : 입출력

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

** 속도를 가지고 작업 분류

- 주기억 장치의 데이터를 이용하는 작업
- 보조기억 장치(파일을 읽고 쓰기)나 외부와 연결(키보드, 모니터, 네트워크)에 의해 이루어지는 작업

 

** Android의 4대 컴포넌트

- Activity : 화면

- Service : 백그라운드 작업

- Broadcast : 통신 - Activity와 Service 간의 데이터 전송에도 사용이 된다.

- ContentProvider : 데이터 공유

- 안드로이드에서는 4대 컴포넌트는 생성만으로는 사용할 수 없고 AndroidManifest 파일에 등록해야만 사용할 수 있다.

   직접 클래스를 생성한 경우에는 등록하는 코드를 작성해 주어야 한다.

   Activity는 없는 Activity를 호출하면 예외가 발생하지만 Broadcast나 Service, ContentProvider는 없는 것을 호출하면 아무 일도 하지 않는다.

   호출하는 부분에서는 예외가 발생하지 않고 작업을 하는 도중에 예외가 발생할 수는 있다.

   디버깅이 어렵기 때문에 Broadcast나 Service, ContentProvider를 사용할 때는 시작하는 메소드에 로그를 출력해놓는 것이 좋다.

   로그가 출력이 안되면 설정을 잘못하거나 잘못된 이름으로 호출한 것이다.

   웹 서비스를 만들 때도 클래스를 만들 때 생성자를 만들어서 로그를 출력해 두는 것이 좋다. 로그가 출력이 안되면 어노테이션을 잘못 설정하거나 패키지 이름이 잘못된 것이거나 servlet-context.xml 파일의 Component-Scan이 잘못된 것이다.