87일차 수업정리(Android - 오디오 재생, 맵 데이터 읽어오기)
**오디오 재생
=> MediaPlayer 클래스를 이용하면 오디오 및 비디오 재생이 가능
=> MediaPlayer는 android media 패키지에 존재 : C++ 라이브러리
=> 공식적으로 재생 가능한 포맷
- Audio : wav, mp3, midi, ogg, 3gp
- video : H263, H264, Mpeg4
=> 설치된 코덱에 따라서 지원 포맷은 늘어나기도 함
1. 재생할 데이터를 설정
=> 메소드는 setDataSource : Overloading(객체지향)
- 하나의 클래스에 매개변수의 개수나 자료형을 다르게하여 동일한 이름의 메소드가 2개이상 존재하는 경우
(동일한 작업을 수행하는 메소드가 매개변수 문제로 다른 이름을 가지면 일관성이 없으므로 동일한 이름을 권장)
- setDataSource(String path)
- setDataSource(Context context, Uri uri)
- setDataSource(FileDescriptor fd[ long offset, long length]) : fd만 주거나, offset, length를 모두 대입해도 됨
2. 재생 준비
=> 대용량 스트림인 경우 준비하는데 상당한 시간이 걸릴수 있으므로 오픈 직후 준비상태로 만들어 주어야 함
=> 메소드
- void prepare() : 이 메소드 호출시 작업이 끝날때까지 다음 작업으로 진행하지 않음
- void prepareAsync : 이 메소드의 수행이 비동기적이라 다음 작업으로 바로 진행
=> 위 메소드를 한번에 진행하는 정적 메소드 create도 존재
- 정적 메소드 : static 메소드
3. 재생 관련 메소드
=> start, stop, pause, setLooping(boolean looping), isLooping
4. 정리
=> release(), reset()
**파일서버 만들기
=> Tomcat 기준 : webapps 디렉토리에 있는 디렉토리를 외부에서 접속할 수 있도록 해줌
- Tomcat의 시작 명령은 bin 디렉토리의 startup.sh(Linux나 Unix) or startup.bar(Windows)
- 중지 명령은 bin 디렉토리의 stop.sh or stop.bat
=> 외부에서 데이터를 다운받는 파일서버를 만드는 경우 webapps 디렉토리에 디렉토리를 만들고 파일 저장 후 Tomcat 시작
- 외부에서는 http://IP:포트번호 또는 도메인/디렉토리명/파일명 으로 접속 가능
- 자체적으로 서버를 구축하는 곳은 웹서버, DB서버, 파일서버등을 별도로 구축
=> 파일을 제공하는 서버를 만들때 파일 뿐 아니라 파일의 목록을 데이터베이스에 만들어 놓거나 아니면 일반 파일로 만들어두어야 함
- 모바일에 제공하는 서버인 경우 마지막에 업데이트된 날짜도 같이 저장
- 클라이언트는 서버에 어떤 데이터가 있는지 모르므로 데이터베이스나 파일을 읽어서 제공되는 데이터를 확인 할 수 있어야 함
- 서버의 데이터를 다운받아 로컬에 저장하는 애플리케이션의 경우 서버의 업데이트 정보를 알아야 하므로 업데이트 날짜를 알아야 함
=> 톰캣의 bin 디렉토리에 있는 startup.sh파일을 실행
- 터미널을 실행시켜서 startup.sh 파일을 드래그 한 후 엔터
=> 테스트
- 브라우저에서 http://자신의IP:8080/webapps에 만든 디렉토리명/파일명
- http://192.168.0.?:8080/song/list.txt : 노래 파일 목록
=> 서버 애플리케이션을 실행하는 것은 톰캣을 시작하고 종료
- 이클립스는 배포와 실행과는 아무런 상관이 없음
**서버의 노래를 재생하는 애플리케이션
1. 모듈을 생성
2. 레이아웃을 설정
=> 텍스트뷰 1개, 버튼 4개, SeekBar 1개
- 텍스트 뷰에는 현재 재생중인 노래 제목을 출력
- 버튼은 노래 재생, 중지, 이전 노래, 다음 노래 재생
- SeekBar는 현재 재생 중인 노래의 진행 상황을 표시
<?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=".MainActivity">
<!-- 안드로이드에서는 뷰를 xml 파일에 디자인 하는 것이 일반적
디자인시 크기는 필수 요소, 자바코드로 동적으로 변경하고자 하는 경우 반드시 id를 설정 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="노래 제목"
android:id="@+id/filename"/>
<!-- 버튼들을 배치 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="재생"
android:id="@+id/btnPlay"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="중지"
android:id="@+id/btnStop"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="이전"
android:id="@+id/btnPrev"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="다음"
android:id="@+id/btnNext"/>
</LinearLayout>
<!-- PrograssBar는 진행상황을 표시해 줄수 있으나 사용자의 이벤트 입력 불가
SeekBar는 thumb을 이용하여 사용자의 이벤트를 받을 수 있음-->
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0"
android:id="@+id/progress"/>
</LinearLayout>
3. 레이아웃 파일에 디자인한 내용을 자바 코드에서 찾아오기
1) 필요한 뷰들을 인스턴스 변수로 선언
Button btnPlay, btnStop, btnPrev, btnNext;
TextView filename;
SeekBar progress;
2) onCreate 메소드에서 찾아오기
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnPlay = (Button)findViewById(R.id.btnPlay);
btnStop = (Button)findViewById(R.id.btnStop);
btnPrev = (Button)findViewById(R.id.btnPrev);
btnNext = (Button)findViewById(R.id.btnNext);
filename = (TextView)findViewById(R.id.filename);
progress = (SeekBar)findViewById(R.id.progress);
}
=> 안드로이드에서는 리소스를 R.java 파일에 int 상수로 저장하고 관리
- 다른 Activity의 id도 보이게 됨
- 다른 Activity의 id를 실수로 작성하는 경우가 있음
4. Activity 클래스에 필요한 인스턴스를 선언
//노래 제목들을 저장할 List
//m은 인스턴스 변수를 의미
ArrayList<String> mSongList;
//현재 재생중인 노래의 인덱스
int mIdx;
//재생여부를 판단할 변수
boolean isPlaying;
//음악 재생기 변수
MediaPlayer mMediaPlayer;
5. Activity가 종료될 때 호출되는 메소드를 재정의
=> 멀티미디어, 네트워크, 데이터베이스등을 사용할 경우 종료되기 직전에 모든 자원의 연결을 해제
- 안드로이드, iOS에서는 외부 데이터베이스에 직접 연결이 안되므로 멀티미디어, 네트워크를 사용하는 경우 정확하게 해제해주면 됨
//내가 만든 클래스가 아닌 클래스의 메소드를 오버라이딩 하는 경우
//상위 클래스의 메소드를 호출하는 것이 좋음
//안드로이드에서는 추상 메소드가 아닌 경우 상위 클래스의 메소드를 미호출시 에러
//메소드 호출 순서는 리턴이 있거나 종료하는 메소드의 경우
//마지막에 호출해야 하고 그렇지 않으면 먼저 호출
@Override
public void onDestroy() {
if(mMediaPlayer != null){
mMediaPlayer.release();
mMediaPlayer = null;
}
super.onDestroy();
}
6. 이벤트 처리하는 메소드에서 코드를 전부 작성하면 코드의 길이가 너무 길어서 코드를 분할하기 위해서 사용자 정의 메소드를 생성
- 접근 지정자를 private로 설정
=> 이 메소드들은 실제 구현시 처음에는 없고, 리펙토링 단계에서 생성
//인덱스를 받아서 재생 가능한 노래인지 판단하는 메소드
private void loadMedia(int idx){
//핸들러에게 전송할 메시지
Message message = new Message();
//메시지를 구분할 번호를 저장
message.what = idx;
try{
mMediaPlayer.setDataSource(this, Uri.parse(mSongList.get(idx)));
}catch (Exception e){
Log.e("노래 준비 실패", e.getMessage());
message.obj = false;
}
//노래를 바로 재생할 수 있도록 재생 준비
try{
mMediaPlayer.prepare();
message.obj = true;
}catch (Exception e){
Log.e("노래 준비 실패", e.getMessage());
message.obj = false;
}
//핸들러 호출
mMessageHandler.sendMessage();
}
7. MediaPlayer의 이벤트 처리를 위한 리스너 생성
//MediaPlayer의 이벤트를 처리할 리스너 생성
//음원 재생이 끝났을 때 호출되는 리스너
MediaPlayer.OnCompletionListener mOnComplete =
new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(
MediaPlayer mediaPlayer) {
//현재 노래 재생이 끝나면 다음 노래 재생
mIdx =
(mIdx == mSongList.size()-1
?0:mIdx+1);
mMediaPlayer.reset();
loadMedia(mIdx);
mediaPlayer.start();
}
};
//노래 재생에 실패했을 때 호출되는 리스너
MediaPlayer.OnErrorListener mOnError =
new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(
MediaPlayer mediaPlayer,
int i, int i1) {
Toast.makeText(MainActivity.this,
"재생 중 에러 발생",
Toast.LENGTH_LONG).show();
return false;
}
};
//노래 재생 준비가 완료되었을 때 호출되는 리스너
MediaPlayer.OnSeekCompleteListener mOnSeekCompleter =
new MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(MediaPlayer mediaPlayer) {
if(isPlaying){
mMediaPlayer.start();
}
}
};
8. SeekBar를 움직였을 때 호출되는 리스너
//시크바의 위치가 변경되었을 때 호출되는 리스너
SeekBar.OnSeekBarChangeListener mOnSeek =
new SeekBar.OnSeekBarChangeListener() {
//썸을 눌러서 이동하고 값이 변경된 후에 호출되는 메소드
@Override
public void onProgressChanged(
SeekBar seekBar, int i, boolean b) {
//boolean b 가 사람에 의해서 변경이 된것인지
//다른 이유로 변경되었는지 알려주는 변수
if(b){
mMediaPlayer.seekTo(i);
}
}
//썸을 처음 눌렀을 때 호출되는 메소드
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if(mMediaPlayer.isPlaying()){
isPlaying = mMediaPlayer.isPlaying();
mMediaPlayer.pause();
}
}
//썸에서 손을 뗐을 때 호출되는 메소드
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
9. 2개의 핸들러를 생성
=> 첫번째 핸들러는 loadMedia 가 호출하는 핸들러로 노래의 재생 가능 여부를 출력하기 위한 핸들러
=> 두번째 핸들러는 노래가 재생될 때 0.2초마다 노래 재생 위치를 확인해서 SeekBar를 업데이트 해주는 핸들러
//화면 갱신을 위한 핸들러
Handler mMessageHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message message){
//넘어온 결과 찾아오기
boolean result = (Boolean)message.obj;
String resultMsg = null;
if(result == true){
resultMsg = "재생 준비 완료";
filename.setText(mSongList.get(message.what));
//재생할 노래의 길이로 Seekbar의 길이 설정
progress.setMax(mMediaPlayer.getDuration());
}else{
resultMsg = "재생 준비 실패";
}
Toast.makeText(MainActivity.this, resultMsg, Toast.LENGTH_LONG).show();
}
};
//0.2초마다 시크바의 값을 업데이트하는 핸들러
Handler mProgressHandler = new Handler(
Looper.getMainLooper()){
@Override
public void handleMessage(Message message){
if(mMediaPlayer == null){
return;
}else if(mMediaPlayer.isPlaying()){
progress.setProgress(
mMediaPlayer.getCurrentPosition());
}
//0.2초 다시 자기 자신을 호출
mProgressHandler.sendEmptyMessageDelayed(
0,200);
}
};
10. onCreate 메소드에서 초기화하는 작업을 수행
mSongList = new ArrayList<>();
//스레드를 생성해서 노래 목록을 다운 받기
new Thread(){
public void run(){
try{
String addr = "http://192.168.0.200:8080/song/";
//노래 목록 파일 주소 생성
URL url = new URL(addr + "list.txt");
//연결
HttpURLConnection con = (HttpURLConnection)url.openConnection();
//문자열 받기 위한 스트림 생성
BufferedReader br = new BufferedReader(new InputStreamReader(
con.getInputStream()));
StringBuilder sb = new StringBuilder();
while(true){
String line = br.readLine();
if(line == null){
break;
}
sb.append(line + "\n");
}
String data = sb.toString();
//문자열을 콤마로 분해
String[] songList = data.split(",");
for(String song : songList){
mSongList.add(addr + song + ".mp3");
}
//음원 재생기 생성
mMediaPlayer = new MediaPlayer();
mIdx = 0;
mMediaPlayer.setOnCompletionListener(mOnComplete);
mMediaPlayer.setOnErrorListener(mOnError);
mMediaPlayer.setOnSeekCompleteListener(mOnSeekCompleter);
progress.setOnSeekBarChangeListener(mOnSeek);
//핸들러 호출
mProgressHandler.sendEmptyMessageDelayed(0, 1000);
//버튼의 이벤트 핸들러
btnPlay.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
if(mMediaPlayer.isPlaying() == false){
mMediaPlayer.start();
btnPlay.setText("pause");
}else{
mMediaPlayer.pause();
btnPlay.setText("play");
}
}
});
btnStop.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
mMediaPlayer.stop();
btnPlay.setText("play");
progress.setProgress(0);
}
});
btnPrev.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
//재생 중인지 여부 저장
boolean isPlaying = mMediaPlayer.isPlaying();
//이전으로 이동
mIdx = (mIdx == 0 ? mSongList.size()-1 : mIdx);
//플레이어 초기화
mMediaPlayer.reset();
//노래 재생 준비
loadMedia(mIdx);
//이전에 재생중이면 바로 재생
if(isPlaying){
mMediaPlayer.start();
btnPlay.setText("pause");
}
}
});
btnNext.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
//재생 중인지 여부 저장
boolean isPlaying = mMediaPlayer.isPlaying();
//다음으로 이동(btnPrev과 이부분이 다름)
mIdx = (mIdx == mSongList.size()-1 ? 0 : mIdx+1);
//플레이어 초기화
mMediaPlayer.reset();
//노래 재생 준비
loadMedia(mIdx);
//이전에 재생중이면 바로 재생
if(isPlaying){
mMediaPlayer.start();
btnPlay.setText("pause");
}
}
});
}catch (Exception e){
Log.e("다운로드 예외 발생", e.getMessage());
}
}
}.start();
11. AndroidManifest.xml 파일에서 인터넷 권한 설정
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true"
**기본 Activity가 없어서 실행이 안되는 경우 발생시
=> 실행가능한 Activity를 생성하고 코드를 복사하여 실행
- 클래스 명과 layout파일의 이름은 수정해야 함
**Surface View
=> 그래픽 가속을 위한 뷰
=> 이전에는 그래픽을 출력할 때는 SurfaceView를 이용해서 좋은 성능을 나타냄
=> 최근의 안드로이드에서는 일반 뷰를 최적화해서 SurfaceView 와 성능 차이가 거의 나지 않음
=> 현재는 SurfaceView는 그래픽 가속이 아니라 카메라로 사진이나 동영상을 촬영할 때 카메라 뷰를 만들기 위한 용도로 주로 이용
- 카메라는 계속해서 변하는 데이터를 출력하므로 약간의 성능 차이가 크게 느껴질 수 있음
**위치 정보 사용
=> 위치 정보를 여러 가지 방법으로 가져올 수 있습니다.
1. 위치 정보 제공자
=> 전화 기지국: 가입된 통신사의 기지국을 기준으로 위치정보를 제공 - 부정확
=> 무선 네트워크를 제공하는 라우터: 무선 네트워크를 이용하는 경우 가장 가까이에 있는 라우터의 위치
- 여기까지 2개는 고도 측정 안됨
=> GPS: 50m 정도의 오차 범위를 갖는 미국방성 저궤도 위성을 이용하는 방식
=> 갈릴레오 서비스: 유럽에서 제공하는 위치 정보 제공자로 오차범위가 1m 이내
2. 위치 정보 제공자
- public List<String> getAllProviders(): 모든 위치 정보 제공자를 리스트를 리턴
- public String getBestProvider(): 현재 상태에서 가장 성능이 좋은 위치 정보 제공자를 리턴
3. 위치 정보 제공자의 옵션 설정
- setAltitudeRequired(고도)
- setLatitudeRequired(위도)
- setLongitudeRequired(경도)
- setBearingRequired(나침반)
=> 어떤 정보를 사용할 지 boolean 으로 설정
- setPowerRequiement(int level): 전력 소모량
- setAccuracy(int accuracy): 정밀도
=> 정밀도가 높아지면 배터리 소모량이 많아지고 정밀도를 낮추면 배터리 소모량이 감소량
=> 배터리 소모량이나 정밀도를 설정
=> gps, 동영상. 블루투스, 와이파이는 배터리 소모량이 심합니다.
4. 위치를 조사
=> 위치 갱신 리스너를 이용해야 합니다.
=> 위치 정보 제공자를 가지고 requestLocationUpdates(String 위치정보제공자, long minTime, float minDistance,
LocationListener listener[, Looper looper]): 마지막 Looper는 화면 갱신을 하고자 할 때 사용
- listener 안에서 핸들러를 호출해도 됩니다.
- requestLocationUpdates(String 위치정보제공자, long minTime, float minDistance, PendingIntent intent)
-> 위치정보가 갱신되면 Listener를 호출하는 것이 아니고 intent를 호출
=> 더이상 필요없을 때는 removeUpdates 메소드에 Listener 나 Intent를 대입
5. LocationListener
=> 위치 정보 갱신을 처리할 수 있는 리스너 인터페이스
=> onProviderEnabled, onProviderDisabled, onStatusChanged
- onLocationChanged(Location location): 위치 정보가 갱신되었을 때 호출되는 메소드
- 위치 정보를 location에 대입되어 넘어옴
- getLatitude, getLongitude, getAltitude, getSpeed, getBearing, getAccuracy, getTimestamp
=> 주기적으로 전송되어온 위치정보를 사용할 때 타임스탬프에 유의
- 스마트 폰의 위치 정보 제공자는 변경될 수 있음
- 현재는 GPS가 최적의 정보 제공자여서 GPS가 보내온 정보가 전송
- 이전에는 WIFI가 최적의 정보 제공자여서 정보 전송
- 나중에 전송한 정보가 먼저 도착하기도 함
- 이전 정보보다 나중에 생성된 정보는 버려야 함
6. 위치 정보도 퍼미션이 있어야 하고 동적으로 권한 설정
=> FINE_LOCATION 과 COASE_LOCATION 이 필요
7.현재 위치를 가져와서 출력하기
1) AndroidManifest.xml 파일에 권한을 설정
=> ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
2) 동적인 권한 설정을 쉽게 하기 위해서 build.gradle에 AutoPermission 라이브러리의 의존성 설정
=> 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'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
3) 실행 가능한 Activity 추가(LocationActivity)
4) 동적인 권한 요청이 필요한 부분을 처리하기 위한 코드 작성
=> Activity 클래스에 동적 권한 요청 인터페이스를 implements
implements AutoPermissionsListener
=> onCreate 메소드에서 동적 권한 요청 메소드를 호출
AutoPermissions.Companion.loadAllPermissions(this, 101);
=> 3개의 메소드를 오버라이딩
//Activity의 메소드로 권한 요청을 설정했을 때 호출되는 메소드
@Override
//requestCode는 권한 요청 할 때 구분하기 위해서 부여한 번호
//permissions는 요청한 권한의 배열
//grantResults는 요청한 권한의 허용 여부 배열
public void onRequestPermissionsResult(
int requestCode,
String permissions[],
int [] grantResults){
super.onRequestPermissionsResult(
requestCode, permissions, grantResults);
//AutoPermissions의 메소드를 호출하도록 설정
AutoPermissions.Companion.parsePermissions(
this, requestCode,
permissions, this);
}
//권한 사용을 거부했을 때 호출되는 메소드
@Override
public void onDenied(int requestCode, String[] permissions){
Toast.makeText(this, "권한 사용을 거부함",
Toast.LENGTH_LONG).show();
}
//권한 사용을 허용했을 때 호출되는 메소드
@Override
public void onGranted(int requestCode, String[] permissions){
Toast.makeText(this, "권한 사용을 허용함",
Toast.LENGTH_LONG).show();
}
5) 레이아웃 수정
=> 버튼 1개와 텍스트뷰 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=".LocationActivity"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="위치정보 가져오기"
android:id="@+id/btnLocation"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lblLocation"/>
</LinearLayout>
6) Activity 파일에 디자인 뷰를 사용하기 위한 인스턴스 변수를 선언
private TextView lblLocation;
private Button btnLocation;
7) Activity 파일의 onCreate 메소드에서 디자인 한 뷰 찾아오기
//뷰 찾아오기
lblLocation = findViewById(R.id.lblLocation);
btnLocation = findViewById(R.id.btnLocation);
8) 위치정보가 갱신되었을 때 호출될 리스너를 생성
=> LocationListner를 implements 해야 함
=> 현재 위치의 위도와 경도를 받아서 텍스트 뷰에 출력
//위치정보가 갱신될 때 호출될 리스너 객체
class GPSListener implements LocationListener {
//위치정보가 변경되면 호출되는 메소드
@Override
public void onLocationChanged(
@NonNull Location location) {
//위도와 경도 가져오기
double latitude = location.getLatitude();
double longitude = location.getLongitude();
//출력
String msg =
String.format("위도:%.6f 경도:%.6f",
latitude, longitude);
lblLocation.setText(msg);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(@NonNull String provider) {
}
@Override
public void onProviderDisabled(@NonNull String provider) {
}
}
9) Activity 파일에 버튼을 눌렀을 때 호출될 메소드를 작성
=> 위치 정보 수집을 위한 메소드
//버튼을 눌렀을 때 호출될 메소드
private void startLocationService(){
//위치정보 사용 객체를 생성
LocationManager manager =
(LocationManager)getSystemService(
Context.LOCATION_SERVICE);
try{
//위치정보 제공자를 설정 : 동적 권한 설정이 되어야 함
//이 코드를 부르는 곳에서 설정
Location location =
manager.getLastKnownLocation(
LocationManager.GPS_PROVIDER);
if(location != null){
//위도와 경도 가져오기
double latitude = location.getLatitude();
double longitude = location.getLongitude();
//출력
String msg =
String.format("위도:%.6f 경도:%.6f",
latitude, longitude);
lblLocation.setText(msg);
}
//리스너를 생성
GPSListener gpsListener = new GPSListener();
//위치정보가 갱신될 때 gpsListener의 메소드를 호출하도록 설정
//첫번째 매개변수는 위치 정보 갱신을 위한 정보 제공자를 설정
//두번째 매개변수는 위치 정보를 측정할 시간 단위
//세번째 매개변수는 위치 정보를 측정할 거리 단위
//네번째 매개변수가 호출될 리스너
manager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
10000, 10, gpsListener);
}catch(Exception e){
Log.e("위치 정보 사용 실패", e.getMessage());
}
}
10) Activity 파일의 onCreate 메소드에 버튼을 눌렀을 때 처리를 위한 이벤트 핸들러 작성
btnLocation.setOnClickListener(
new Button.OnClickListener(){
public void onClick(View view){
startLocationService();
}
});
TIP!
**모든 애플리케이션이 공통으로 사용하는 디렉토리 명
- bin : 명령어 - 실행파일
- lib : 실행파일들이 사용할 보조적인 역할의 파일
- logs : 로그파일
- temp : 임시파일 저장
**프로그램에서 말하는 메시지
=> 명령어를 의미(메소드를 호출하는 것을 메시지를 전송한다고 표현)
**모든 GUI 프로그래밍에서는 기본적으로 Main.Thread에서만 UI를 갱신할 수 있음
**안드로이드에서는 MainThread에게 작업을 부탁하는 클래스로 Handler와 AsyncTask가 있음
**핸들러의 속성은 4개
=> Message.what : 정수
=> Message.arg1 : 정수 - LPARAM
=> Message.arg2 : 정수 - RPARAM
=> Message.obj : Object 타입의 데이터
**컴파일러 - 기계어로 번역 X
- 어셈블리어로 번역