로그인 바로가기 하위 메뉴 바로가기 본문 바로가기
난이도
심화

안드로이드 앱 프로그래밍

임시 이미지 정재곤
http://www.boostcourse.org/mo316/notice/2536
좋아요 1436 수강생 20099
아래의 글은 BOOSTER 서포터즈로 활동했던 케니(kart***)님이
작성한 부스트코스 후기입니다.
여러분들의 성원에 더 노력하는 부스트코스가 되겠습니다.
감사합니다.
******************************************
1)링크: https://blog.naver.com/kartmon/221619474577
2)작성날짜: 19/08/17
<본문내용>

1. 스레드 이해하기

1) 스레드 사용하기

하나의 프로세스에서 여러개의 작업을 진행하는 작업을 멀티스레드라고 한다.

자바에서는 공통메모리 리소스에서 사용자가 직접 스레드를 생성해서 처리 할 수 있지만,

안드로이드에서는 공통메모리 리소스에서 처리가 불가능 하다.

(서비스에서 스레드 작업은 괜찮지만, 액티비티에서 ui에 접근하게 되면, 데드락이 발생할 가능성이 높기 때문)

그렇기 때문에 아래와 같은 구조로 핸들러에서 스레드를 처리를 하게 한다.

public class MainActivity extends AppCompatActivity { TextView textView; //int value = 0; ValueHandler handler = new ValueHandler(); Handler handler2 = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //첫번째 방법 스레드 클래스를 정의하여 사용 //BackgroundThread thread = new BackgroundThread(); //thread.start(); //runnable 을 implement해서 바로 사용 new Thread(new Runnable() { boolean running = false; int value = 0; //아래의 스래드 클래스와 동일한 내 @Override public void run() { running = true; //run 동작 내용 while(running) { value += 1; //runnable 객체를 던질 수 있다. //핸들러 내부에서 실행된 전달된 코드 //(메인스레드에서 실행되므로, ui에 접근이 가능하다.) handler2.post(new Runnable() { //ui 접근이 가능하다. @Override public void run() { textView.setText("현재 값"+value); } }); try{ Thread.sleep(1000); }catch (Exception e){ } } } }).start(); } }); Button button2 = (Button) findViewById(R.id.button2); button2.setOnClickListener(new View.OnClickListener( ) { @Override public void onClick(View view) { //textView.setText("현재 값:" + value); } }); } }

스레드 클래스 생성해서 사용하는 법

//스래드 클래스 생성classBackgroundThreadextendsThread{ boolean running =false; int value =0;publicvoidrun(){ running =true;while(running){ value+=1;//스레드 내에서는 사용 불가능 하다.//textView.setText("현재 값:" + value);//핸들러 쪽으로 send message 를 사용 Message message = handler.obtainMessage(); Bundle bundle =newBundle(); bundle.putInt("value",value); message.setData(bundle); handler.sendMessage(message);try{ Thread.sleep(1000);}catch(Exception e){}}}}//sendMessage를 전달하면 자동으로 호출된다.classValueHandlerextendsHandler{ @Override publicvoidhandleMessage(@NonNull Message msg){super.handleMessage(msg);//message를 받는다. Bundle bundle = msg.getData();//내부에 있던 value 값을 전달 받는다. int value = bundle.getInt("value");//textView에 접근 가능하다. textView.setText("현재값"+value);}}

출력 결과

생각해보기

Message 객체를 사용하는 방식보다 post 메소드를 호출하는 방식이 좋은 이유가 뭘까요?

- post 메소드를 사용하면 직관적으로 바로 확인을 할 수 있고, 코드의 복잡도를 줄일 수 있기 떄문이라고 생각합니다.

일정 시간 후에 코드가 실행되도록 만들면서 그 때 다시 또 한번 일정 시간 후에 코드가 실행되도록 만들면 어떻게 될까요?

- 스레드가 종료 될때 까지 일정 시간 후에 코드가 실행이 되고, 다시한번 일정 시간후에 코드가 실행되게 될 것 같습니다.

2) AsyncTask 사용하기

스레드에서 ui에 접근할때는 핸들러가 필요하지만, AsyncTask라는 클래스를 사용하게 되면 스레드로 동작하는 코드와 ui로 동작하는 코드를 같이 넣어서 정의를 할 수 있다.

AsyncTask는 아래와 같이 동작하게 된다.

