국비교육(22-23)

107일차(1)/Android App(71) : 모바일 갤러리 기능 구현(5)

서리/Seori 2023. 3. 15. 01:03

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의 필드명과 일치시켜주기!!

 

 

 

 

- 현재까지 이런 형태로 완성되었다.