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)activity 레이아웃을 수정
=>android api 에 익숙해지면 xml 대신에 자바 나 코틀린 코드로 UI를 작성하는 것도 고려
<?xml version="1.0" encoding="utf-8"?>
<!-- RelativeLayout 과 ConstraintLayout은 부모 나
다른 뷰와의 관계를 이용해서 배치하는 레이아웃
안드로이드나 아이폰에서 권장하는 레이아웃
뷰 들 사이의 여백 때문 -->
<?xml version="1.0" encoding="utf-8"?>
<!-- RelativeLayout 과 ConstraintLayout은 부모 나
다른 뷰와의 관계를 이용해서 배치하는 레이아웃
안드로이드나 아이폰에서 권장하는 레이아웃
뷰 들 사이의 여백 때문 -->
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"
android:background="#303999">
<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: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: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/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 메시지에 따라 프로그래스 바의 설정응ㄹ 변경하는 리시버
//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();
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라는 데이터를 같이 전송
진행율을 표시하는 스레드를 중지
=>이 원리를 이용해서 다운로드 받는 과정을 출력해 줄 수 있습니다.
음악을 재생하는 부분을 다운로드 받는 코드로 변경
duraion에 음악의 전체 재생 시간을 전송했는데 다운로드 받는 스트림의 전체 사이즈를 전송하면 됩니다.
available() 이라는 메소드를 이용하면 읽을 수 있는 전체 크기(다운로드 받는 데이터의 전체 크기)를 가져올 수 있습니다.
백분율로 표시하고자 할 때는 progress 현재값 / progress 최대값 * 100 하면 됩니다.
**Intent Service
=>CPU를 오랜 시간 동안 사용하는 작업을 서비스로 수행하고자 할 때 사용
=>새로운 스레드를 만들어서 작업을 수행하도록 해서 작업이 다른 애플리케이션의 성능에 영향을 미치는 것을 방지할 목적으로 만드는 서비스
=>비동기식으로 동작하기 때문에 여러 개의 작업을 수행할 때 수행 순서를 알 수 없습니다.
토렌트에서 영화 다운로드 것과 유사
=>IntentService 클래스로부터 상속받아서 만들고 onHandleIntent 메소드만 재정의 하면 됩니다.
=>StartService는 반드시 종료를 직접해야 하지만 이 서비스는 자신의 작업이 전부 완료되면 자동으로 종료되기 때문에 직접 종료할 필요가 없습니다,
실습 - 로그를 출력하는 작업을 IntentService로 실행
1.실행 가능한 Activity - IntentServiceActivity
2.백그라운드 작업을 위한 IntentService 클래스를 생성
public class MyIntentService extends IntentService {
public MyIntentService() {
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.Acitity 클래스의 onCreate 메소드에서 서비스를 실행하는 코드를 작성
//서비스를 시작
Intent intent = new Intent(this, MyIntentService.class);
startService(intent);
**시스템 서비스
=>안드로이드 시스템이 제공하는 서비스
=>자신만의 별도의 방법으로 서비스를 제공받아서 사용
1.생성
시스템서비스이름 변수 = (시스템서비스이름)getSystmService(서비스에 해당하는 상수);
=>LayoutInflater(xml 로 만든 레이아웃을 자바의 View로 변경해주는 서비스)
=>알림을 만들어주는 NotificationManager
=>최상위 액티비티를 확인하고자 할 때는 ActivityManager를 생성
=>앱의 설치 여부를 확인할 때는 PackageManger를 생성
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, 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"?>
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 메소드에 버튼을 클릭했을 때 수행할 동작을 작성
Button btn = (Button)findViewById(R.id.btn);
btn.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View view) {
//알람 시간 만들기
//현재 시간에서 20초 후
Calendar calendar =
Calendar.getInstance();
calendar.add(Calendar.SECOND, 20);
//알람등록
Intent intent = new Intent(
AlarmActivity.this,
AlarmReceiver.class);
PendingIntent pIntent =
PendingIntent.getBroadcast(
AlarmActivity.this,
0, intent, 0);
AlarmManager am =
(AlarmManager)getSystemService(
Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(), pIntent);
}
});
**Content Provider
=>안드로이드의 컴포넌트로 2개의 애플리케이션이 데이터를 공유하기 위한 개념
=>안드로이드 자체에서 연락처나 이미지 갤러리 등은 자체적으로 기능을 제공하고 있고 우리가 만든 애플리케이션끼리 데이터를 공유하기 위한 목적으로 사용
=>실제 앱 개발에서는 서버를 먼저 구성하고 업데이터 된 데이터를 서버에 업로드를 하고 다른 애플리케이션이 처음 접속할 때 서버로부터 데이터를 다운로드 받는 방식으로 구현하는 경우가 많습니다.
이렇게 구현하면 서버와 연결을 해야 하기 때문에 반드시 네트워크가 사용 가능해야 합니다.
이 대안으로 서버에 접속이 되었을 때 받아온 데이터를 로컬에 저장해서 네트워크가 되면 서버로부터 받아오고 그렇지 않으면 로컬의 데이터를 이용하는 방식으로 많이 구현
=>여기 push server를 이용해서 데이터 변경을 로컬 노티피케이션으로 알려주는 경우도 많습니다.
=>실제 애플리케이션 개발에서는 앱끼리 데이터를 공유하는 것 보다는 기본 앱의 데이터를 가져와서 사용하는 것이 훨씬 더 중요합니다.
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은 가져올 컬럼의 이름 배열 - sql에서는 select 절
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, String 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=“normal”/>
<permission android:name=“프로바이더의 authorities.WRITE_DATABASE” android:protectionLevel=“normal”/>
**시스템 앱의 데이터 공유
=>연락처나 이미지 갤러리의 데이터는 모든 앱에서 사용이 가능합니다.
대신에 권한 설정을 해야 합니다.
=>안드로이드 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에서 수행 (App)
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, 이미지를
<?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
package com.example.android0805;
import androidx.appcompat.app.AppCompatActivity;
import android.icu.text.UnicodeSetSpanner;
import android.os.Bundle;
import android.widget.Toast;
import com.pedro.library.AutoPermissions;
import com.pedro.library.AutoPermissionsListener;
public class BasicAppShareActivity extends AppCompatActivity implements AutoPermissionsListener {
//에러에서 onDenied, onGranted 를 implements 한다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_basic_app_share);
}
//권한을 거부할 때 호출되는 메소드 : AutoPermissionListener의 메소드
@Override
public void onDenied(int i, String[] strings) {
Toast.makeText(this, "권한 사용을 하지 않으면 기능을 사용 못함", Toast.LENGTH_LONG).show();
}
//권한을 혀용했을 때 호출되는 메소드 : AutoPermissionListener의 메소드
@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);
package com.example.android0805;
import androidx.appcompat.app.AppCompatActivity;
import android.icu.text.UnicodeSetSpanner;
import android.os.Bundle;
import android.widget.Toast;
import com.pedro.library.AutoPermissions;
import com.pedro.library.AutoPermissionsListener;
public class BasicAppShareActivity extends AppCompatActivity implements AutoPermissionsListener {
//에러에서 onDenied, onGranted 를 implements 한다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_basic_app_share);
//시작하자마자 필요한 권한을 요청
AutoPermissions.Companion.loadAllPermissions(this, 101);
}
//권한을 거부할 때 호출되는 메소드 : AutoPermissionListener의 메소드
@Override
public void onDenied(int i, String[] strings) {
Toast.makeText(this, "권한 사용을 하지 않으면 기능을 사용 못함", Toast.LENGTH_LONG).show();
}
//권한을 혀용했을 때 호출되는 메소드 : AutoPermissionListener의 메소드
@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);
}
}
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(){
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());
}
}
}
팁
**서비스를 이용하는 애플리케이션
=>백그라운드에서 음원 재생
=>사이즈가 큰 데이터를 다운로드 받는 애플리케이션
=>위치 정보를 계속해서 사용해야하는 애플리케이션
=>근거리 네트워크를 이용해야 하는 애플리케이션
**안드로이드나 아이폰에서 이미지를 출력하는 이미지 뷰나 단순한 텍스트를 출력하는 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이 잘못된 것입니다.
월요일: Oracle + MyBatis + Spring 에서 데이터 조회
화요일: MySQL + Hibernate + Spring 에서 데이터 조회
=>테이블 작성 - 샘플 데이터를 입력
=>검색조건, 검색어, 페이지번호, 데이터 개수를 파라미터로 전송해서 데이터를 가져오도록
=>비밀번호가 아니라면 모두 소문자나 대문자로만 저장