Personal Study/안드로이드

[Android] 서버로 이미지 업로드하기 (Java)

vㅔ로 2021. 7. 11. 13:40
728x90

이미지 업로드 방식에는 다양한 방법이 있다.

하지만 나는 다른 방식들을 잘 이해하지 못해서, JSON으로 압축한 이미지 코드를 서버에 저장하는 형식을 사용하였다. 

이 방식의 단점은 JSON string이 너무 커서, 서버에 들어가지 않을 가능성이 있다는 점..이다. 매우 큰 오류라고 생각하고,다음에 사진 업로드를 다시 하게 된다면 이 방식은 사용하지 않을 것이다. 스레드에 대해 공부해야 한다는 필요성을 느꼈다. 

 

사진 한 장을 업로드 하는데에는 문제가 안 되는데, 여러 장의 사진을 업로드 할 때 JSON에 모두 담을 수 없어서 JSON에 하나씩 담고 하나씩 업로드하는 방식을 채택하였다. 

 

서버는 NestJS와 통신하였다.

 

addImageButton.setOnClickListener(new View.OnClickListener() {  /** 이미지 추가 버튼 */
            @Override
            public void onClick(View v) {
                writePictures = new ArrayList<>();
                Intent intent = new Intent(Intent.ACTION_PICK);
                intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                        "image/*");
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                startActivityForResult(intent, CODE_ALBUM_REQUEST);
            }
        });

이미지를 추가하는 버튼의 ClickListener이다. 

intent.putExtra 특성으로 여러 사진을 선택하는 기능을 활성화했다. 

setDataAndType으로 이미지를 불러온다. startActivityForResult로 앨범을 실행 후, 클릭한 사진을 불러온다. 

 

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {  /** 사진 선택하는 창 */
        super.onActivityResult(requestCode, resultCode, data);
        String bitmapString = "";
        if(requestCode == CODE_ALBUM_REQUEST && resultCode == RESULT_OK && data != null){
            ArrayList<Uri> uriList = new ArrayList<>();
            if(data.getClipData() != null){
                ClipData clipData = data.getClipData();
                if(clipData.getItemCount() > 3) {
                    Toast.makeText(QuestPostWriteActivity.this, "사진은 3개까지 선택가능합니다",
                            Toast.LENGTH_SHORT).show();
                    return;
                } else if(clipData.getItemCount() == 1){
                    Uri filePath = clipData.getItemAt(0).getUri();
                    try {
                        bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), filePath);
                        bitmapString = bitmapToString(bitmap);
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                    writePictures.add(bitmapString);
                    uriList.add(filePath);
                } else if(clipData.getItemCount() > 1 && clipData.getItemCount() <= 3){
                    for(int i = 0; i<clipData.getItemCount(); i++){
                        try {
                            bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), clipData.getItemAt(i).getUri());
                            bitmapString = bitmapToString(bitmap);
                        } catch (Exception e){
                            e.printStackTrace();
                        }
                        writePictures.add(bitmapString);
                        uriList.add(clipData.getItemAt(i).getUri());
                    }
                }
            }
            UriImageAdapter adapter = new UriImageAdapter(uriList, QuestPostWriteActivity.this);
            recyclerView.setAdapter(adapter);
        }
    }

onActivityResult는 Override하여 사용한다. 

사진은 3장까지 선택할 수 있도록 하였다. 

사진의 bitmap을 String으로 변환하기 위해 bitmapString을 정의하여 저장하였다. 

앨범에서 확인 버튼을 누르고, 사진이 선택되었을 경우 사진의 clipData를 가져온다. Uri를 알아내기 위해 가져온 clipData에 getUri() 메소드로 Uri를 뽑아낸다. 

뽑아낸 Uri로 bitmap 정보를 읽어내고, 이를 정의한 bitmapToString 메소드를 이용해 (아래에 나온다) String으로 변환하였다. bitmapString은 writePictures라는 arrayList에 저장한다. 사진이 한 장일 때, 한 장 이상일 때를 구분하여 코드를 구성하였다. 

 

아래 RecyclerView 부분은 가져온 사진들을 리사이클러뷰 형태로 보여주는 부분이다. 

 

