티스토리 뷰
# 네트워킹?
: 인터넷에 연결되어 있는 원격지의 서버 또는 원격지의 단말과 통신해서 데이터를 주고받는 동작
💡 네트워킹을 통해 인터넷에 연결되어 있는 여러 단말을 동시에 사용할 수 있어 다양한 데이터 자원을 효율적으로 사용가능!
# 네트워크 연결 방식
✔ 가장 단순한 네트워크 연결 방식은 클라이언트와 서버가 일대일로 연결하는 '2-tier Client/Server' 방식 (ex. HTTP 프로토콜, FTP 프로토콜, POP3 프로토콜...)
✔ 서버를 더 유연하게 구성하고 싶을 때는 '3-tier' 연결방식 사용(서버를 응용서버와 데이터 서버로 구성)
✔ 단말 간 통신은 서버를 두지 않고 단말끼리 서버와 클라이언트 역할을 하는 'Peer-to-Peer' 연결 방식 사용
# 소켓
: 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점
: 프로그램이 네트워크에서 데이터를 통신할 수 있도록 연결해주는 연결부
✔ 소켓은 TCP/IP 수준의 통신 방식을 제공
✔ 소켓 연결을 통해 IP주소로 목적지 호스트를 찾아내고 포트로 통신 접속점을 찾아냄
✔ TCP와 UDP 방식으로 나누어짐(일반적으로 TCP사용)
✔ 안드로이드에서 소켓 연결 등을 시도하거나 응답을 받아 처리할 때 반드시 스레드를 사용!
# 단말끼리 통신하는 소켓을 구현해보자!
설명) 클라이언트 단말에서 메시지와 함께 요청을 보내면, 서버 단말에서 메시지를 받아 뒤에 'from Server'를 붙여 다시 클라이언트로 응답 → 여기선, 하나의 단말로 통신(localhost)
1) 클라이언트 : 네트워킹이 필요한 부분에 스레드 생성(소켓 전송) 및 start()
//네트워킹은 무조건 스레드로!
new Thread(new Runnable() {
@Override
public void run() {
send(data); //네트워킹 작업은 send()함수로 따로 구현
}
}).start();
//send() 메서드
public void send(String data) {
try {
int portNumber = 5001; //서버 포트번호
Socket sock = new Socket("localhost", portNumber); //IP주소와 포트번호로 소켓 연결
ObjectOutputStream outstream = new ObjectOutputStream(sock.getOutputStream()); //소켓으로 데이터 보내기
outstream.writeObject(data);
outstream.flush();
printClientLog("데이터 전송함.");
ObjectInputStream instream = new ObjectInputStream(sock.getInputStream()); //소켓으로 데이터 받기
printClientLog("서버로부터 받음 : " + instream.readObject());
sock.close(); //소켓 연결 끊기
} catch(Exception ex) {
ex.printStackTrace();
}
}
//printClientLog() 메서드
public void printClientLog(final String data) { //클라이언트 쪽 로그를 출력, 스레드에서 호출 할 것임으로 핸들러 사용
Log.d("MainActivity", data);
handler.post(new Runnable() {
@Override
public void run() {
textView.append(data + "\n");
}
});
}
2) 서버 : 네트워킹이 필요한 부분에 스레드 생성(서버 시작) 및 start()
//네트워킹은 무조건 스레드로!
new Thread(new Runnable() {
@Override
public void run() {
startServer();
}
}).start();
//startServer() 메서드
public void startServer() {
try {
int portNumber = 5001;
ServerSocket server = new ServerSocket(portNumber); //소켓 서버 객체 만들기
printServerLog("서버 시작함 : " + portNumber);
while(true) { //클라이언트 접속을 기다림
Socket sock = server.accept(); //클라이언트가 접속 했을 때 만들어지는 소켓 객체 참조
InetAddress clientHost = sock.getLocalAddress();
int clientPort = sock.getPort();
printServerLog("클라이언트 연결됨 : " + clientHost + " : " + clientPort);
ObjectInputStream instream = new ObjectInputStream(sock.getInputStream());
Object obj = instream.readObject();
printServerLog("데이터 받음 : " + obj);
ObjectOutputStream outstream = new ObjectOutputStream(sock.getOutputStream());
outstream.writeObject(obj + " from Server.");
outstream.flush();
printServerLog("데이터 보냄.");
sock.close();
}
} catch(Exception ex) {
ex.printStackTrace();
}
}
//printServerLog() 메서드
public void printServerLog(final String data) {
Log.d("MainActivity", data);
handler.post(new Runnable() { //스레드에서 호출 할 것임으로 핸들러 사용
@Override
public void run() {
textView2.append(data + "\n");
}
});
}
💡 ObjectInputStream과 ObjectOutputStream은 자바의 객체 정보를 편리하게 주고받을 수 있도록 만들어졌기 때문에 자바가 아닌 다른 언어로 만들어진 서버와 통신할 경우 데이터 송수신이 정상적으로 이루어지지 않을 수 있어서 잘 사용X (대신, DataInputStream과 DataOutputStream을 많이 사용)
# HTTP 프로토콜
: HTTP(Hypertext Transfer Protocol)는 인터넷상에서 데이터를 주고 받기 위한 서버/클라이언트 모델을 따르는 프로토콜
✔ 비연결성이기 때문에 웹 서버에 연결한 후에 요청을 전송하고 응답을 받은 다음 연결을 끊음
✔ 자바에서 HTTP 클라이언트를 만드는 가장 간단한 방법은 URL 객체를 만들고 openConnection 메서드를 호출하여 HttpURLConnection 객체를 만드는 것 → 단점) 요청과 응답에 필요한 코드가 많음 → 해결책) 웹 요청과 응답을 단순화해주는 Volley 라이브러리 사용!
// build.gradle
dependencies {
...
implementation 'com.android.volley:volley:1.2.0' //Volley 라이브러리
}
✔ HTTP 프로토콜을 통한 웹 응답으로 받는 데이터는 JSON(JavaScript Object Notation)포맷으로 된 데이터 → 문제) JSON 포맷 데이터를 객체로 변환해야 JAVA에서 데이터 처리 가능! → 해결책) JSON 응답을 자바 객체로 바꾸기 위해 Gson 라이브러리 사용!
// build.gradle
...
dependencies {
...
implementation 'com.google.code.gson:gson:2.8.6' //Gson 라이브러리
}
# 웹 서버 네트워킹을 구현해보자!
설명) 에딧텍스트에 영화 API 서버 주소를 입력하고 '요청하기' 버튼을 누르면, 리싸이클러뷰에 영화 리스트가 나타남.
1) build.gradle에 외부 라이브러리 추가
dependencies {
...
implementation 'com.android.volley:volley:1.2.0' //Volley 라이브러리
implementation 'com.google.code.gson:gson:2.8.6' //Gson 라이브러리
}
2) 매니페스트 파일에 인터넷 권한 추가
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.highfive.samplemovie">
<uses-permission android:name="android.permission.INTERNET" /> //추가
<application
android:usesCleartextTraffic="true" //추가
...
3) 액티비티 XML 레이아웃 구성
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:text="http://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=072b596b4a06cbb74e5f13a6bcdba521&targetDt=20120101" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="요청하기" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
4) 영화 정보 JSON 응답의 포맷에 맞추어 자바 클래스 정의
//JSON 응답
{
"boxOfficeResult":
{
"boxofficeType":"일별 박스오피스",
"showRange":"20120101~20120101",
"dailyBoxOfficeList":[
{"rnum":"1",
"rank":"1",
"rankInten":"0",
"rankOldAndNew":"OLD",
"movieCd":"20112207",
"movieNm":"미션임파서블:고스트프로토콜",
"openDt":"2011-12-15",
"salesAmt":"2776060500",
"salesShare":"36.3",
"salesInten":"-415699000",
"salesChange":"-13",
"salesAcc":"40541108500",
"audiCnt":"353274",
"audiInten":"-60106",
"audiChange":"-14.5",
"audiAcc":"5328435",
"scrnCnt":"697",
"showCnt":"3223"},
{...},
...
]
}
}
//MovieList.java
public class MovieList {
MovieListResult boxOfficeResult;
}
* boxOfficeResult는 응답 json파일에 있는 속성값이며, MovieListResult 객체 또한 응답 json파일에 있는 boxOfficeResult의 자료형과 같음
//MovieListResult.java
public class MovieListResult {
String boxofficeType;
String showRange;
ArrayList<com.highfive.samplemovie.Movie> dailyBoxOfficeList = new ArrayList<Movie>();
}
//Movie.java
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;
}
5) 어댑터 클래스 정의 → MovieAdqpter.java
public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.ViewHolder> {
ArrayList<Movie> items = new ArrayList<Movie>();
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { //뷰홀더가 만들어질 때 자동 호출
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
View itemView = inflater.inflate(R.layout.movie_item, viewGroup, false); //movie_item.xml 인플레이션
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
Movie item = items.get(position); //현재 인덱스에 맞는 Movie객체 참조
viewHolder.setItem(item); //뷰홀더에 설정
}
@Override
public int getItemCount() {
return items.size();
}
public void addItem(Movie item) {
items.add(item);
}
public void setItems(ArrayList<Movie> items) {
this.items = items;
}
public Movie getItem(int position) {
return items.get(position);
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
TextView textView2;
public ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
textView2 = itemView.findViewById(R.id.textView2);
}
public void setItem(Movie item) {
textView.setText(item.movieNm);
textView2.setText(item.audiCnt + " 명");
}
}
}
6) 각 아이템을 위한 XML 레이아웃 정의
<!-- movie_item.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
app:cardBackgroundColor="#FFFFFFFF"
app:cardCornerRadius="10dp"
app:cardElevation="5dp" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:padding="5dp"
app:srcCompat="@drawable/movie" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="제목"
android:textSize="22sp"
android:maxLines="1"/>
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:paddingRight="10dp"
android:gravity="right"
android:text="관객수"
android:textColor="#FF0000FF"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
7) 액티비티 자바 소스파일 수정
//MainActivity.java
public class MainActivity extends AppCompatActivity {
EditText editText;
TextView textView;
static RequestQueue requestQueue; //한 번만 만들어 계속 사용 가능
RecyclerView recyclerView;
MovieAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.editText);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
makeRequest();
}
});
if (requestQueue == null) {
requestQueue = Volley.newRequestQueue(getApplicationContext()); //요청 큐 생성
}
recyclerView = findViewById(R.id.recyclerView); //xml레이아웃에 정의한 리사이클러뷰 객체 참조
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); //리싸이클러뷰를 세로방향 리스트 모양으로 설정하기 위해
recyclerView.setLayoutManager(layoutManager);
adapter = new MovieAdapter();
recyclerView.setAdapter(adapter); //리싸이클러뷰에 어댑터 설정
}
public void makeRequest() {
String url = editText.getText().toString();
//요청 객체 생성
StringRequest request = new StringRequest(
Request.Method.GET, //파라미터1, 요청 방식
url, //파라미터2, 웹 사이트 주소
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
println("응답 -> " + response);
processResponse(response); //응답을 받았을 때 processResponse 메서드 호출
}
}, //파라미터3, 응답받을 리스너
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
println("에러 -> " + error.getMessage());
}
} //파라미터4, 오류 리스너
) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> params = new HashMap<String,String>();
//POST방식을 사용한다면, params객체에 파라미터 값을 넣어주면 됨
return params;
}
};
request.setShouldCache(false); //캐쉬를 사용하지 않도록 설정
requestQueue.add(request); //요청 객체를 요청 큐에 넣기
println("요청 보냄.");
}
public void println(String data) {
Log.d("MainActivity", data);
}
public void processResponse(String response) {
Gson gson = new Gson();
MovieList movieList = gson.fromJson(response, MovieList.class); //응답받은 JSON 문자열을 MovieList로 변환하기
println("영화정보의 수 : " + movieList.boxOfficeResult.dailyBoxOfficeList.size());
for (int i = 0; i < movieList.boxOfficeResult.dailyBoxOfficeList.size(); i++) {
Movie movie = movieList.boxOfficeResult.dailyBoxOfficeList.get(i);
adapter.addItem(movie);
}
adapter.notifyDataSetChanged(); //변경사항이 반영
}
}
출처
Do it! 안드로이드 앱 프로그래밍(개정8판)
'안드로이드 > 자바' 카테고리의 다른 글
[Effective Java] 1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2023.09.20 |
---|---|
[안드로이드] 스레드와 핸들러 (0) | 2021.09.12 |
[안드로이드] 스피너 (0) | 2021.09.09 |
[안드로이드] 리싸이클러뷰 (0) | 2021.09.09 |
[안드로이드] 레이아웃 커스텀 (0) | 2021.09.07 |