** 오디오 재생
1. 재생할 데이터를 설정
2. 재생 준비
3. 재생 관련 메소드
4. 정리
** 파일 서버 만들기
** 서버와 노래를 재생하는 애플리케이션
1. 모듈을 생성
2. 레이아웃을 설정
3. 레이아웃 파일에 디자인한 내용을 자바 코드에서 찾아오기
4. Activity 클래스에 필요한 인스턴스 변수를 선언
5. Activity가 종료될 때 호출되는 메소드를 재정의
6. 이벤트 처리하는 메소드에서 코드를 전부 작성하면 코드의 길이가 너무 길어서 코드를 분할하기 위한 사용자 정의 메소드를 생성 - 접근지정자를 private으로 설정
7. MediaPlayer의 이벤트 처리를 위한 리스너 생성
8. SeekBar를 움직였을 때 호출되는 리스너
9. 2개의 핸들러를 생성
10. onCreate 메소드에서 초기화하는 작업을 수행
11. AndroidManifest.xml 파일에서 인터넷 권한을 설정
** Surface View
** 위치 정보 사용
1. 위치 정보 제공자
2. 위치 정보 제공자를 얻어내는 메소드
3. 위치 정보 제공자의 옵션 설정하는 메소드
4. 위치를 조사하는 경우
5. LocationListener
6. 위치 정보도 퍼미션이 있어야 하고 동적으로 권한 설정을 해야 한다.
7. 현재 위치를 가져와서 출력하기
** 모든 애플리케이션이 공통으로 사용하는 디렉토리 이름
** 프로그램에서 말하는 메시지는 명령어를 메시지라고 한다.
** 모든 GUI 프로그래밍에서는 기본적으로 Main Thread에서만 UI를 갱신할 수 있다.
** 안드로이드에서는 MainThread에게 작업을 부탁하는 클래스로 Handler와 AsyncTask가 있다.
** 핸들러의 속성
** 오디오 재생
- 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)
//fd 1개만 줘도 되고 fd, offset, length를 모두 대입해도 된다.
setDataSource(FileDescriptor fd[, long offset, long length])
2. 재생 준비
- 대용량 스트림인 경우 준비하는데 상당한 시간이 걸릴 수 있으므로 오픈 직후에 준비상태로 만들어 주어야 한다.
- 이 때 사용하는 메소드
void prepare() : 이 메소드를 호출하면 작업이 끝날 때까지 다음 작업으로 진행하지 않는다.
void prepareAsync() : 이 메소드의 수행이 비동기적이라서 다음 작업으로 바로 진행한다.
- 1번과 2번을 한번에 진행하는 정적 메소드인 create도 존재한다.
정적 메소드 : static 메소드
3. 재생 관련 메소드
- start, stop, pause, setLoopgin(boolean looping), isLooping
4. 정리
- release()
- reset()
** 파일 서버 만들기
- Tomcat 기준 : webapps 디렉토리에 있는 디렉토리를 외부에서 접속할 수 있도록 해준다.
Tomcat의 시작 명령은 bin 디렉토리에 있는 stratip.sh(Linux나 Unix) 또는 startup.bat(Windows) 중지 명령은 bin 디렉토리에 있는 stop.sh 또는 stop.bat
- 외부에서 데이터를 다운로드 받는 파일 서버를 만들고자 하면 webapps 디렉토리에 디렉토리를 만들고 파일을 저장한 후 Tomcat을 시작하면 된다.
외부에서는 http://IP:포트번호 또는 도메인/디렉토리이름/파일이름으로 접속할 수 있다.
자체적으로 서버를 구축하는 곳들은 웹서버, 데이터베이스 서버, 파일 서버 등을 별도로 구축한다.
- 파일을 제공하는 서버를 만들 때 파일 뿐 아니라 파일의 목록을 데이터베이스에 만들어 놓거나 아니면 일반 파일로 만들어 두어야 한다.
모바일에 제공하는 서버인 경우 마지막에 업데이트된 날짜도 같이 저장한다.
클라이언트는 서버에 어떤 데이터가 있는지 모르기 때문에 데이터베이스나 파일을 읽어서 제공되는 데이터를 확인할 수 있어야 한다.
서버의 데이터를 다운받아서 로컬에 저장하려고 하는 애플리케이션에서는 서버의 업데이트 정보를 알아야 하므로 서버가 업데이트된 날짜를 인지할 수 있어야 한다.
- 톰캣의 bin 디렉토리에 있는 startup.sh 파일을 실행
터미널을 실행시켜서 startup.sh 파일을 드래그 한 후 엔터
- 테스트
브라우저에서 http://자신의IP:8080/webapps에만든 디렉토리 이름/파일이름
http://192.168.0.200: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"
tools:context=".MainActivity"
android:orientation="vertical">
<!--안드로이드에서는 뷰를 xml 파일에 디자인 하는 것이 일반적
디자인을 할 때 크기는 필수 요소이고 자바 코드로 동적으로 변경하고자 할 때는 반드시 id를 설정-->
<TextView
android:layout_width="match_parent"
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:id="@+id/btnPlay"
android:text="재생"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/btnStop"
android:text="중지"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/btnPrev"
android:text="이전"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/btnNext"
android:text="다음"/>
</LinearLayout>
<!--ProgressBar는 진행 상황을 표시해 줄 수 이씨만 사용자의 이벤트를 받을 수 없고
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) 필요한 뷰들을 인스턴스 변수로 선언
TextView filename;
Button btnPlay, btnStop, btnNext, btnPrev;
SeekBar progress;
2) onCreate 메소드에서 찾아오기
filename = (TextView)findViewById(R.id.filename);
btnNext = (Button)findViewById(R.id.btnNext);
btnPlay = (Button)findViewById(R.id.btnPlay);
btnPrev = (Button)findViewById(R.id.btnPrev);
btnStop = (Button)findViewById(R.id.btnStop);
progress = (SeekBar)findViewById(R.id.progress);
- 안드로이드에서는 리소스를 R.java 파일에 int 상수로 저장하고 관리한다.
이 때문에 다른 Activity의 id도 보이게 된다. 그래서 다른 Activity의 id를 실수로 작성하는 경우가 있으니 주의해야 한다.
4. Activity 클래스에 필요한 인스턴스 변수를 선언
//노래 제목들을 저장할 List 변수 m의 의미는 인스턴스 변수를 의미한다.
ArrayList<String> mSongList;
//현재 재생 중인 노래의 인덱스를 저장할 변수
int mIndex;
//재생 여부를 판단할 변수
boolean isPlaying;
//음악 재생기 변수
MediaPlayer mMediaPlayer;
5. Activity가 종료될 때 호출되는 메소드를 재정의
- 멀티미디어, 네트워크 데이터베이스 등을 사용할 때는 반드시 종료되기 직전에 모든 자원의 연결을 해제해야 한다.
안드로이드나 iOS에서는 외부 데이터베이스에 직접 연결이 안되기 때문에 멀티미디어나 네트워크를 사용하는 경우만 정확하게 해제해주면 된다.
//내가 만들 클래스가 아닌 클래스의 메소드를 오버라이딩할 때는 상위 클래스의 메소드를 호출하는 것이 좋다.
//안드로이드에서는 추상메소드가 아닌 경우 상위 클래스의 메소드를 호출하지 않으면 에러
//메소드 호출 순서는 리턴이 있거나 종료하는 메소드의 경우는 마지막에 호출해야 하고 그렇지 않으면 먼저 호출해야 한다.
@Override
protected 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(message);
}
7. MediaPlayer의 이벤트 처리를 위한 리스너 생성
//MediaPlayer의 이벤트를 처리할 리스너 생성 음원 재생이 끝났을 때 호출되는 리스너
MediaPlayer.OnCompletionListener mOmCompletion = 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개의 핸들러를 생성
- 첫 번째 핸들러는 loadMeda가 호출하는 핸들러로 노래의 재생 가능 여부를 출력하기 위한 핸들러
- 두 번재 핸들러는 노래가 재생될 때 0.2초마다 노래 재생 위치를 확인해서 SeekBar를 업데이트 해주는 핸들러
//화면 갱신을 위한 핸들러
Handler mMessageHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//넘어온 결과를 찾아오기
boolean result = (Boolean)msg.obj;
String resultMsg = null;
if(result == true){
resultMsg = "재생 준비 완료";
filename.setText(mSongList.get(msg.what));
//재생할 노래의 길이로 seekbar의 길이 설정
progress.setMax(mMediaPlayer.getDuration());
}else{
resultMsg = "재생 준비 실패";
}
Toast.makeText(MainActivity.this, resultMsg, Toast.LENGTH_LONG).show();
}
};
//0.2초마다 SeekBar를 업데이트해주는 핸들러
Handler mProgressBarHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if(mMediaPlayer == null){
return;
}else if(mMediaPlayer.isPlaying()){
progress.setProgress(mMediaPlayer.getCurrentPosition());
}
//0.2초마다 다시 자기 자신을 호출
mProgressBarHandler.sendEmptyMessageDelayed(0, 200);
}
};
10. onCreate 메소드에서 초기화하는 작업을 수행
mSongList = new ArrayList<>();
//스레드를 생성해 노래 목록을 다운받기
new Thread(){
@Override
public void run() {
super.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);
//핸들러 호출
mProgressBarHandler.sendEmptyMessageDelayed(0, 1000);
//버튼의 이벤트 핸들러
btnPlay.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View view) {
if(mMediaPlayer.isPlaying() == false){
mMediaPlayer.start();
btnPlay.setText("puase");
}else{
mMediaPlayer.pause();
btnPlay.setText("play");
}
}
});
btnPrev.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View view) {
//재생 중인지 여부를 저장
boolean isPlaying = mMediaPlayer.isPlaying();
//이전으로 이동
mIdx = (mIdx == 0 ? mSongList.size() - 1 : mIdx - 1);
//플레이어 초기화
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();
//다음으로 이동
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 파일에서 인터넷 권한을 설정
** 기본 Activity가 없어서 실행이 안되는 경우가 발생할 때 대처
- 실행 가능한 Activity를 생성하고 코드를 복사해서 실행하면 된다.
클래스 이름과 layout 파일의 이름만 수정해 주면 된다.
** Surface View
- 그래픽 가속을 위한 뷰
- 이전에는 그래픽을 출력할 때 SurfaceView를 이용해서 좋은 성능을 나타낸다고 했다.
- 최근 안드로이드에서는 일반 뷰를 최적화해서 SurfaceView와 성능 차이가 거의 나지 않는다.
- 현재는 SurfacaView는 그래픽 가속 대문에 사용하는 것이 아니라 카메라로 사진이나 동영상을 촬영할 때 카메라 뷰를 만들기 위한 용도로 주로 이용한다.
카메라는 계속해서 변하는 데이터를 출력하기 때문에 약간의 성능 차이가 크게 느껴질 수 있기 때문이다.
** 위치 정보 사용
- 위치 정보를 여러 가지 방법으로 가져올 수 있다.
1. 위치 정보 제공자
- 전화 기지국 : 가입된 통신사의 기지국을 기준으로 위치정보를 제공하는 방법 - 제일 부정확하다.
- 무선 네트워크를 제공하는 라우터 : 무선 네트워크를 이용하는 경우 가장 가까이에 있는 라우터의 위치로 알려준다.
위 2가지 방법은 고도 측정은 안된다.
- GPS : 50m 정도의 오차 범위를 갖는 미국방성 저궤도 위성을 이용하는 방식
- 갈릴레오 서비스 : 유럽에서 제공하는 위치 정보 제공자로 오차범위가 1m이다.
2. 위치 정보 제공자를 얻어내는 메소드
- public List<String> getAllProviders() : 모든 위치 정보 제공자를 리스트로 리턴하는 메소드
- public String getBestProvider() : 현재 상태에서 가장 성능이 좋은 위치 정보 제공자를 리턴하는 메소드
3. 위치 정보 제공자의 옵션 설정하는 메소드
- setAltitudeRequired : 고도
- setLatitudeRequired : 위도
- setLongitudeRequired : 경도
- setBearingRequired : 나침반
위 4가지 메소드는 어떤 정보를 사용할지 boolean으로 설정한다.
- setPowerRequirement(int level) : 전력 소모량
- setAccuracy(int accuracy) : 정밀도
정밀도가 높아지면 배터리 소모량이 많아지고 정밀도를 낮추면 배터리 소모량이 감소한다.
위 2개의 메소드는 배터리 소모량이나 정밀도를 설정해주는 것이다.
- GPS, 동영상, 블루투스, 와이파이는 배터리 소모량이 심하다.
4. 위치를 조사하는 경우
- 위치 갱신 리스너를 이용해야 한다.
- 위치 정보 제공자를 가지고 request.LocationUpdates(String 위치정보제공자, long minTime, float minDistance, LocationListener[, Looper looper])
마지막 Looper는 화면 갱신을 하고자 할 때 사용 - listener 안에서 핸들러를 호출해도 된다.
requestLocationIpdates(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 파일에 권한을 설정
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
2) 동적 권한 설정을 쉽게 하기 위해 build.gradle에 AutoPermission 라이브러리의 의존성 설정
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 추가(LocatinActivity)
4) 동적인 권한 요청이 필요한 부분을 처리하기 위한 코드 작성
- Activity 클래스에 동적 권한 요청 인터페이스를 implements
implements AutoPermissionsListener
- onCreate 메소드에서 동적 권한 요청 메소드를 호출
AutoPermissions.Companion.loadAllPermissions(this, 101);
- 3개의 메소드를 오버라이딩
//Activity의 메소드로 권한 요청을 설정했을 때 호출되는 메소드
//requestCode는 권한 요청할 때 구분하기 위해 부여한 번호
//permissions는 요청한 권한의 배열
//grantResults는 요청한 권한의 허용 여부 배열
@Override
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 i, String[] strings) {
Toast.makeText(this, "권한 사용을 거부함", Toast.LENGTH_LONG).show();
}
@Override
public void onGranted(int i, String[] strings) {
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="wrap_content"
android:id="@+id/lblLocation"/>
</LinearLayout>
6) Activity 파일에 디자인 뷰를 사용하기 위한 인스턴스 변수를 선언
private TextView lblLocation;
private Button btnLocation;
7) Activity 파일의 onCreate 메소드에서 디자인한 뷰 찾아오기
lblLocation = (TextView)findViewById(R.id.lblLocation);
btnLocation = (Button)findViewById(R.id.btnLocation);
8) 위치정보가 갱신됐을 때 호출될 리스너를 생성
- LocationListener를 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(){
@Override
public void onClick(View view) {
startLocationService();
}
});
** 모든 애플리케이션이 공통으로 사용하는 디렉토리 이름
- bin : 명령어 - 실행 파일
- lib : 실행 파일들이 사용할 보조적인 역할의 파일
- conf : 환경 설정 파일
- logs : 로그 파일
- temp : 임시 파일 저장
** 프로그램에서 말하는 메시지는 명령어를 메시지라고 한다.
- 메소드를 호ㅜㄹ하는 것을 메시지를 전송한다고 표현한다.
** 모든 GUI 프로그래밍에서는 기본적으로 Main Thread에서만 UI를 갱신할 수 있다.
** 안드로이드에서는 MainThread에게 작업을 부탁하는 클래스로 Handler와 AsyncTask가 있다.
** 핸들러의 속성
- Message.what : 정수
- Message.arg1 : 정수 - LPARAM
- Message.arg2 : 정수 - RPARAM
- Message.obj : Object 타입의 데이터
'안드로이드&IOS 앱 개발자 양성' 카테고리의 다른 글
안드로이드&iOS 앱 개발자 양성(88일차) - Node.js (0) | 2020.09.10 |
---|---|
안드로이드&iOS 앱 개발자 양성(87일차) (0) | 2020.08.10 |
안드로이드&iOS 앱 개발자 양성(84~85일차) (0) | 2020.08.05 |
안드로이드&iOS 앱 개발자 양성(83일차) (0) | 2020.08.04 |
안드로이드&iOS 앱 개발자 양성(82일차) (0) | 2020.08.03 |