public String bitmapToString(Bitmap bitmap){ /** Bitmap을 String 으로 변환 */
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 10, baos);
        byte[] imageBytes = baos.toByteArray();
        String imageString = Base64.getEncoder().encodeToString(imageBytes);
        return imageString;
    }

bitmap을 String으로 변환하는 메소드이다. 

bitmap의 크기가 커서, 먼저 compress로 압축을 했다. 그리고 나서 bitmap을 byte 배열로 변환하고, byte 배열을 다시 Base64로 인코딩한다. 

 

private void sendToServer(String questName, String title, String content, String questNumberSend) throws JSONException {
        SharedPreferences sharedPreferences = getSharedPreferences("token", MODE_PRIVATE);
        String token = sharedPreferences.getString("Authorization", "");
        String url = getString(R.string.url) + "/auth-posting";
        Log.i("Questurl", url);
        Log.i("QuestName", questName);
        RequestQueue queue = Volley.newRequestQueue(QuestPostWriteActivity.this);

        JSONObject questPost = new JSONObject();
        questPost.put("questName", questName);
        questPost.put("postTitle", title);
        questPost.put("postContent", content);
        questPost.put("mission", questNumberSend);
        Log.i("questPost", questPost.toString());

        // 일단 글만 서버로 보냄
        final JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, url, questPost,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            userId = response.get("id").toString(); // 글 보내고 서버에서 id 받아온다

                        } catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                //Toast.makeText(QuestPostWriteActivity.this, "내부 문제가 발생했습니다", Toast.LENGTH_SHORT).show();
            }
        }) {
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> heads = new HashMap<String, String>();
                heads.put("Authorization", "Bearer " + token);
                return heads;
            }
        };

        queue.add(jsonObjectRequest);

        Handler handler = new Handler();
        handler.postDelayed(new Runnable() { /** 1초 텀을 두고 사진을 보낸다 */
            @Override
            public void run() {
                for(int i = 0; i<writePictures.size(); i++){ // 서버로 입력받은 사진 개수만큼 보내기
                    try {
                        sendImageToServer(photoProgressInt);
                    } catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }, 1000);


    }

 

Handler 이전 코드는 글을 서버로 보내는 코드이다. 글을 보낸 후 서버에서 id를 받아온다.

그런데 글과 함께 사진을 올리지 않고, 글을 먼저 보내고 사진을 보내는 방식을 사용했는데, 글이 올라가기도 전에 사진 업로드가 시작되어서 오류가 발생했었다. 이는 1초 텀을 두고 사진을 보냄으로써 해결하였다. 

 

<sendImageToServer 메소드>

 

private void sendImageToServer(int i) throws JSONException {  // 서버로 입력받은 사진 개수만큼 보내기
        SharedPreferences sharedPreferences = getSharedPreferences("token", MODE_PRIVATE);
        String token = sharedPreferences.getString("Authorization", "");
        RequestQueue queue = Volley.newRequestQueue(this);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("postingId", userId);
        jsonObject.put("image", writePictures.get(i));
        photoProgressInt++;

        String url = getString(R.string.url) + "/auth-posting/image"; // 사진 보내는 api

        final JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, url, jsonObject,
                new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                //Toast.makeText(QuestPostWriteActivity.this, "내부 문제가 발생했습니다", Toast.LENGTH_SHORT).show();
            }
        }){
            @Override
            public Map<String, String> getHeaders() throws AuthFailureError {
                Map<String, String> heads = new HashMap<String, String>();
                heads.put("Authorization", "Bearer " + token);
                return heads;
            }
        };

        queue.add(jsonObjectRequest);
    }

서버로 입력받는 사진 개수만큼 보내는 메소드이다. 

photoProgressInt라는 전역 변수를 선언하여 사진이 몇 개 올라갔는지 저장하도록 했다. 

그 다음은 JSON 파일을 서버로 올리는 방식과 동일하다.

 

삽질을 5일 동안 했던 코드이다. 누더기 코드를 올리는 것 같아 부끄럽다. 

매우 비효율적인 코드이니 가급적 사용하지 않는 것을 추천한다...

나중에 이미지 업로드 관련 포스팅을 다시 하길 바라며

 

참고한 링크 : https://youngest-programming.tistory.com/54 / https://stickode.com/detail.html?no=1631 

728x90