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

안드로이드 앱 프로그래밍

임시 이미지 정재곤
http://www.boostcourse.org/mo316/notice/2536
좋아요 1439 수강생 20255
아래의 글은 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를 통해서 처리가 가능할 것 같습니다.

 


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