107일차(1)/Android App(71) : 모바일 갤러리 기능 구현(5)
- 이전 게시물 참조
2023.03.11 - [국비교육] - 105일차(1)/Android App(70) : 모바일 갤러리 기능 구현(4)
- 유틸리티 안에서 알아서 GET방식, POST방식 요청을 해줄 수 있도록 만드는 중!
- 쿠키가 이미 존재한다면 수정을 하고, 존재하지 않는다면 새로운 쿠키를 저장
- map을 MyHttpUtil에 전달하면 로그인처리를 실행해주는 구조로 만들었다.
- 성공이면 id라는 키값으로 로그인된 아이디를 얻어낼 수 있다.
- Spring Boot에서 원격지 서버의 이 메소드에 알아서 요청을 해주는 유틸리티를 만든 것이다.
- 현재 로그인했지만 sessionId가 저장되지 않아 계속 로그인 화면이 뜨는 상태.
- MainActivity에서 수정!
MainActivity
package com.example.step25imagecapture;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import com.example.step25imagecapture.util.MyHttpUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class MainActivity extends AppCompatActivity implements MyHttpUtil.RequestListener {
ImageView imageView;
//저장된 이미지의 전체 경로
String imagePath;
//최초 사진을 찍었는지 여부
boolean isTakePicured=false;
//업로드할 File 객체의 참조값을 저장할 필드
File photoFile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//사진을 출력할 ImageView 의 참조값 필드에 저장하기
imageView=findViewById(R.id.imageView);
Button takePicture=findViewById(R.id.takePicture);
takePicture.setOnClickListener(v->{
//사진을 찍고 싶다는 Intent 객체 작성하기
Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//운영체제에 해당 인턴트를 처리할수 있는 App 을 실행시켜 달라고 하고 결과 값도 받아올수 있도록 한다.
startActivityForResult(intent, 0);
});
Button takePicture2=findViewById(R.id.takePicture2);
takePicture2.setOnClickListener(v->{
//사진을 찍고 싶다는 Intent 객체 작성하기
Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//외부 저장 장치의 절대 경로
String absolutePath=getExternalFilesDir(null).getAbsolutePath();
//파일명 구성
String fileName= UUID.randomUUID().toString()+".jpg";
//생성할 이미지의 전체 경로
imagePath=absolutePath+"/"+fileName;
//이미지 파일을 저장할 File 객체
photoFile=new File(imagePath);
//File 객체를 Uri 로 포장을 한다.
//Uri uri= Uri.fromFile(photoFile);
Uri uri=FileProvider.getUriForFile(this,
"com.example.step25imagecapture.fileprovider",
photoFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, 1);
});
EditText inputCaption=findViewById(R.id.inputCaption);
//업로드 버튼에 대한 동작
Button uploadBtn=findViewById(R.id.uploadBtn);
uploadBtn.setOnClickListener(v->{
//입력한 caption 과 찍은 사진 파일을 서버에 업로드 한다.
String caption=inputCaption.getText().toString();
//서버에 전송할 요철 파라미터를 Map 에 담고
Map<String, String> map=new HashMap<>();
map.put("caption", caption);
//MyHttpUtil 객체를 이용해서 파일 업로드 요청을 한다.
new MyHttpUtil(this).fileUploadRequest(2,
AppConstants.BASE_URL+"/api/gallery/insert",
map, this, photoFile);
});
}
@Override
protected void onStart() {
super.onStart();
new MyHttpUtil(this).sendGetRequest(1,
AppConstants.BASE_URL+"/music/logincheck", null, this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//만일 위에서 요청한 요청과 같고 결과가 성공적이라면
if(requestCode == 0 && resultCode == RESULT_OK){
//data Intent 객체에 결과값(섬네일 이미지 데이터) 가 들어 있다.
Bitmap image=(Bitmap)data.getExtras().get("data");
//ImageView 에 출력하기
imageView.setImageBitmap(image);
}else if(requestCode == 1 && resultCode == RESULT_OK){
//만일 여기가 실행된다면 imagePath 경로에 이미지 파일이 성공적으로 만들어진 것이다
//Bitmap image=BitmapFactory.decodeFile(imagePath);
//imageView.setImageBitmap(image);
fitToImageView(imageView, imagePath);
}
}
//이미지 뷰의 크기에 맞게 이미지를 출력하는 메소드
public static void fitToImageView(ImageView imageView, String absolutePath){
//출력할 이미지 뷰의 크기를 얻어온다.
int targetW = imageView.getWidth();
int targetH = imageView.getHeight();
// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(absolutePath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
// Determine how much to scale down the image
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);
// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeFile(absolutePath, bmOptions);
/* 사진이 세로로 촬영했을때 회전하지 않도록 */
try {
ExifInterface ei = new ExifInterface(absolutePath);
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
switch(orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
bitmap = rotateImage(bitmap, 90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
bitmap = rotateImage(bitmap, 180);
break;
// etc.
}
}catch(IOException ie){
Log.e("####", ie.getMessage());
}
imageView.setImageBitmap(bitmap);
}
//Bitmap 이미지 회전시켜서 리턴하는 메소드
public static Bitmap rotateImage(Bitmap source, float angle) {
Bitmap retVal;
Matrix matrix = new Matrix();
matrix.postRotate(angle);
retVal = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
return retVal;
}
//MyHttpUtil 을 이용해서 작업한 내용이 성공이면 호출되는 메소드
@Override
public void onSuccess(int requestId, String data) {
switch (requestId){
case 1: //로그인 체크 요청인 경우
try{
JSONObject obj=new JSONObject(data);
boolean isLogin=obj.getBoolean("isLogin");
//만일 로그인 하지 않았다면
if(!isLogin){
//로그인 액티비티로 이동
Intent intent=new Intent(MainActivity.this, LoginActivity.class);
startActivity(intent);
}else if(isLogin && !isTakePicured){//만일 로그인을 했고 아직 사진을 찍은 상태가 아니라면
//사진을 찍고 싶다는 Intent 객체 작성하기
Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//외부 저장 장치의 절대 경로
String absolutePath=getExternalFilesDir(null).getAbsolutePath();
//파일명 구성
String fileName= UUID.randomUUID().toString()+".jpg";
//생성할 이미지의 전체 경로
imagePath=absolutePath+"/"+fileName;
//이미지 파일을 저장할 File 객체
photoFile=new File(imagePath);
//File 객체를 Uri 로 포장을 한다.
//Uri uri= Uri.fromFile(photoFile);
Uri uri=FileProvider.getUriForFile(MainActivity.this,
"com.example.step25imagecapture.fileprovider",
photoFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, 1);
//사진을 이미 찍었다고 표시 한다.
isTakePicured=true;
}
}catch(JSONException je){
Log.e("onSuccess()", je.getMessage());
}
break;
case 2: //사진 업로드 요청인 경우
//s 는 {"isSuccess":true} or {"isSuccess":false} 형식의 문자열이다.
try {
JSONObject obj = new JSONObject(data);
boolean isSuccess=obj.getBoolean("isSuccess");
if(isSuccess){
new AlertDialog.Builder(MainActivity.this)
.setTitle("알림")
.setMessage("업로드 했습니다.")
.setNeutralButton("확인", (dialog, which) -> {
//액티비티를 종료 시켜서 GalleryListActivity 가 다시 활성화 되도록한다.
MainActivity.this.finish();
})
.create()
.show();
}else{
Toast.makeText(MainActivity.this, "실패!", Toast.LENGTH_SHORT).show();
}
}catch (JSONException je){
Log.e("onSuccess()", data);
Log.e("onSuccess()", je.getMessage());
Toast.makeText(MainActivity.this, "응답된 문자열이 json 문자열이 아닙니다.", Toast.LENGTH_SHORT).show();
}
break;
}
}
@Override
public void onFail(int requestId, Map<String, Object> result) {
}
}
- MyHttpUtil을 사용하기 위해서는 먼저 리스너를 구현해야 한다!
- 메소드 오버라이드: 성공,실패에 따라 이 메소드를 호출하게 될 것!
- onStart() 안에서 MyHttpUtil을 사용하는 구조로 바꾸어주었다.
- 이 Util 안에 자동으로 sessionId 값이 전달되도록 되어있다.
- 즉 따로 pref, sessionId 값을 관리하지 않아도 된다.
- 로그인 후에 하게 되는 작업은 onSuccess, onFail 안에서 해주면 된다.
- 요청의 값은 분류해서 switch문에서 사용
- 이 작업을 onSuccess 안에서 한다.
- 그러면 이제 LoginCheckTask 를 삭제해도 된다.
- 그럼 이제 Util만으로도 로그인이 확인되어 바로 사진을 찍을 수 있다.
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.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;
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){
//요청 오류인 경우에 이 요청은 실패!
}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(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);
}
}
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);
}
}
}
- MyHttpUtil에서 메소드 추가
- 요청 파라미터가 있다면 받고, 결과를 응답할 리스너를 받는다.
- 추가로 파일 객체 또는 업로드할 파일의 경로가 필요하다.
- 파일 객체를 넘겨주면 메소드 내부적으로 이미지를 업로드할 수 있도록 만들 예정!
- 파일 객체가 필요하기 때문에 추가로 파일을 인자로 받아준다.
- 메소드 2개를 생성해주기
- FileUploadTask에 필드를 만들어주었다. File도 필드에서 관리!!
- fileUploadRequest 메소드에서 file객체의 참조값이 위와 같이 들어올 것이다.
- 필드 정의후 setter 메소드를 추가해준다.
- setter 메소드를 사용해서 file도 uploadTask에 넣어준다.
- 메소드로 전달받은 내용이 fileUpladTask에 전달되고 setter메소드를 사용해서 필드에 값이 들어간다.
MainActivity
- uploadTask에 있던 코드를 가져와서 붙여넣어 준다.
- 가지고 와서 메소드명에 맞추어 변경하면 된다.
- filePath는 이후에 수정할 예정!
- doInBackground 메소드 안에 있던 것을 HttpUtil로 옮겨오기
- 요청Url 대신 필드 사용
- file객체의 값은 필드에 넣어주면 된다.
- 그럼 filePath, ImagePath는 필요없으므로 지워준다.
- 만약 이미 저장된 쿠키가 있다면 로그인체크할 때 같이 보내기
- 응답받은 문자열을 onPostExecute에서 받아서 리스너에 넣어준다.
- 파일 파라미터(FILE_PARAM_NAME)의 이름을 "image"로 지정 (상수값으로 만들어주기)
- 전송되는 파일의 파라미터명을 static final 상수로 설정해준 것이다.
- 이렇게 하면 이후에 필요에 따라 값을 바꾸기가 쉬워진다.
- 이전에 변수를 사용하던 위치에 상수값을 참조해서 쓰도록 바꾼다.
- 이 값을 어떻게 하느냐에 따라서 서버에서 업로드한 파일을 추출하는 코드가 달라진다.
MainActivity
- 이 파일 객체의 참조값을 유틸리티 메소드에 전달해주면서 메소드를 호출하면 된다.
- 그 결과가 onActivityResult에 들어가면 된다.
- 그러면 위 메소드에서 유틸 사용 가능
- MyHttpUtil을 호출할 때 쓰기위해 File 객체 photoFile 을 필드로 선언
- MyHttpUtil의 메소드를 호출하면서 파일 객체를 넣어준다.
- 여기서 요청의 아이디는 2번!
- 요청아이디 1번은 로그인체크, 2번은 파일 업로드체크가 된다.
- onStart에서는 1번 로그인체크 요청, 버튼 클릭시에는 2번 업로드요청을 보냄으로써
들어오는 요청의 아이디(requestId)로 리스너에서 어떤 요청인지 구분할 수가 있다.
- onPostExecute 안에 있던 작업을 여기서 바꿔준다.
- 오류가 있어서 로그를 찍어서 에러를 체크해봄...
WebConfig
package com.sy.boot07.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.sy.boot07.interceptor.LoginInterceptor;
import com.sy.boot07.interceptor.MobileLoginInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer{
//로그인 인터셉터 주입 받기
@Autowired LoginInterceptor loginInterceptor;
@Autowired MobileLoginInterceptor mLoginInterceptor;
//인터셉터 동작하도록 등록하기
@Override
public void addInterceptors(InterceptorRegistry registry) {
//웹브라우저의 요청에 대해 개입할 인터셉터 등록
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/users/*","/gallery/*","/cafe/*","/file/*", "/music/*")
.excludePathPatterns("/users/signup_form", "/users/signup", "/users/loginform", "/users/login",
"/gallery/list", "/gallery/detail",
"/cafe/list","cafe/detail","/cafe/ajax_comment_list",
"/file/list","/file/download",
"/music/login", "/music/logincheck");
//모바일 요청에 대해 개입할 인터셉터 등록
registry.addInterceptor(mLoginInterceptor)
.addPathPatterns("/api/gallery/*", "/api/music/*", "/api/gallery/insert")
.excludePathPatterns("/api/gallery/list");
}
// resources 폴더안에 있는 자원을 spring 컨트롤러를 거치지 않고 응답되도록 설정
// webapp 안에 resources 폴더를 만들어야 한다.
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
- Spring에서 처리되지 않은 부분이 있었다.
- "/music/logincheck" 를 로그인하지 않고 볼수있는 제외 리스트에 추가!
- 이렇게 Utility를 만들어두면 http요청을 할 때 편리하게 사용할 수 있다.
- Util에서 요청이 들어오면 알아서 원하는 파일을 업로드까지 해준다.
- 이렇게 상수로 지정해두면 파라미터명은 그때그때 다르게 지정할 수 있다.
(서버측 DTO의 필드명과 동일하게 만들어준다)
- 좌: Spring Boot 서버 GalleryDto / 우: Android 의 MyHttpUtil
- GalleryDto의 필드명과 일치시켜주기!!
- 현재까지 이런 형태로 완성되었다.
'국비교육(22-23)' 카테고리의 다른 글
109일차(1)/Android App(73) : 모바일 갤러리 기능 구현(7) (0) | 2023.03.17 |
---|---|
108일차(1)/Android App(72) : 모바일 갤러리 기능 구현(6) (0) | 2023.03.16 |
105일차(1)/Android App(70) : 모바일 갤러리 기능 구현(4) (0) | 2023.03.11 |
104일차(1)/Android App(69) : 모바일 갤러리 기능 구현(3) (0) | 2023.03.09 |
103일차(1)/Android App(68) : 모바일 갤러리 기능 구현(2) (0) | 2023.03.09 |