Android-네트워킹
스레드 & 핸들러
- mainactivity에서 스레드를 따로 하나 생성하고 거기에서 값을 조작시키고 메인스레드에서 UI를 통제하는건 표준자바와 같기 때문에 당연히 되는 원리
- 근데 만약에 UI를 제어하는걸 따로 만든 스레드클레스에다가 넣고 하려고하면 메인스레드와의 충돌이 일어나서 앱이 죽는다.
- 이럴 때 필요한게 핸들러이다.
- 핸들러클래스 생성하고 따로 만든 스레드에선 값을 조작하고 그 조작한 값을 Message 객체에다가 Bundle형태로 넣어서 값을 저장시키고 핸들러클래스에서 다시 그 메세지를 받아와서 값을 UI제어를 통해 보여준다. 이렇게 하면 메인스레드가 핸들러에서 하는 UI제어이기 때문에 인정!하고 충돌이 일어나지않는다.
-
그런데 이런 Handler로 접근하는건 굉장히 복잡하다. 숫자만 바뀌게하는 UI제어 뿐만아니라 프로그레스바를 늘리고 하는 상황이 발생할 수도 있다. 그래서 좀 더 단순한 형태를 사용한다.
- 단순한 형태
- 일단 Thread클래스를 따로 만드는게 아니라 button안에서 runnable을 implement한다.
- Thread만드는 방법은 2가지인데 하나가 Thread 클래스를 따로 정의해서 사용하는거고 하나는 밑에처럼 runnable을 implement하는 방식
- 아래와 같이 한다.
Handler handler2 = new Handler(); textView = (TextView) findViewById(R.id.textView); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { int value = 0; boolean running = true; @Override public void run() { while(running) { value ++; handler2.post(new Runnable() { @Override public void run() { textView.setText("현재 값 : " + value); // 이렇게 handler를 통해 접근하는건 가능하다. } }); try { Thread.sleep(1000); } catch (Exception e){ } } } }).start(); } });
AsyncTask
- 핸들러를 사용하는 코드가 복잡하게 느껴지는 이유는 어떤 코드는 스레드 안에서 실행되고 어떤 코드는 UI를 접근하기 위해 핸들러 안에서 실행되어야 한다는 것을 구분해야 한다는 것 때문입니다.
- 이런 혼란스러운 면을 조금이라도 줄일 수 있도록 AsyncTask에서는 하나의 클래스 안에서 스레드 안에 넣을 코드와 UI를 접근할 코드를 모두 넣어둘 수 있도록 했습니다.
AsyncTask에서 메소드 실행 순서 예시
- 스레드 안에서 실행될 코드는 doInBackground 메소드 안에 넣어두고 UI를 접근할 코드는 onPreExecute, onProgressUpdate, onPostExecute에 넣어둘 수 있습니다.
- AsyncTask도 스레드를 실행하는 것과 같기 때문에 스레드 안에서 실행될 대부분의 코드는 doInBackground 안에 들어가 있게 되며 중간중간 화면에 표시하기 위한 코드 실행을 위해 onProgressUpdate 메소드가 호출됩니다.
- onProgressUpdate 메소드는 doInBackground 메소드 안에서 publishProgress 메소드가 호출 될 때 마다 자동으로 호출됩니다.
package com.example.progress;
import android.os.AsyncTask;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
ProgressBar progressBar;
Handler handler = new Handler();
@Override
protected void onCreate(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(new View.OnClickListener() {
@Override
public void onClick(View v) {
ProgressTask task = new ProgressTask();
task.execute("시작");
// 시작이라는 문자열은 아무 의미없다. 다만 첫번째 매개변수로 들어갈 뿐이다.
}
});
}
class ProgressTask extends AsyncTask<String, Integer, Integer> {
//이걸 객체생성해서 실행하면 자동적으로 doInBackground메소드가 실행이 된다.
//생성자같은 느낌인듯하다.
int value = 0;
@Override
protected Integer doInBackground(String... strings)
while (true) {
if (value>100) {
break;
}
value++;
publishProgress(value);
// 이걸 하면 자동으로 onProgressUpdate가 자동으로 호출된다.
// 그리고 이 값이 onProgressUpdate의 매개변수로 들어간다.
try {
Thread.sleep(200);
} catch (Exception e){
}
}
return value;
}
@Override
protected void onProgressUpdate(Integer... values) {
//UI업데이트
super.onProgressUpdate(values);
progressBar.setProgress(values[0].intValue());
//위에 있는 publishProgress에서 받아온 값
}
@Override
protected void onPostExecute(Integer integer) {
// 마지막에 과정이 다 끝나고 나면 이 메소드가 호출된다.
super.onPostExecute(integer);
Toast.makeText(getApplicationContext(), "완료됨", Toast.LENGTH_LONG).show();
}
}
소켓으로 클라이언트 - 서버 네트워킹 구축하기
- 서버측
- ChatService.java
package com.example.server; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; public class ChatService extends Service { public ChatService() { } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } @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 { // 이 스레드 안에서 서버를 실행을 하는 메소드를 만들 것이다. @Override public void run() { int port = 5001; try { ServerSocket server = new ServerSocket(port); // 이렇게 하면 서버가 실행이 되는데 5001번이라는 포트에서 대기하고 있는다. Log.d("ServerThread", "서버가 실행됨"); while(true) { Socket socket = server.accept(); // accept는 대기를 하고 있다가 클라이언트가 접속을 하게되면 socket이라는 객체가 리턴이 된다. // 이 socket객체를 통해서 클라이언트가 요청한 정보를 받을 수 있다. ObjectInputStream instream = new ObjectInputStream(socket.getInputStream()); // socket에 들어있는 클라이언트의 요청정보를 objectinputstream에 저장해서 참조변수 instream에다가 저장한다. // 그럼 이제 instream을 이용해서 클라이언트쪽에서 보내온 정보를 읽어낼 수 있다. Object input = instream.readObject(); Log.d("ServerThread", "input : "+ input); // 밑의 코드는 이제 클라이언트 쪽으로 데이터를 보낼 때 사용하는 코드이다. ObjectOutputStream outstream = new ObjectOutputStream(socket.getOutputStream()); outstream.writeObject(input+"from server"); //outputstream은 항상 버퍼에 남아있을 수 있으니까 꼭 flush를 해줘야한다. outstream.flush(); Log.d("ServerThread", "output 보냄."); socket.close(); } } catch (Exception e) { } } } }
- MainActivity.java
package com.example.server;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
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.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//ServerThread thread = new ServerThread();
//thread.start();
// 직접 스레드를 메인엑티비티에 구현하는건 좋지않은 방법이라 service클래스를 만들어서 관리한다.
Intent intent = new Intent(getApplicationContext(), ChatService.class);
startService(intent);
}
});
}
}
- 클라이언트측
- MainActivity.java
package com.example.socket; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; 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 v) { ClientThread thread = new ClientThread(); thread.start(); } }); } class ClientThread extends Thread{ @Override 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(); Log.d("ClientThread", "받은 데이터 : " + input); handler.post(new Runnable() { @Override public void run() { textView.setText("받은 데이터 : " + input) ; } }); } catch (Exception e) { e.printStackTrace(); } } } }
웹서버에서 http프로토콜을 사용한 데이터 송수신
package com.example.http;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
Handler handler = new Handler();
EditText editText;
TextView textView;
String urlstr;
@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 v) {
urlstr = editText.getText().toString();
RequestThread thread = new RequestThread();
thread.start();
}
});
}
class RequestThread extends Thread{
@Override
public void run() {
try {
URL url = new URL(urlstr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if(conn != null) {
conn.setConnectTimeout(10000);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("GET");
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");
}
});
}
}
Volley
- Volley 라이브러리는 웹 요청과 응답을 단순화시키기 위해 만들어진 라이브러리들 중의 하나입니다.
- 꼭 이 라이브러리를 사용해야 하는 것은 아니지만 앱에서 가장 많이 사용되는 라이브러리들 중의 하나이므로 어떻게 이 라이브러리를 사용하는지 알아두는 것이 좋습니다.
- Volley를 사용하는 방법은 그리 어렵지 않습니다.
- 먼저 요청(Request) 객체를 만들고 이 요청 객체를 요청 큐(RequestQueue)라는 곳에 넣어주기만 하면 됩니다.
-
그러면 요청 큐가 알아서 웹서버에 요청하고 응답까지 받아 여러분이 사용할 수 있도록 지정된 메소드를 호출해줍니다.
- Volley 라이브러리를 사용할 때의 가장 큰 장점은 스레드를 신경 쓰지 않아도 된다는 것입니다.
- Volley가 가지는 몇 가지 장점들은 다음과 같습니다.
- 네트워크 요청(Request) 우선 순위를 자동으로 관리한다.
- 동시에 여러 네트워크 요청을 할 수 있다.
- 요청을 할 때 Cache 적용 여부를 의식하지 않아도 된다.
- 요청 큐가 내부에서 스레드를 만들고 웹서버에 요청하고 응답을 받고 나면 메인 스레드에서 결과를 처리할 수 있도록 만든 후 여러분이 설정한 리스너의 메소드를 호출해줍니다.
- 따라서 화면에 결과를 표시할 때 핸들러를 사용하지 않아도 됩니다.
package com.example.myvolley; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.android.volley.AuthFailureError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.Volley; import java.util.HashMap; import java.util.Map; public class MainActivity extends AppCompatActivity { TextView textView; @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); if(AppHelper.requestQueue == null) { AppHelper.requestQueue = Volley.newRequestQueue(getApplicationContext()); } button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequest(); } }); } public void println(String data) { textView.append(data+"\n"); } public void sendRequest() { String url = "http://www.google.co.kr"; StringRequest request = new StringRequest( Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { println("응답 : " + response.substring(0,500)); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { println("에러 : " + error.getMessage()); } }){ @Override protected Map<String, String> getParams() throws AuthFailureError { Map<String, String> params = new HashMap<String,String>(); return params; } }; request.setShouldCache(false); AppHelper.requestQueue.add(request); println("요청 보냄"); } }
- AppHelper.java
package com.example.myvolley; import com.android.volley.RequestQueue; public class AppHelper { public static RequestQueue requestQueue; }
Gson
- Gson은 JSON 문자열을 객체로 변환해 주는 라이브러리입니다.
- 즉, JSON 문자열이 자바 객체로 만들어질 수 있습니다.
- Volley를 이용해 웹서버로부터 JSON 응답을 받았다면 Gson을 이용해 자바 객체로 바꾸고 그 객체 안에 들어있는 데이터를 사용하게 됩니다.
public class MovieList {
MovieListResult boxOfficeResult;
}
public class MovieListResult {
String boxofficeType;
String showRange;
ArrayList<Movie> dailyBoxOfficeList = new ArrayList<Movie>();
}
public class Movie {
String rnum;
String rank;
String rankInten;
String rankOldAndNew;
String movieCd;
String movieNm;
String openDt;
String salesAmt;
String salesShare;
String salesInten;
String salesChange;
String salesAcc;
String audiCnt;
String audiInten;
String audiChange;
String audiAcc;
String scrnCnt;
String showCnt
}
public void processResponse(String response) {
Gson gson = new Gson();
MovieList movieList = gson.fromJson(response, MovieList.class);
if(movieList != null) {
int countMovie = movieList.boxOfficeResult.dailyBoxOfficeList.size();
println("박스오피스의 타입 : " + movieList.boxOfficeResult.boxofficeType);
println("응답받은 영화의 갯수 : " + countMovie);
}
}
이미지 다운로드
- volley라이브러리는 이미지 다운로드받는 기능이 훌륭하지않다.
- 그래서 AsyncTask를 이용한다.
댓글남기기