ProgressBar progressBar; Handler handler =newHandler(); @Override protectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); progressBar =(ProgressBar)findViewById(R.id.progressBar); Button button =(Button)findViewById(R.id.button); button.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(View view){// //post를 사용 할 경우, 스레드를 여기서 지연 시킬 수 있다.// //5초 후에 실행이 된다.// handler.postDelayed(new Runnable() {// @Override// public void run() {// ProgressThread thread = new ProgressThread();// thread.start();// }// },5000);//asyncTask 호출 ProgressTask task =newProgressTask(); task.execute("시작");}});}//AsyncTask 클래스 생성classProgressTaskextendsAsyncTask<String, Integer, Integer>{ int value =0;//자동으로 호출이 된다. 스레드 안에서 동작을 하게 된다.//string 타입으로 전달이 되면 strings 로 받게 된다.//처리를 하는 코드 @Override protected Integer doInBackground(String... strings){while(true){if(value >100){break;} value +=1;//onProgressUpdate 를 호출한다.publishProgress(value);try{ Thread.sleep(200);}catch(Exception e){}}return value;}//두번째 integer를 받게 된다.//중간에 호출될떄 필요한 코드들 @Override protectedvoidonProgressUpdate(Integer... values){super.onProgressUpdate(values);//int타입으로 변경하여 progress바에 전달 progressBar.setProgress(values[0].intValue());}//세번째 integer를 받게 된다.//doInBackground에서 return 되는 값을 받게 된다.//완료가 되는 곳 @Override protectedvoidonPostExecute(Integer integer){super.onPostExecute(integer); Toast.makeText(getApplicationContext(),"완료됨",Toast.LENGTH_LONG).show();}}

출력 결과

생각해보기

AsyncTask로 만든 클래스는 별도의 자바 파일로 만들어두는 것이 좋을까요?

- 아니요, 액티비티에 바로 접근하기 위해서 inner 클래스에서 생성하는 것이 좋다고 생각합니다.

2. 소켓 사용하기

데이터를 클라이언트 서버간 주고 받는 것을 네트워크라고 한다.

네트워킹의 방식

- 클라이언트와 서버가 일대일로 연결하는 방식

- 응용 서버와 데이터 서버로 구성하는 경우, 데이터베이스를 분리시키는 방식

앱을 만들떄 사용자가 입력한 데이터를 가지고 다른 사용자에게 데이터를 전달 해주는 경우가 많아졌기 때문에 네트워킹은 필수로 사용된다.

네트워킹을 사용할 때는 반드시 스레드를 사용하고, ui업데이트를 위해서 반드시 핸들러를 사용하게 된다.

소켓이란?

서버와 클라이언트간에 데이터를 주고 받기 위해서는 포트를 점유하게 된다.

1) 소켓 사용하기

서버 어플리케이션

서버 메인 클래스

package org.mv.myserver;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.net.ServerSocket;import java.net.Socket;publicclassMainActivityextendsAppCompatActivity{ @Override protectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); Button button =(Button)findViewById(R.id.button); button.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(View view){// ServerThread thread = new ServerThread();// thread.start();//서비스 실행해준다. Intent intent =newIntent(getApplicationContext(),ChatService.class);startService(intent);}});}// //서버 스레드 선언// //서버를 스레드로 처리하면 시스템 리소스를 잡아먹기 때문에// //서버는 서비스로 구성해야한다.// class ServerThread extends Thread{// public void run(){// //서버를 실행하는 코드// int port = 5001; //5001번 포트로 요청을 받는다.//// try{// ServerSocket server = new ServerSocket(port);// Log.d("ServerThread","서버가 실행됨.");//// while(true){// //대기 상태로 들어가게 될때 클라이언트의 요청 정보를// // 소켓 객체로 받을 수 있다.// Socket socket = server.accept();//// //들어오는 데이터를 처리한다.// ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream());//// //클라이언트에서 전달받은 객체를 읽어올 수 있다.// Object input = inStream.readObject();// Log.d("ServerThread","input : " + input);//// //클라이언트로 데이터를 보낸다.// ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream());// outStream.writeObject(input + " from server.");// //데이터에 남아있는 것을 처리해준다.// outStream.flush();//// Log.d("ServerThread","output 보냄.");//// //클라이언트의 요청을 처리하면 소켓 객체를 끊어준다.// socket.close();// }//// } catch (Exception e){//// }//// }// }}

서비스 클래스

public class ChatService extends Service { //안에서 스레드를 생성하도록 만든다. public ChatService() { } //오버라이드 메소드 선언 //onCreate가 초기화 되는 시점 @Override public void onCreate() { super.onCreate(); ServerThread thread = new ServerThread(); thread.start(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); } //서버 스레드 선언 //서버를 스레드로 처리하면 시스템 리소스를 잡아먹기 때문에 //서버는 서비스로 구성해야한다. class ServerThread extends Thread{ public void run(){ //서버를 실행하는 코드 int port = 5001; //5001번 포트로 요청을 받는다. try{ ServerSocket server = new ServerSocket(port); Log.d("ServerThread","서버가 실행됨."); while(true){ //대기 상태로 들어가게 될때 클라이언트의 요청 정보를 // 소켓 객체로 받을 수 있다. Socket socket = server.accept(); //들어오는 데이터를 처리한다. ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); //클라이언트에서 전달받은 객체를 읽어올 수 있다. Object input = inStream.readObject(); Log.d("ServerThread","input : " + input); //클라이언트로 데이터를 보낸다. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject(input + " from server."); //데이터에 남아있는 것을 처리해준다. outStream.flush(); Log.d("ServerThread","output 보냄."); //클라이언트의 요청을 처리하면 소켓 객체를 끊어준다. socket.close(); } } catch (Exception e){ } } } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } }

클라이언트 어플리케이션

public class MainActivity extends AppCompatActivity { TextView textView; Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { ClientThread thread = new ClientThread(); thread.start(); } }); } //클라이언트 스레드 클래스 생성 class ClientThread extends Thread{ public void run(){ String host = "localHost"; //포트를 서버와 동일하게 설정해야한다. int port = 5001; //서버에 접속 try{ //소켓 객체 생성 Socket socket = new Socket(host,port); //서버에 보냄 ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("안녕!"); outStream.flush(); Log.d("ClientThread","서버로 보냄."); //서버에서 받음 ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); final Object input = inStream.readObject(); //상수처럼 접근하기 위해서 final 선언 //직접 접근 불가능 하기때문에 핸들러가 필요하다. //textView.setText("받은 데이터 : ",+input); handler.post(new Runnable() { @Override public void run() { textView.setText("받은 데이터 : " + input); } }); Log.d("ClientThread","받은 데이터 : " + input); }catch (Exception e){ e.printStackTrace(); } } } }

