108일차(1)/Android App(72) : 모바일 갤러리 기능 구현(6)
- 유틸리티로 이미지를 찍어서 원격지 서버에 업로드한다.
- 이 유틸리티는 java 코드이므로, 안드로이드 말고도 다른 이클립스 등에서도 사용할 수 있다.
- MyHttpUtil의 이 메소드를 사용해서 디바이스 안의 특정파일을 서버에 업로드!
- 추가로 업로드할 파일 객체의 참조값을 넣어주면 된다.
- 윈도우 안의 파일이라면 이런 경로를 넣어줄 것이고, 안드로이드의 폴더 경로를 전달해주면 업로드 해준다.
- 첫 화면은 GalleryListActivity, 사진찍기 클릭시 MainActivity 활성화
- 사진을 찍으면 이 파일 객체를 사용해서 사진을 저장해준다.
- onActivityResult에서 이미지를 이미지뷰에 출력해주고,
- 업로드버튼을 누르면 캡션에 문자열이 담기고 map에 캡션을 담아서 그 map을 MyHttpUtil에 전달한다.
- GalleryListActivity 도 유틸리티를 사용하는 구조로 변경
GalleryListActivity
package com.example.step25imagecapture;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.example.step25imagecapture.databinding.ActivityGalleryListBinding;
import com.example.step25imagecapture.util.MyHttpUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GalleryListActivity extends AppCompatActivity implements View.OnClickListener,
MyHttpUtil.RequestListener {
ActivityGalleryListBinding binding;
//서버에서 받아온 갤러리 목록을 저장할 객체
List<GalleryDto> list=new ArrayList<>();
GalleryAdapter adapter;
//진행중 알림을 띄우기 위한 객체
ProgressDialog progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//바인딩 객체의 참조값을 필드에 저장
binding=ActivityGalleryListBinding.inflate(getLayoutInflater());
//바인딩 객체를 이용해서 화면 구성
setContentView(binding.getRoot());
//진행중 알림을 구성한다.
progress=new ProgressDialog(this);
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.setMessage("로딩중입니다...");
//ListView에 연결할 아답타 객체 생성
adapter=new GalleryAdapter(this, R.layout.listview_cell, list);
//ListView에 아답타 연결하기
binding.listView.setAdapter(adapter);
//버튼에 리스너 등록하기
binding.takePicBtn.setOnClickListener(this);
binding.refreshBtn.setOnClickListener(this);
//ListView 의 cell을 클릭했을 때 리스너
binding.listView.setOnItemClickListener((parent, view, position, id) -> {
//position은 클릭한 cell의 인덱스 값이다.
GalleryDto dto=list.get(position);
Intent intent=new Intent(this, DetailActivity.class);
intent.putExtra("dto",dto);
startActivity(intent);
});
}
@Override
protected void onStart() {
super.onStart();
//원격지 서버로부터 갤러리 목록을 받아오는 요청을 한다.
//new GalleryListTask().execute(AppConstants.BASE_URL+"/api/gallery/list");
new MyHttpUtil(this).sendGetRequest(1,
AppConstants.BASE_URL+"/api/gallery/list", null, this);
//ProgressDialogue를 띄운다.
progress.show();
}
//버튼을 눌렀을때 호출되는 메소드
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.takePicBtn:
//사진을 찍어서 올리는 액티비티를 실행한다.
Intent intent=new Intent(this, MainActivity.class);
startActivity(intent);
break;
case R.id.refreshBtn:
//목록을 다시 받아온다.
//new GalleryListTask().execute(AppConstants.BASE_URL+"/api/gallery/list");
new MyHttpUtil(this).sendGetRequest(1,
AppConstants.BASE_URL+"/api/gallery/list", null, this);
progress.show();
break;
}
}
@Override
public void onSuccess(int requestId, String data) {
//여기는 UI 스레드 (자유롭게 UI작업을 할수있다)
//jsonStr 은 [{},{},...] 형식의 문자열이기 때문에 JSONArray 객체를 생성한다.
list.clear();
try {
JSONArray arr=new JSONArray(data);
for(int i=0; i<arr.length(); i++){
//i번째 JSONObject 객체를 참조
JSONObject tmp=arr.getJSONObject(i);
int num=tmp.getInt("num");
String writer=tmp.getString("writer");
String caption=tmp.getString("caption");
String imagePath=tmp.getString("imagePath");
String regdate=tmp.getString("regdate");
GalleryDto dto=new GalleryDto();
dto.setNum(num);
dto.setWriter(writer);
dto.setCaption(caption);
//http://xxx/xxx/resources/upload/xxx.jpg 형식의 문자열을 구성해서 넣기
dto.setImagePath(AppConstants.BASE_URL+imagePath);
dto.setRegdate(regdate);
//ArrayList 객체에 누적시키기
list.add(dto);
}
//모델의 데이터가 바뀌었다고 아답타에 알려서 listView가 업데이트 되도록 한다.
adapter.notifyDataSetChanged();
} catch (JSONException je) {
Log.e("onPostExecute()", je.getMessage());
}
progress.dismiss();
}
@Override
public void onFail(int requestId, Map<String, Object> result) {
progress.dismiss();
}
}
- 이 2가지를 수정해줄 예정!
- 액티비티에 리스너를 구현하고 메소드 오버라이드.
- 요청 파라미터는 없으므로 null, 액티비티가 리스너 역할을 하도록 함
- 위에 있는 작업을 이렇게 바꿔줄 수 있다.
- Refresh 버튼 안에서도 이 작업을 해준다.
- 여기서는 요청이 하나밖에 없기 때문에, 요청의 번호(requestId)를 다르게 해서 결과를 구분할 필요는 없다!
- onPostExecute() 안에있는 작업을 onSuccess() 로 옮겨주기만 하면 된다.
- 변수명만 jsonStr을 data로 바꾸어 줌!
- 진행중 알림을 띄우는 Progress Dialog 는 액티비티가 가지도록 한다.
- 이 Progress는 요청을 하고 나서 띄우면 된다.
- 필드로 선언만 하고, onCreate 안에서 참조값을 넣어 구성해준다.
- 구성은 미리 해놓았다가 요청한 시점에 progress를 띄워주면 된다.
- 작업이 성공했을 때, 실패했을 때 모두 취소하는 코드를 넣어준다.
- pref, sessionId 관련은 모두 삭제 가능! (유틸리티 안에서 처리)
- 그러면 아래의 GalleryListTask를 전부 삭제 가능하다.
- 이렇게 유틸리티를 만들어두면 복잡한 로직 없이 코딩에만 집중할 수 있다는 장점이 있다.
- DetailActivity에서도 수정 가능하다. 로그인 여부를 확인해서 삭제버튼을 보이게 할지 말지
- LoginCheckTask를 수정
DetailActivity
package com.example.step25imagecapture;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide;
import com.example.step25imagecapture.databinding.ActivityDetailBinding;
import com.example.step25imagecapture.util.MyHttpUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DetailActivity extends AppCompatActivity implements MyHttpUtil.RequestListener {
ActivityDetailBinding binding;
GalleryDto dto;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
binding=ActivityDetailBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
//DetailActivity가 활성화될 때 전달받은 Intent 객체의 참조값 얻어오기
//GalleryListActivity 에서 생성한 Intent 객체이기 때문에 "dto" 라는 키값으로 GalleryDto 객체가 들어있다.
Intent intent=getIntent();
dto=(GalleryDto)intent.getSerializableExtra("dto");
//이미지 출력(Glide 활용)
Glide.with(this)
.load(dto.getImagePath())
.centerCrop()
.placeholder(R.drawable.ic_launcher_background)
.into(binding.imageView);
//세부 정보 출력
binding.writer.setText("writer:"+dto.getWriter());
binding.caption.setText(dto.getCaption());
binding.regdate.setText(dto.getRegdate());
//삭제 버튼에 리스너 등록
binding.deleteBtn.setOnClickListener(v -> {
new AlertDialog.Builder(this)
.setMessage("삭제 하시겠습니까?")
.setPositiveButton("네", (dialog, which) -> {
//삭제할 갤러리 사진의 Primary key를 이용해서 삭제 작업을 진행한다.
int num=dto.getNum();
Map<String, String> map=new HashMap<>();
//삭제할 번호를 Map에 담는다.
map.put("num", Integer.toString(num));
//삭제 요청하기
new MyHttpUtil(this).sendPostRequest(2,
AppConstants.BASE_URL+"/api/gallery/delete", map, this);
})
.setNegativeButton("아니요", null)
.create()
.show();
});
}
@Override
protected void onStart() {
super.onStart();
//로그인 체크
new MyHttpUtil(this).sendGetRequest(1,
AppConstants.BASE_URL+"/music/logincheck", null, this);
}
@Override
public void onSuccess(int requestId, String data) {
switch (requestId){
case 1: //로그인 체크의 결과
try {
//json문자열을 이용해서 JSONObject 객체를 생성한다.
JSONObject obj=new JSONObject(data);
//로그인 여부
boolean isLogin=obj.getBoolean("isLogin");
if(isLogin){
//로그인된 아이디를 읽어와서
String id=obj.getString("id");
//갤러리 writer와 비교해서 같으면 삭제 버튼을 보이게 한다.
if(id.equals(dto.getWriter())){
//삭제 버튼 보이게하기
binding.deleteBtn.setVisibility(View.VISIBLE);
}
}
}catch (JSONException je){
Log.e("onPostExecute", je.getMessage());
}
break;
case 2: //삭제 요청에 대한 결과
//data는 {"isSuccess",true} 형식의 json 문자열이다. 필요하다면 여기서 사용하면 된다.
//DetailActivity를 종료시켜서 GalleryListActivity가 다시 활성화되도록 한다.
finish();
break;
}
}
@Override
public void onFail(int requestId, Map<String, Object> result) {
//에러 메시지를 읽어와서
String errMsg=(String) result.get("errMsg");
switch (requestId){
case 1:
break;
case 2:
break;
}
}
}
- isLogin의 값이 true인지, false인지 여부에 따라 switch로 분기한다.
- 아래 메소드에서 가져와서 jsonStr대신 이 메소드로 들어오는 인자 data 값을 넣어주면 된다.
- 그러면 아래 로그인 체크 메소드를 전체 삭제가능!
- 삭제요청을 하면 삭제 확인을 위한 알림창이 뜨는데,
Positive 버튼에 리스너 함수를 걸어두었다. 예를 클릭하면 리스너 함수가 호출된다.
- 이 삭제요청도 MyHttpUtil로 수정해볼 예정!
- 이 삭제요청은 Post 요청이다. 요청id는 2번으로 하기
- 경로를 /api/gallery/delete 로 요청한다.
- 이렇게 람다식으로 쓰면 this가 리스너를 가리키지 않고 액티비티를 가리키게 된다.
- 람다식으로 작성하면 밖으로 빠져나간다! DetailActivity의 참조값을 가리키므로 DetailActivity.this 할 필요가 없다.
- 삭제할 번호를 담아준다. 문자열을 숫자로 바꾸어 담아준다.(toString 메소드 사용)
- 그러면 "num" 이라는 파라미터명으로 MyHttpUtil에 map에 담긴 숫자(문자형태)가 전달된다.
- 이미지를 삭제하는 경우를 case2 로 분기!
- 이 요청을 처리할 갤러리의 서버 측 메소드가 필요하다.
- Spring Boot 에서 GalleryDao에 메소드를 추가한다.
GalleryDao
//갤러리 사진 정보 삭제
public void delete(int num);
GalleryDaoImpl
@Override
public void delete(int num){
session.delete("gallery.delete", num);
};
- 이렇게 작성하면 parameter type: int, sql id: delete가 된다.
GalleryMapper
<delete id="delete" parameterType="int">
DELETE FROM board_gallery
WHERE num=#{num}
</delete>
- mapper에 삭제 SQL문 추가
GalleryService
//갤러리 사진 삭제 및 DB에서도 삭제하기
public void deleteGallery(HttpServletRequest request, int num);
GalleryServiceImpl
@Override
public void deleteGallery(HttpServletRequest request, int num){
//삭제할 Gallery 사진 정보를 읽어와서
GalleryDto dto=dao.getData(num);
//DB에서 삭제
dao.delete(num);
//파일 시스템에서 삭제 (upload폴더)
//webapp 폴더까지의 실제 경로
String realPath=request.getServletContext().getRealPath("/resources/upload");
//webapp 폴더까지의 실제 경로 + /resources/upload/xxx.jpg
String imagePath=realPath.separator+dto.getImagePath();
//삭제할 File 객체 생성
File file=new File(imagePath);
file.delete();
};
- DB, 파일시스템에서 모두 삭제해주는 코드 작성!
- 파일시스템에서 삭제하려면 실제 경로를 추출해낸 후 File객체를 사용해 삭제하면 된다.
GalleryController
@PostMapping("/api/gallery/delete")
@ResponseBody
public Map<String, Object> apiDelete(HttpServletRequest request, int num){
service.deleteGallery(request,num);
Map<String, Object> map=new HashMap<>()
map.put("isSuccess",true);
return map;
}
- Controller에서 POST방식으로 전송 (PostMapping)
- {"isSuccess",true} 라는 json문자열이 ResponseBody 로 응답된다.
- 2번 요청에 대해 리스너가 응답하도록 하기
- 안드로이드에서 수정
- 위에서 리턴한 map을 받아오도록 한다!
- 삭제(case 2)한 후에는 현재 켜져 있는 DetailActivity를 종료시키면 된다.
- 위와 같이 알림창이 나오고 예를 누르면 삭제된다.
- MyHttpUtil에는 아직 실패 상황에 대한 고려는 없다.
- 에러 관련 수정!
MyHttpUtil
package com.example.step25imagecapture.util;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/*
- Http 요청을 할때 서버가 응답하는 쿠키를 모두 읽어서 저장하고 싶다
- 다음번 Http 요청을 할때 저장된 쿠키를 모두 보내고 싶다
- 쿠키 값이 수정되어서 응답되면 저장되어 있는 쿠키를 수정해야 한다.
- 그러면 쿠키를 SQLiteDataBase 를 활용해서 관리하면 빠르게 처리 할수 있지 않을까?
*/
public class MyHttpUtil {
//필드
private Context context;
private DBHelper dbHelper;
//생성자
public MyHttpUtil(Context context){
this.context=context;
//DBHelper 객체의 참조값을 얻어내서 필드에 저장해 둔다.
dbHelper=new DBHelper(context, "CookieDB.sqlite", null, 1);
}
//이 유틸리티를 사용하는 곳에서 구현해야 하는 인터페이스
public interface RequestListener{
public void onSuccess(int requestId, String data);
public void onFail(int requestId, Map<String, Object> result);
}
class DBHelper extends SQLiteOpenHelper{
public DBHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
//해당 DBHelper 를 처음 사용할때 호출되는 메소드 (new DBHelper() 를 처음 호춯할때)
@Override
public void onCreate(SQLiteDatabase db) {
//테이블을 만들면 된다.
String sql="CREATE TABLE board_cookie (cookie_name TEXT PRIMARY KEY, cookie TEXT)";
db.execSQL(sql);
}
//DB 를 리셋(업그래이드)할때 호출되는 메소드
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//업그래이드할 내용을 작성하면 된다.
db.execSQL("DROP TABLE IF EXISTS board_cookie"); //만일 테이블이 존재하면 삭제한다.
//다시 만들어 질수 있도록 onCreate() 메소드를 호출한다.
onCreate(db);
}
}
/*
GET 방식 요청을 하는 메소드
*/
public void sendGetRequest(int requestId, String requestUrl, Map<String, String> params,
RequestListener listener){
//GET 방식 요청을 할 비동기 Task 객체를 생성해서
GetRequestTask task=new GetRequestTask();
//필요한 값을 넣어주고
task.setRequestId(requestId);
task.setRequestUrl(requestUrl);
task.setListener(listener);
//비동기 Task 를 실행한다.
task.execute(params);
}
/*
POST 방식 요청을 하는 메소드
*/
public void sendPostRequest(int requestId, String requestUrl, Map<String, String> params,
RequestListener listener){
//POST 방식 요청을 할 비동기 Task 객체를 생성해서
PostRequestTask task=new PostRequestTask();
//필요한 값을 넣어주고
task.setRequestId(requestId);
task.setRequestUrl(requestUrl);
task.setListener(listener);
//비동기 Task 를 실행한다.
task.execute(params);
}
/*
파일업로드 요청을 하는 메소드
*/
public void fileUploadRequest(int requestId, String requestUrl, Map<String, String> params,
RequestListener listener, File file){
FileUploadTask task=new FileUploadTask();
//필요한 값을 넣어주고
task.setRequestId(requestId);
task.setRequestUrl(requestUrl);
task.setListener(listener);
task.setFile(file);
//비동기 Task 를 실행한다.
task.execute(params);
}
private class FileUploadTask extends AsyncTask<Map<String, String>, Void, String>{
//필요한 필드 구성
private int requestId;
private String requestUrl;
private RequestListener listener;
private File file;
//전송되는 파일의 파라미터명 설정(프로젝트 상황에 맞게 변경해서 사용해야 한다)
private final String FILE_PARAM_NAME="image";
private final String boundary;
private static final String LINE_FEED = "\r\n"; //개행기호 설정
private String charset;
//생성자
public FileUploadTask(){
// 경계선은 사용할때 마다 다른 값을 사용하도록 time milli 를 조합해서 사용한다. (캐쉬방지)
boundary = "===" + System.currentTimeMillis() + "===";
charset="utf-8";
}
public void setRequestId(int requestId) {
this.requestId = requestId;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public void setListener(RequestListener listener) {
this.listener = listener;
}
public void setFile(File file) {
this.file = file;
}
@Override
protected String doInBackground(Map<String, String>... maps) {
Map<String, String> param=maps[0];
//서버가 http 요청에 대해서 응답하는 문자열을 누적할 객체
StringBuilder builder=new StringBuilder();
HttpURLConnection conn=null;
InputStreamReader isr=null;
PrintWriter pw=null;
OutputStream os=null;
FileInputStream fis=null;
BufferedReader br=null;
try{
//URL 객체 생성
URL url=new URL(requestUrl);
//HttpURLConnection 객체의 참조값 얻어오기
conn=(HttpURLConnection)url.openConnection();
if(conn!=null){//연결이 되었다면
conn.setConnectTimeout(20000); //응답을 기다리는 최대 대기 시간
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setUseCaches(false);//케쉬 사용 여부
//저장된 쿠키가 있다면 읽어내서 쿠키도 같이 보내기
SQLiteDatabase db=dbHelper.getReadableDatabase();
String sql="SELECT cookie FROM board_cookie";
//select 된 결과를 Cursor 에 담아온다.
Cursor cursor=db.rawQuery(sql, null);
while(cursor.moveToNext()){
String cookie=cursor.getString(0);
conn.addRequestProperty("Cookie", cookie);
}
//전송하는 데이터에 맞게 값 설정하기
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.setRequestProperty("User-Agent", "CodeJava Agent");
//인터냇을 통해서 서버로 출력할수 있는 스트림 객체의 참조값 얻어오기
os=conn.getOutputStream();
//출력할 스트림 객체 얻어오기
pw=new PrintWriter(new OutputStreamWriter(os, charset));
//-------------- 전송 파라미터 추가 ------------------
if(param!=null){//요청 파리미터가 존재 한다면
Set<String> keySet=param.keySet();
Iterator<String> it=keySet.iterator();
//반복문 돌면서 map 에 담긴 모든 요소를 전송할수 있도록 구성한다.
while(it.hasNext()){
String key=it.next();
pw.append("--" + boundary).append(LINE_FEED);
pw.append("Content-Disposition: form-data; name=\"" + key + "\"")
.append(LINE_FEED);
pw.append("Content-Type: text/plain; charset=" + charset).append(
LINE_FEED);
pw.append(LINE_FEED);
pw.append(param.get(key)).append(LINE_FEED);
pw.flush();
}
}
//------------- File Field ------------------
//이미 필드에 업로드할 File 객체의 참조값이 있기 때문에 필드의 값을 사용하면 된다.
String filename=file.getName(); //파일명
pw.append("--" + boundary).append(LINE_FEED);
pw.append("Content-Disposition: form-data; name=\"" + FILE_PARAM_NAME + "\"; filename=\"" + filename + "\"")
.append(LINE_FEED);
pw.append("Content-Type: " + URLConnection.guessContentTypeFromName(filename))
.append(LINE_FEED);
pw.append("Content-Transfer-Encoding: binary").append(LINE_FEED);
pw.append(LINE_FEED);
pw.flush();
//파일에서 읽어들일 스트림 객체 얻어내기
fis = new FileInputStream(file);
//byte 알갱이를 읽어들일 byte[] 객체 (한번에 4 kilo byte 씩 읽을수 있다)
byte[] buffer = new byte[4096];
//반복문 돌면서
while(true){
//byte 를 읽어들이고 몇 byte 를 읽었는지 리턴 받는다.
int readedByte=fis.read(buffer);
//더이상 읽을게 없다면 반복문 탈출
if(readedByte==-1)break;
//읽은 만큼큼 출력하기
os.write(buffer, 0, readedByte);
os.flush();
}
pw.append(LINE_FEED);
pw.flush();
pw.append(LINE_FEED).flush();
pw.append("--" + boundary + "--").append(LINE_FEED);
pw.flush();
//응답 코드를 읽어온다.
int responseCode=conn.getResponseCode();
if(responseCode==200){//정상 응답이라면...
//서버가 출력하는 문자열을 읽어오기 위한 객체
isr=new InputStreamReader(conn.getInputStream());
br=new BufferedReader(isr);
//반복문 돌면서 읽어오기
while(true){
//한줄씩 읽어들인다.
String line=br.readLine();
//더이상 읽어올 문자열이 없으면 반복문 탈출
if(line==null)break;
//읽어온 문자열 누적 시키기
builder.append(line);
}
}
//서버가 응답한 쿠키 목록을 읽어온다.
List<String> cookList=conn.getHeaderFields().get("Set-Cookie");
//만일 쿠키가 존재 한다면
if(cookList != null){
//반복문 돌면서 DB 에 저장한다.
//새로 응답된 쿠키라면 insert, 이미 존재하는 쿠키라면 update
SQLiteDatabase db2=dbHelper.getWritableDatabase();
for(String cookie:cookList){
//쿠키의 이름
String cookie_name=cookie.split("=")[0];
//쿠키의 이름을 String[] 에 담고
String[] arg={cookie_name};
//해당 쿠키가 이미 존재하는지 select 해 본다.
Cursor cursor2=db2.rawQuery("SELECT * FROM board_cookie WHERE cookie_name=?", arg);
//select 된 row 의 갯수
int selectRow=cursor2.getCount();
if(selectRow == 0){//새로운 쿠키이면 저장
Object[] args={cookie_name, cookie};
db2.execSQL("INSERT INTO board_cookie (cookie_name, cookie) VALUES(?, ?)", args);
}else{//이미 존재하는 쿠키이면 수정
Object[] args={cookie, cookie_name};
db2.execSQL("UPDATE board_cookie SET cookie=? WHERE cookie_name=?", args);
}
}
// .close() 해야지만 실제로 반영된다.
db2.close();
}
}
}catch(Exception e){//예외가 발생하면
Log.e("UploadTask", e.getMessage());
}finally {
try{
if(pw!=null)pw.close();
if(isr!=null)isr.close();
if(br!=null)br.close();
if(fis!=null) isr.close();
if(os!=null)os.close();
if(conn!=null)conn.disconnect();
}catch(Exception e){}
}
//응답 받은 json 문자열 리턴하기
return builder.toString();
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
listener.onSuccess(requestId, s);
}
}
private class GetRequestTask extends AsyncTask<Map<String, String>, Void, String>{
//필요한 필드 구성
private int requestId;
private String requestUrl;
private RequestListener listener;
//에러 메시지를 담을 필드
private String errMsg;
public void setRequestId(int requestId) {
this.requestId = requestId;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public void setListener(RequestListener listener) {
this.listener = listener;
}
@Override
protected String doInBackground(Map<String, String>... maps) {
//maps 배열의 0 번방에 GET 방식 요청 파라미터가 들어 있다.
//파라미터가 없으면 null 이 전달될 예정
Map<String, String> param = maps[0];
if(param!=null){//요청 파리미터가 존재 한다면
//서버에 전송할 데이터를 문자열로 구성하기
StringBuffer buffer=new StringBuffer();
//Map 에 존재하는 key 값을 Set 에 담아오기
Set<String> keySet=param.keySet();
Iterator<String> it=keySet.iterator();
boolean isFirst=true;
//반복문 돌면서 map 에 담긴 모든 요소를 전송할수 있도록 구성한다.
while(it.hasNext()){
String key=it.next();
String arg=null;
//파라미터가 한글일 경우 깨지지 않도록 하기 위해.
String encodedValue=null;
try {
encodedValue= URLEncoder.encode(param.get(key), "utf-8");
} catch (UnsupportedEncodingException e) {}
if(isFirst){
arg="?"+key+"="+encodedValue;
isFirst=false;
}else{
arg="&"+key+"="+encodedValue;
}
buffer.append(arg);
}
String data=buffer.toString();
//GET 방식 요청 파라미터를 요청 url 뒤에 연결한다.
requestUrl +=data;
}
//서버가 http 요청에 대해서 응답하는 문자열을 누적할 객체
StringBuilder builder=new StringBuilder();
HttpURLConnection conn=null;
InputStreamReader isr=null;
BufferedReader br=null;
try{
//URL 객체 생성
URL url=new URL(requestUrl);
//HttpURLConnection 객체의 참조값 얻어오기
conn=(HttpURLConnection)url.openConnection();
if(conn!=null){
conn.setConnectTimeout(20000); //응답을 기다리는 최대 대기 시간
conn.setRequestMethod("GET");//Default 설정
conn.setUseCaches(false);//케쉬 사용 여부
//저장된 쿠키가 있다면 읽어내서 쿠키도 같이 보내기
SQLiteDatabase db=dbHelper.getReadableDatabase();
String sql="SELECT cookie FROM board_cookie";
//select 된 결과를 Cursor 에 담아온다.
Cursor cursor=db.rawQuery(sql, null);
while(cursor.moveToNext()){
String cookie=cursor.getString(0);
conn.addRequestProperty("Cookie", cookie);
}
//응답 코드를 읽어온다. (200, 404, 500 등등의 값)
int responseCode=conn.getResponseCode();
if(responseCode==200){
//서버가 출력하는 문자열을 읽어오기 위한 객체
isr=new InputStreamReader(conn.getInputStream());
br=new BufferedReader(isr);
//반복문 돌면서 읽어오기
while(true){
//한줄씩 읽어들인다.
String line=br.readLine();
//더이상 읽어올 문자열이 없으면 반복문 탈출
if(line==null)break;
//읽어온 문자열 누적 시키기
builder.append(line);
}
}else if(responseCode==301 || responseCode==302 || responseCode==303){
//리다일렉트 요청할 경로를 얻어내서
String location=conn.getHeaderField("Location");
//해당 경로로 다시 요청을 해야 한다.
}else if(responseCode >= 400 && responseCode < 500){
//요청 오류인 경우에 이 요청은 실패!
//예외 발생시키기
throw new RuntimeException("잘못된 요청에 의해 작업이 실패했습니다.");
}else if(responseCode == 500){
//서버의 잘못된 동작으로 인한 요청 실패!
//예외 발생시키기
throw new RuntimeException("서버 오류로 인해 작업이 실패했습니다. 조속히 복구하겠습니다.");
}
}
//서버가 응답한 쿠키 목록을 읽어온다.
List<String> cookList=conn.getHeaderFields().get("Set-Cookie");
//만일 쿠키가 존재 한다면
if(cookList != null){
//반복문 돌면서 DB 에 저장한다.
//새로 응답된 쿠키라면 insert, 이미 존재하는 쿠키라면 update
SQLiteDatabase db=dbHelper.getWritableDatabase();
for(String cookie:cookList){
//쿠키의 이름
String cookie_name=cookie.split("=")[0];
//쿠키의 이름을 String[] 에 담고
String[] arg={cookie_name};
//해당 쿠키가 이미 존재하는지 select 해 본다.
Cursor cursor=db.rawQuery("SELECT * FROM board_cookie WHERE cookie_name=?", arg);
//select 된 row 의 갯수
int selectRow=cursor.getCount();
if(selectRow == 0){//새로운 쿠키이면 저장
Object[] args={cookie_name, cookie};
db.execSQL("INSERT INTO board_cookie (cookie_name, cookie) VALUES(?, ?)", args);
}else{//이미 존재하는 쿠키이면 수정
Object[] args={cookie, cookie_name};
db.execSQL("UPDATE board_cookie SET cookie=? WHERE cookie_name=?", args);
}
}
// .close() 해야지만 실제로 반영된다.
db.close();
}
}catch(Exception e){
Log.e("MyHttpUtil.sendGetRequest()", e.getMessage());
//예외가 발생한 경우 이 작업은 실패이다.
errMsg=e.getMessage(); //예외 메세지를 필드에 담고
this.cancel(true); //이 비동기작업을 취소시킨다.
}finally {
try{
if(isr!=null)isr.close();
if(br!=null)br.close();
if(conn!=null)conn.disconnect();
}catch(Exception e){}
}
//응답받은 문자열을 리턴한다.
return builder.toString();
}
//비동기 작업이 취소되면 호출되는 메소드
@Override
protected void onCancelled() {
super.onCancelled();
//예외 메세지를 Map에 담아서
Map<String, Object> map=new HashMap<>();
map.put("errMsg", errMsg);
//리스너에 전달한다.
listener.onFail(requestId, map);
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
//RequestListener 객체에(액티비티 or 서비스 or 프레그먼트 .. ) 넣어주기
listener.onSuccess(requestId, s);
}
}
private class PostRequestTask extends AsyncTask<Map<String, String>, Void, String>{
//필요한 필드 구성
private int requestId;
private String requestUrl;
private RequestListener listener;
public void setRequestId(int requestId) {
this.requestId = requestId;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public void setListener(RequestListener listener) {
this.listener = listener;
}
@Override
protected String doInBackground(Map<String, String>... maps) {
//maps 배열의 0 번방에 GET 방식 요청 파라미터가 들어 있다.
//파라미터가 없으면 null 이 전달될 예정
Map<String, String> param = maps[0];
//query 문자열을 누젓 시킬 StringBuffer
StringBuffer buffer=new StringBuffer();
if(param!=null){//요청 파리미터가 존재 한다면
//서버에 전송할 데이터를 문자열로 구성하기
//Map 에 존재하는 key 값을 Set 에 담아오기
Set<String> keySet=param.keySet();
Iterator<String> it=keySet.iterator();
boolean isFirst=true;
//반복문 돌면서 map 에 담긴 모든 요소를 전송할수 있도록 구성한다.
while(it.hasNext()){
String key=it.next();
String arg=null;
//파라미터가 한글일 경우 깨지지 않도록 하기 위해.
String encodedValue=null;
try {
encodedValue= URLEncoder.encode(param.get(key), "utf-8");
} catch (UnsupportedEncodingException e) {}
if(isFirst){
arg=key+"="+encodedValue;
isFirst=false;
}else{
arg="&"+key+"="+encodedValue;
}
//query 문자열을 StringBuffer 에 누적 시키기
buffer.append(arg);
}
}
//post 방식으로 전송할때 사용할 query문자열
String queryString=buffer.toString();
//서버가 http 요청에 대해서 응답하는 문자열을 누적할 객체
StringBuilder builder=new StringBuilder();
HttpURLConnection conn=null;
InputStreamReader isr=null;
BufferedReader br=null;
PrintWriter pw=null;
try{
//URL 객체 생성
URL url=new URL(requestUrl);
//HttpURLConnection 객체의 참조값 얻어오기
conn=(HttpURLConnection)url.openConnection();
if(conn!=null){
conn.setConnectTimeout(20000); //응답을 기다리는 최대 대기 시간
conn.setRequestMethod("POST");//POST 방식
conn.setUseCaches(false);//케쉬 사용 여부
//저장된 쿠키가 있다면 읽어내서 쿠키도 같이 보내기
SQLiteDatabase db=dbHelper.getReadableDatabase();
String sql="SELECT cookie FROM board_cookie";
//select 된 결과를 Cursor 에 담아온다.
Cursor cursor=db.rawQuery(sql, null);
while(cursor.moveToNext()){
String cookie=cursor.getString(0);
conn.addRequestProperty("Cookie", cookie);
}
//전송하는 데이터에 맞게 값 설정하기
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); //폼전송과 동일
//출력할 스트림 객체 얻어오기
OutputStreamWriter osw=
new OutputStreamWriter(conn.getOutputStream());
//문자열을 바로 출력하기 위해 osw 객체를 PrintWriter 객체로 감싼다
pw=new PrintWriter(osw);
//서버로 출력하기
pw.write(queryString);
pw.flush();
//응답 코드를 읽어온다. (200, 404, 500 등등의 값)
int responseCode=conn.getResponseCode();
if(responseCode==200){
//서버가 출력하는 문자열을 읽어오기 위한 객체
isr=new InputStreamReader(conn.getInputStream());
br=new BufferedReader(isr);
//반복문 돌면서 읽어오기
while(true){
//한줄씩 읽어들인다.
String line=br.readLine();
//더이상 읽어올 문자열이 없으면 반복문 탈출
if(line==null)break;
//읽어온 문자열 누적 시키기
builder.append(line);
}
}else if(responseCode==301 || responseCode==302 || responseCode==303){
//리다일렉트 요청할 경로를 얻어내서
String location=conn.getHeaderField("Location");
//해당 경로로 다시 요청을 해야 한다.
}else if(responseCode >= 400 && responseCode < 500){
//요청 오류인 경우에 이 요청은 실패!
}else if(responseCode == 500){
//서버의 잘못된 동작으로 인한 요청 실패!
}
}
//서버가 응답한 쿠키 목록을 읽어온다.
List<String> cookList=conn.getHeaderFields().get("Set-Cookie");
//만일 쿠키가 존재 한다면
if(cookList != null){
//반복문 돌면서 DB 에 저장한다.
//새로 응답된 쿠키라면 insert, 이미 존재하는 쿠키라면 update
SQLiteDatabase db=dbHelper.getWritableDatabase();
for(String cookie:cookList){
//쿠키의 이름
String cookie_name=cookie.split("=")[0];
//쿠키의 이름을 String[] 에 담고
String[] arg={cookie_name};
//해당 쿠키가 이미 존재하는지 select 해 본다.
Cursor cursor=db.rawQuery("SELECT * FROM board_cookie WHERE cookie_name=?", arg);
//select 된 row 의 갯수
int selectRow=cursor.getCount();
if(selectRow == 0){//새로운 쿠키이면 저장
Object[] args={cookie_name, cookie};
db.execSQL("INSERT INTO board_cookie (cookie_name, cookie) VALUES(?, ?)", args);
}else{//이미 존재하는 쿠키이면 수정
Object[] args={cookie, cookie_name};
db.execSQL("UPDATE board_cookie SET cookie=? WHERE cookie_name=?", args);
}
}
// .close() 해야지만 실제로 반영된다.
db.close();
}
}catch(Exception e){
Log.e("MyHttpUtil.sendGetRequest()", e.getMessage());
}finally {
try{
if(pw!=null)pw.close();
if(isr!=null)isr.close();
if(br!=null)br.close();
if(conn!=null)conn.disconnect();
}catch(Exception e){}
}
//응답받은 문자열을 리턴한다.
return builder.toString();
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
//RequestListener 객체에(액티비티 or 서비스 or 프레그먼트 .. ) 넣어주기
listener.onSuccess(requestId, s);
}
}
}
- GetRequestTask 에 필드 추가. 에러메세지 필드 생성!
- 실패시의 에러메시지를 담아주고 예외를 발생시킬 예정!
- 301은 리다이렉트 요청을 다시 해주면 된다.
- 위에서 발생시킨 new RuntimeException 이 이 catch문으로 들어온다.
- 예외가 발생하면 실행의 흐름이 아래의 Catch문으로 뛴다!
this.cancel(true);
- 비동기작업을 여기서 멈추도록 한다. 그러면 onPostExecute() 는 실행되지 않는다.
- 실패했을 때 호출될 메소드 onCancelled 오버라이드!
- 안에서 map객체를 생성해 errMsg를 담아준다.
- requestTask에서 예외를 다루는 방식 참고!
DetailActivity
- 이 유틸리티를 활용하는 입장에서 fail인 상황의 값이 onFail 로 들어온다.
- 이제 어떤 요청에 대해서 어떤 오류가 났는지 onFail에서 알 수 있다.
- 어떤 에러냐에 따라 switch문으로 분기해서 해당 case 블록 안에서 작업해주면 된다.
'국비교육(22-23)' 카테고리의 다른 글
109일차(2)/CSS(8) : 3차원 컨텐츠 만들기(1) (0) | 2023.03.17 |
---|---|
109일차(1)/Android App(73) : 모바일 갤러리 기능 구현(7) (0) | 2023.03.17 |
107일차(1)/Android App(71) : 모바일 갤러리 기능 구현(5) (0) | 2023.03.15 |
105일차(1)/Android App(70) : 모바일 갤러리 기능 구현(4) (0) | 2023.03.11 |
104일차(1)/Android App(69) : 모바일 갤러리 기능 구현(3) (0) | 2023.03.09 |