manifest.xml에서 인터넷 허가가 필요하다.

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

서버

클라이언트

출력 결과

생각해보기

소켓 서버를 이클립스로 만들 때는 어떻게 만들 수 있을까요?

- 안드로이드에서 사용했던것과 같은 형식으로 socket을 정의해서 사용하면 될 것 같습니다.

소켓을 이용해 데이터를 주고받을 때는 ObjectInputStream과 ObjectOutputStream 만을 사용해야 하나요?

-일반적으로는 두개의 스트림을 사용하는 것이 좋지만, 이외의 라이브러리를 사용하거나 사용자가 정의해서 사용해도 괜찮을 것 같습니다.

다른 언어로 만들어진 소켓 서버에 연결할 때는 어떤 IO 클래스를 사용해야 할까요?

- 다른 언어로 만들어지더라도 전달 받는 소켓의 데이터는 같은 형식으로 받아서 처리하면 될 것 같습니다.

3. 웹으로 요청하기

1) HTTP 이해하기

일반적으로 네트워킹은 소켓서버를 만들고 클라이언트와 데이터를 주고 받을때 http라는 형식을 정의해서 주고 받게 된다.

html의 요청 포맷과 응답 포맷은 헤더와 바디로 구분된다.

요청 포맷

응답 포맷

생각해보기

HTTP의 바디 부분에는 아무렇게나 데이터를 넣어도 되는 걸까요?

- http에서 사용되는 일정한 형식에 따라서 데이터를 넣어야 합니다.

HTTP의 바디 부분에 들어가는 데이터의 길이나 데이터의 유형은 어떻게 헤더에 기술할까요?

- 헤더의 content-length, content-type에 기술해야합니다.

2) 웹으로 요청하기

웹으로 사용하게 되면, 데이터가 전달되는 과정이 정해져있기 때문에 빨리 만들 수 있지만,

보안에 대해서 취약해질 수 있다.

가장 기본적으로 httpURLConnection을 사용한다.(코드양이 많아질 수 있다.)

이외에도 라이브러리를 사용하면 더 간단하게 사용할 수 있다.(volley)

mainActivity.java

public class MainActivity extends AppCompatActivity { EditText editText; TextView textView; String urlStr; //핸들러 추가 Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = (EditText) findViewById(R.id.editText); textView = (TextView) findViewById(R.id.textView); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { urlStr = editText.getText().toString(); RequestThread thread = new RequestThread(); thread.start(); } }); } //요청한 URL 처리 class RequestThread extends Thread{ public void run(){ try{ URL url = new URL(urlStr); //httpUrlconnection 객체 생성 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); if(conn != null){ //10초동안 기다리고 응답이 없으면 끝낸다. conn.setConnectTimeout(10000); //get 방식으로 요청 conn.setRequestMethod("GET"); //입력과 출력이 모두 가능하도록 한다. conn.setDoInput(true); //서버에서 받음 conn.setDoOutput(true);//서버로 보냄 //연결을 하고 코드를 resCode에 할당한다. int resCode = conn.getResponseCode(); //바이트 배열이 아니라 문자열을 처리할 수 있기 때문에 한줄씩 읽을 수 있다. BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while(true){ line = reader.readLine(); //라인을 모두 읽으면 정지 if(line == null){ break; } println(line); } //다 읽어오면 종료 reader.close(); conn.disconnect(); } }catch (Exception e){ e.printStackTrace(); } } } //텍스트뷰에 글자 추가하는 메소드 //핸들러를 통해서 접근 public void println(final String data){ handler.post(new Runnable() { @Override public void run() { textView.append(data+"\n"); } }); } }

출력 결과

안드로이드 최신버전으로 강의를 따라하다보면

No Network Security Config specified, using platform default

와 같은 오류가 생기게 된다. 해결방법은 아래 참고

http://voopro.blogspot.com/2018/11/android.html

[Android] 네트워크 보안구성 문제

안드로이드Pi (9.0) 버젼에서 http통신을 이용한 app을 만들어보려 하는 와중에 오류가 발생하기 시작했다. error : No Network Security Config specified, using platform default ( 플 ...

voopro.blogspot.com

생각해보기

응답 데이터가 웹페이지인 경우에는 화면에 어떻게 표시할 수 있을까요?

- 웹뷰라는 위젯을 사용하여 화면에 표시를 하면 될 것 같습니다.

응답 데이터가 JSON이라면 화면에 어떻게 표시할 수 있을까요?

- json을 가져올수 있는 라이브러리나 클래스를 가져와서 데이터를 처리해, 사용하면 될 것 같습니다.

4. Volley 사용하기

1) Volley 사용하기

httpUrlConnection을 사용하여 http요청을 하게 되면 많은 양의 코드를 처리하게되고, 스레드 또한 사용해야하기 때문에 이를 단순화 시키기 위한 volley 라는 라이브러리를 제공하고 있다.

volley를 사용하게 되면,

코드의 양이 적고, 스레드를 신경 쓰지 않아도 되는 장점이 있다.

request 객체를 이용하여 큐로 전달하면 알아서 스레드 처리해준다.

volley를 사용하기 위해서 외부 라이브러리를 gradle에 추가

dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation 'com.android.volley:volley:1.1.0' }

AppHelper.java

public class AppHelper { public static RequestQueue requestQueue; }

mainActivity.java

publicclassMainActivityextendsAppCompatActivity{ TextView textView; @Override protectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); textView =(TextView)findViewById(R.id.textView); Button button =(Button)findViewById(R.id.button); button.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(View view){//request 메소드 호출sendRequest();}});//메인이 만들어질때 동시에 requestQueue객체 초기화if(AppHelper.requestQueue ==null){ AppHelper.requestQueue = Volley.newRequestQueue(getApplicationContext());}}//request객체에 요청을 보내는 메소드publicvoidsendRequest(){ String url ="http://www.google.com"; StringRequest request =newStringRequest(//get방식으로 요청한다. Request.Method.GET,//전달 할 url url,//정상 응답일 경우newResponse.Listener<String>(){ @Override publicvoidonResponse(String response){println("응답 -> "+ response);}},//잘못된 응답일 경우newResponse.ErrorListener(){ @Override publicvoidonErrorResponse(VolleyError error){println("에러 -> "+ error.getMessage());}}){//request 안에서 재정의를 할 수 있다.//요청 파라미터를 수정하기 위한 메소드 @Override protected Map<String, String>getParams() throws AuthFailureError { Map<String,String> params =newHashMap<String, String>();return params;}};//매번 받은 결과를 그대로 보여준다. request.setShouldCache(false);//requestQueue에 request를 추가한다. AppHelper.requestQueue.add(request);println("요청 보냄.");}//텍스트 뷰를 추가하는 메소드publicvoidprintln(String data){ textView.append(data +"\n");}}

출력 결과

생각해보기

Volley를 사용하면 HttpURLConnection을 사용할 때보다 코드 양이 얼마나 줄어드나요?

- 스레드 처리와 입출력 등의 처리를 안해도 되기 떄문에 상당히 많이 줄어들 것 같습니다.

Volley를 이용해 웹서버에서 이미지를 받아볼 수도 있을까요?

- 이미지 url을 받아서 imageRequest를 통해서 처리가 가능할 것 같습니다.

 


*********************************************