728x90

Android 코드에서 실제 파일 존재 유무를 확인하는 방법으로 했더니 Main Thread UI에서 작업을 한다고 경고 메시지가 나온다.


    private class PhotoURLExists extends AsyncTask<String, Void, Boolean> {
        @Override
        protected Boolean doInBackground(String... params) {
            try {
                HttpURLConnection.setFollowRedirects(false);
                HttpURLConnection con =  (HttpURLConnection) new URL(params[0]).openConnection();
                con.setRequestMethod("HEAD");
                return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
            }
            catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    } 


위 코드로 확인을 할 수는 있으나, 제대로 처리하는 것이 아닌거 같아서 PHP에서 자료를 가져올 때 아예 체크하는 로직으로 대체하였다.


테이블 칼럼에는 파일명이 존재하는데, 실제로는 파일이 존재하지 않을 수가 있다.

이럴 경우 Android 코드에서 파일이 존재하는 줄 알고 가져오기를 하면 에러가 발생한다.

이걸 방지하는 방법은 PHP 코드에서 파일이 폴더에 존재하는지 검사하여 없으면 공백을 반환하도록 하는 것이 좋다.


<?php
if(!isset($_SESSION)) {
    session_start();
}
// 파일을 직접 실행하면 동작되지 않도록 하기 위해서
if(isset($_POST) && $_SERVER['REQUEST_METHOD'] == "POST"){
    @extract($_POST); // POST 전송으로 전달받은 값 처리
    if(!(isset($idx) && !empty($idx))) {
        echo 0;
        exit;
    }
    require_once 'phpclass/dbconnect.php';
    require_once 'phpclass/loginClass.php';
    $c = new LoginClass();
    $sql = "select idx,userNM,mobileNO,telNO,photo from Person";
    if(!empty($search)) {
        $where = "userNM LIKE '%".$search."%' or mobileNO LIKE '%".$search."%'";
    } else {
        $where = "";
    }

    if(strlen($where) > 0){
        $sql .= " where ".$where;
    }

    $R = array(); // 결과 담을 변수 생성
    $result = $c->putDbArray($sql);
    while($row = $result->fetch_assoc()) {
        if($row['photo'] == NULL) {
            $row['photo'] = "";
        } else {
            $path = "./photos/".$row['photo'];
            if(!file_exists($path)) {
                $row['photo'] = "";
            }
        }
        array_push($R, $row);
    }
    echo json_encode(array('result'=>$R)); //배열-문자열등을 json형식의 '문자열'로 변환
}
?>

<?php
class DBController {
    protected $db; // 변수를 선언한 클래스와 상속받은 클래스에서 참조할 수 있다.

    // 생성자
    function __construct() {
        $this->db = $this->connectDB();
        // construct 메소드는 객체가 생성(인스턴스화)될 때 자동으로 실행되는 특수한 메소드다.
    }

    // 소멸자(destructor)
    function __destruct() {
        mysqli_close($this->connectDB());
    }

    private function connectDB() {
        require_once 'dbinfo.php';
        // MySQLi 객체지향 DB 연결
        $conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
        return $conn; // return database handler
    }
}
?>

<?php
class LoginClass extends DBController {
    // class 자식클래스 extends 부모클래스
    // override : 부모 클래스와 자식 클래스가 같은 메소드를 정의했을 경우 자식 클래스가 우선시된다.

    // 여기에 함수를 작성하면 된다.

    public function putDbArray($sql) {
        $stmt = $this->db->prepare($sql);
        $stmt->execute();
        $result = $stmt->get_result();
        return $result;
    }

    public function SearchFiltering($str){
        // 해킹 공격을 대비하기 위한 코드
        $str = preg_replace("/[\s\t\'\;\"\=\-]+/","", $str); // 공백이나 탭 제거, 특수문자 제거
        return $str;
    }

}


MySQLi 객체지향 방식으로 Class를 작성해보니, 함수화하기가 쉽지 않다.
변수를 받아서 처리하는 경우가 쉽지 않다.
PDO 방식으로 하는 것은 변수처리까지 원활하게 할 수 있다.


블로그 이미지

Link2Me

,
728x90

Retrofit2 라이브러리를 이용하여 파일을 업로드하는 예제 예시다.

파일 업로드 외에 POST 변수 idx를 추가해서 보낼 때,

RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), idx);

가 추가하면 된다.


Retrofit2 통신에 대한 기본 이해는 https://link2me.tistory.com/1806 에 이미지 도식도를 참조하시라.



앱 build.gradle

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    implementation 'com.google.android.material:material:1.2.1'

    implementation 'com.squareup.okhttp3:okhttp:4.4.0'
    implementation 'com.squareup.retrofit2:retrofit:2.7.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
} 


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />


public interface DataCommAPI {

    @Multipart
    @POST(RetrofitURL.URL_ImageUpload)
    Call<UploadResult> uploadFile(@Part("idx") RequestBody idx,
                                  @Part MultipartBody.Part uploaded_file);
}

public class UploadResult {
    private String result;

    public String getResult() {
        return result;
    }

}

public class RetrofitURL {
    public static final String IPADDRESS = "http://www.abc.com/androidSample/upload/";

    public static final String URL_ImageUpload = "upload.php";
}

public class APIRequest {
    static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(RetrofitURL.IPADDRESS)
                    .client(createOkHttpClient())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }

    public static OkHttpClient createOkHttpClient() {
        // 네트워크 통신 로그(서버로 보내는 파라미터 및 받는 파라미터) 보기
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(interceptor);
        return builder.build();
    }
}

private void uploadImage(String sourceImageFile, String idx){
     DataCommAPI retrofitInterface = APIRequest.getClient().create(DataCommAPI.class);

     File file = new File(sourceImageFile);
     RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
     MultipartBody.Part body = MultipartBody.Part.createFormData("uploaded_file", file.getName(), requestFile);
     RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), idx);

     Call<UploadResult> call = retrofitInterface.uploadFile(descBody,body);
     call.enqueue(new Callback<UploadResult>() {
         @Override
         public void onResponse(Call<UploadResult> call, Response<UploadResult> response) {
             if (response.isSuccessful()) {
                 UploadResult responseBody = response.body();
                 if(responseBody.getResult().contains("success")){
                     Toast.makeText(getApplicationContext(), "파일 업로드 성공", Toast.LENGTH_LONG).show();
                 } else {
                     Utils.showAlert(mContext,"파일 업로드","파일 업로드 실패");
                 }

             } else {
                 ResponseBody errorBody = response.errorBody();
                 Gson gson = new Gson();
                 try {
                     Response errorResponse = gson.fromJson(errorBody.string(), Response.class);
                    
Toast.makeText(getApplicationContext(), errorResponse.message(),

                        Toast.LENGTH_LONG).show();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }

         @Override
         public void onFailure(Call<UploadResult> call, Throwable t) {
             Log.e(TAG, "onFailure: "+t.getLocalizedMessage());
         }
     });

 }




블로그 이미지

Link2Me

,
728x90

서버에 있는 이미지를 다운로드하여 Glide 로딩 라이브러리를 이용하여 ImageView 에 보여주는 코드를 테스트하고 적어둔다.


앱 build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation 'com.github.bumptech.glide:glide:3.8.0'
    implementation 'gun0912.ted:tedpermission:2.0.0'
}



public class MainActivity extends AppCompatActivity {
    Context context;
    String croppedFolderName = "/DCIM/PhotoTemp";
    File outputFile = null;
    String outputFileName="myprofile.jpg";
    private ImageView imageView;

    private BackPressHandler backPressHandler;
    DownloadFileFromURL downloadFileAsyncTask;
    ProgressBar progressBar;

    PermissionListener permissionlistener = new PermissionListener() {
        @Override
        public void onPermissionGranted() {
            initView();
        }

        @Override
        public void onPermissionDenied(ArrayList<String> deniedPermissions) {
            Toast.makeText(MainActivity.this, "권한 허용을 하지 않으면 서비스를 이용할 수 없습니다.", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this.getBaseContext();

        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트

        NonSecretApp_Setting(); // 출처를 알 수 없는 앱 설정 화면 띄우기
        checkPermissions();
    }

    private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= 23){ // 마시멜로(안드로이드 6.0) 이상 권한 체크
            TedPermission.with(context)
                    .setPermissionListener(permissionlistener)
                    .setRationaleMessage("이미지를 다루기 위해서는 접근 권한이 필요합니다")
                    .setDeniedMessage("앱에서 요구하는 권한설정이 필요합니다...\n [설정] > [권한] 에서 사용으로 활성화해주세요.")
                    .setPermissions(new String[] { Manifest.permission.READ_EXTERNAL_STORAGE,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.CAMERA})
                    .check();

        } else {
            initView();
        }
    }

    private void initView() {
        imageView = (ImageView) findViewById(R.id.cropImageView);
        Button btn_downloadPhoto = (Button) findViewById(R.id.btn_PhotoDownload);
        btn_downloadPhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (NetworkHelper.checkConnection(context)) { // 인터넷 연결 체크
                    Log.d("Photo", "Photo Upload Task Start");
                    String PhotoURL = Value.IPADDRESS + "/myphoto.jpg";
                    downloadFileAsyncTask = new DownloadFileFromURL();
                    downloadFileAsyncTask.execute(PhotoURL);
                } else {
                    Toast.makeText(context, "인터넷 연결을 확인하세요", Toast.LENGTH_LONG).show();
                }
            }
        });

    }

    class DownloadFileFromURL extends AsyncTask<String, Integer, String> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected String doInBackground(String... apkurl) {
            int count;
            int lenghtOfFile = 0;
            InputStream input = null;
            OutputStream fos = null;

            try {
                URL url = new URL(apkurl[0]);
                URLConnection connection = url.openConnection();
                connection.connect();

                lenghtOfFile = connection.getContentLength(); // 파일 크기를 가져옴

                File path = new File(Environment.getExternalStorageDirectory() + croppedFolderName);
                if (! path.exists())
                    path.mkdirs(); // 디렉토리가 없으면 생성
                outputFile = new File(path, outputFileName);
                if (outputFile.exists()) { // 기존 파일 존재시 삭제하고 다운로드
                    outputFile.delete();
                }

                input = new BufferedInputStream(url.openStream());
                fos = new FileOutputStream(outputFile);
                byte data[] = new byte[1024];
                long total = 0;

                while ((count = input.read(data)) != -1) {
                    if (isCancelled()) {
                        input.close();
                        return String.valueOf(-1);
                    }
                    total = total + count;
                    if (lenghtOfFile > 0) { // 파일 총 크기가 0 보다 크면
                        publishProgress((int) (total * 100 / lenghtOfFile));
                    }
                    fos.write(data, 0, count); // 파일에 데이터를 기록
                }

                fos.flush();

            } catch (Exception e) {
                e.printStackTrace();
                Log.e("UpdateAPP", "Update error! " + e.getMessage());
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    }
                    catch(IOException ioex) {
                        //Very bad things just happened... handle it
                    }
                }
                if (fos != null) {
                    try {
                        fos.close();
                    }
                    catch(IOException ioex) {
                        //Very bad things just happened... handle it
                    }
                }
            }
            return null;
        }

        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            // 백그라운드 작업의 진행상태를 표시하기 위해서 호출하는 메소드
        }

        protected void onPostExecute(String result) {
            if (result == null) {
                // 미디어 스캐닝
                MediaScannerConnection.scanFile(getApplicationContext(), new String[]{outputFile.getAbsolutePath()}, null, new MediaScannerConnection.OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String s, Uri uri) {
                    }
                });

                String imageUri = outputFile.getAbsolutePath();
                Glide.with(context).load(imageUri).into(imageView);
            } else {
                Toast.makeText(getApplicationContext(), "다운로드 에러", Toast.LENGTH_LONG).show();
            }
        }

        protected void onCancelled() {
            // cancel메소드를 호출하면 자동으로 호출되는 메소드
            progressBar.setProgress(0);
        }
    }

    private void NonSecretApp_Setting() {
        if(Build.VERSION.SDK_INT >= 26)  { // 출처를 알 수 없는 앱 설정 화면 띄우기
            PackageManager packageManager = getPackageManager();
            boolean isTrue = packageManager.canRequestPackageInstalls();

            if (!packageManager.canRequestPackageInstalls()) {
                AlertDialog.Builder b = new AlertDialog.Builder(this, android.R.style.Theme_DeviceDefault_Light_Dialog);
                b.setTitle("알림");
                b.setMessage("보안을 위해 스마트폰 환경설정의 '앱 설치 허용'을 설정해 주시기 바랍니다.설정화면으로 이동하시겠습니까?");
                b.setCancelable(false);
                b.setPositiveButton("설정하기", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
                        intent.setData(Uri.parse("package:" + getPackageName()));
                        startActivity(intent);
                    }
                });

                b.setNegativeButton("건너띄기", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                });
                b.show();
            }
        }
    }

    @Override
    public void onBackPressed() {
        backPressHandler.onBackPressed();
    }
}


테스트에 사용한 파일을 첨부한다.

imagedownload.zip


이미지 CROP 처리 구현을 깔끔하게 하는 방법을 해보고자 하는데 좀 더 노력이 필요한 듯....

블로그 이미지

Link2Me

,
728x90

Context mContext;
ImageView mImage = (ImageView) findViewById(R.id.croppedImageView);

String imageUri = Item.getPhoto();
if (imageUri.equals("")) { // 이미지가 없을 때
    int resourceId = R.drawable.photo_base;
    Glide.with(mContext).load(resourceId).into(mImage);
} else { // 이미지가 있을 때
    Glide.with(mContext).load(imageUri).into(mImage);
}


imageUri 는 실제 파일이 존재하는 경로, 예) http://www.abc.com/images/abc.jpg


폰의 폴더에 있는 이미지를 View 할 때

String croppedFolderName = "/DCIM/PhotoTemp";
File outputFile = null;
String outputFileName="myprofile.jpg";
private ImageView imageView;

File path = new File(Environment.getExternalStorageDirectory() + croppedFolderName);
if (! path.exists())
    path.mkdirs(); // 디렉토리가 없으면 생성
outputFile = new File(path, outputFileName);

Bitmap photoBitmap = BitmapFactory.decodeFile(outputFile.getAbsolutePath() );
imageView.setImageBitmap(photoBitmap);


Glide 로 View 하면

String imageUri = outputFile.getAbsolutePath();
Glide.with(context).load(imageUri).into(imageView);

블로그 이미지

Link2Me

,
728x90

인터넷을 검색하여 사용자가 만든 라이브러리를 사용하여 이미지 로딩 처리를 하기도 하는데 이미지 로딩 라이브러리를 사용하는 것이 좋을 거 같아서 테스트를 해보고 있다.


Glide는 안드로이드 이미지 로딩 라이브러리로, 빠르고 효율적인 미디어 관리 오픈소스이고 이미지 로딩 프레임워크다. 미디어 디코딩, 메모리와 디스크 캐싱, 리소스 풀링을 간단하고 쉽게 할 수 있도록 도와준다.


사용법

Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);

- RecyclerView.Adapter listViewAdapter 에서는 this 대신에 context 사용


테스트 환경

Android Studio 2.3.3 버전 ==> 노트북 스펙 부족으로 최신 3.1.3 으로 업그레이드 중지


compileSdkVersion 26
buildToolsVersion "26.0.0"

defaultConfig {
    applicationId "com.tistory.link2me.asynchttpjson"
    minSdkVersion 19
    targetSdkVersion 26
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.loopj.android:android-async-http:1.4.9' // 서버와의 통신 라이브러리
    compile 'com.github.bumptech.glide:glide:3.8.0' // 이미지 라이브러리
    compile 'com.android.support:recyclerview-v7:24.2.0' // ListView 개선 버전
    compile "com.android.support:cardview-v7:24.2.0"
}


https://github.com/bumptech/glide 에 가면 최신버전이 4.7.1 로 나오는데 이걸 사용하면 buildToolsVersion 을 최신으로 사용하라고 나온다. 그래서 4.6.0 으로 해서 컴파일 해봤는데 recyclerview 에서 제대로 처리가 안되는지 에러가 발생하면서 앱이 종료된다. 4.5.0 4.2.0 으로도 시도했으나 실패된다.

glide:3.6.0, 3.7.0, 3.8.0 으로 한 이후에 문제없이 정상동작된다. ==> Android Studio 3.1.3 에서도 정상 동작된다.

자세히 공부하기 싫어서 검색하는 건 포기.....

https://bumptech.github.io/glide/doc/migrating.html


implementation 'com.squareup.picasso:picasso:2.5.2' // 이미지 라이브러리

Picasso 라이브러리를 사용하는데 에러가 발생하여 Glide 라이브러리를 사용해본 건데 아무 버전이나 잘 동작되는건 아닌거 같다.


라이브러리는 버전업이 되면서 오히려 버그가 발생하기도 해서 가능하면 사용하고 싶지 않은데, 코드가 너무 길어지고 메모리 에러 발생 등 고려할 요인들이 너무 많아서 잘 만들어진 라이브러리를 사용하는 것이 속 편하다.


참고하면 도움되는 글

https://d2.naver.com/helloworld/429368


블로그 이미지

Link2Me

,
728x90

안드로이드에서 이미지 파일 다운로드 소스 테스트한 것을 적어둔다.


이미지가 있는 url 에서 파일명만 추출하여 파일명을 자동으로 저장하기 위해서

private String getFileName(String url) {
    // url 에서 파일 이름만 자르기(확장자 제외)
   String fileName = url.substring( url.lastIndexOf('/')+1, url.length() );
   return fileName.substring(0, fileName.lastIndexOf('.'));
}

private String getFileName(String url) {
    // url 에서 파일만 자르기
   return url.substring( url.lastIndexOf('/')+1, url.length() );
}


이미지 파일을 저장할 폴더(디렉토리)를 생성하는 메소드

private File file, dir;
private String saveFolderName = "ImageTemp";

private void MakePhtoDir(){
    //saveFolderName = "/Android/data/" + getPackageName();
    //dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), saveFolderName);
    dir = new File(Environment.getExternalStorageDirectory(), saveFolderName);
    if (!dir.exists())
        dir.mkdirs(); // make dir
}


이미지 파일은 다운로드 완료한 후에는 갤러리를 업데이트 해주어야 볼 수 있다.

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));

이 한줄을 생략하면 USB 케이블 연결해서 해당 폴더를 확인해도 파일명을 다운로드 했는지 여부를 알 수가 없더라.

파일 다운로드 여부를 굳이 알 수 있게 할 것이 아니라면 생략해도 된다.


ImageView 에 출력하는 것은 아래 4줄만 적어주면 된다.

File file = new File(dir + "/" + tempFileName);
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
Bitmap photoBitmap = BitmapFactory.decodeFile(file.getAbsolutePath() );
imageView.setImageBitmap(photoBitmap);


갤러리에서 저장한 이미지를 확인하려면

//저장한 이미지 확인
Intent i = new Intent(Intent.ACTION_VIEW);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setDataAndType(Uri.fromFile(file), "image/*"); //type 지정 (이미지)
getApplicationContext().startActivity(i);



전체 테스트 코드


Androidmanifest.xml 파일에 추가할 사항

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_photodownload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="30dp"
        android:text="사진다운로드"
        tools:layout_editor_absoluteX="239dp"
        tools:layout_editor_absoluteY="31dp" />

    <ImageView
        android:id="@+id/DNImageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:adjustViewBounds="true"
        android:layout_marginTop="30dp"
        android:background="@drawable/profile_noimage"
        android:src="@drawable/photo_base" />
</LinearLayout>

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;

public class PhotoDownload extends Activity {
    private static String TAG = "Photo";
    TextView textView;
    ImageView imageView;
    Context context;

    ProgressBar progressBar;
    private File file, dir;
    private String savePath= "ImageTemp";
    private String FileName = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.photo_download);
        context = this.getBaseContext();

        MakePhtoDir();

        imageView = (ImageView) findViewById(R.id.DNImageView);

        Button photo_download = (Button) findViewById(R.id.btn_photodownload);
        photo_download.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String imgUrl = "http://img.naver.net/static/www/u/2013/0731/nmms_224940510.gif";
                FileName = imgUrl.substring( imgUrl.lastIndexOf('/')+1, imgUrl.length() );
                DownloadPhotoFromURL downloadPhotoFromURL = new DownloadPhotoFromURL();
                // 동일한 파일이 있는지 검사
                if(new File(dir.getPath() + File.separator + FileName).exists() == false){
                    downloadPhotoFromURL.execute(imgUrl,FileName);
                } else {
                    Toast.makeText(context, "파일이 이미 존재합니다", Toast.LENGTH_SHORT).show();

                    File file = new File(dir + "/" + FileName);
                    Bitmap photoBitmap = BitmapFactory.decodeFile(file.getAbsolutePath() );
                    imageView.setImageBitmap(photoBitmap);
                }
            }
        });
    }

    private void MakePhtoDir(){
        //savePath = "/Android/data/" + getPackageName();
        //dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), savePath);
        dir = new File(Environment.getExternalStorageDirectory(), savePath );
        if (!dir.exists())
            dir.mkdirs(); // make dir
    }

    public String getRealPathFromURI(Uri contentUri) {
        // 갤러리 이미지 파일의 실제 경로 구하기
        String[] proj = { MediaStore.Images.Media.DATA };
        Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        return cursor.getString(column_index);
    }

    class DownloadPhotoFromURL extends AsyncTask<String, Integer, String> {
        int count;
        int lenghtOfFile = 0;
        InputStream input = null;
        OutputStream output = null;
        String tempFileName;

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //progressBar.setProgress(0);
        }

        @Override
        protected String doInBackground(String... params) {
            tempFileName = params[1];
            file = new File(dir, params[1]); // 다운로드할 파일명
            try {
                URL url = new URL(params[0]);
                URLConnection connection = url.openConnection();
                connection.connect();

                lenghtOfFile = connection.getContentLength(); // 파일 크기를 가져옴

//                if (file.exists()) {
//                    file.delete();
//                    Log.d(TAG, "file deleted...");
//                }

                input = new BufferedInputStream(url.openStream());
                output = new FileOutputStream(file);
                byte data[] = new byte[1024];
                long total = 0;

                while ((count = input.read(data)) != -1) {
                    if (isCancelled()) {
                        input.close();
                        return String.valueOf(-1);
                    }
                    total = total + count;
                    if (lenghtOfFile > 0) { // 파일 총 크기가 0 보다 크면
                        publishProgress((int) (total * 100 / lenghtOfFile));
                    }
                    output.write(data, 0, count); // 파일에 데이터를 기록
                }

                output.flush();

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (input != null) {
                    try {
                        input.close();
                    }
                    catch(IOException ioex) {
                    }
                }
                if (output != null) {
                    try {
                        output.close();
                    }
                    catch(IOException ioex) {
                    }
                }
            }
            return null;
        }

        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            // 백그라운드 작업의 진행상태를 표시하기 위해서 호출하는 메소드
            //progressBar.setProgress(progress[0]);
            //textView.setText("다운로드 : " + progress[0] + "%");
        }

        protected void onPostExecute(String result) {
            // pdLoading.dismiss();
            if (result == null) {
                Toast.makeText(getApplicationContext(), "다운로드 완료되었습니다.", Toast.LENGTH_LONG).show();

                File file = new File(dir + "/" + tempFileName);
                //이미지 스캔해서 갤러리 업데이트
                sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
                Bitmap photoBitmap = BitmapFactory.decodeFile(file.getAbsolutePath() );
                imageView.setImageBitmap(photoBitmap);
            } else {
                Toast.makeText(getApplicationContext(), "다운로드 에러", Toast.LENGTH_LONG).show();
            }
        }
    }
}


블로그 이미지

Link2Me

,
728x90

이미지 파일 전송시 서버에서 구현할 PHP 소스는 다음과 같다.

개인 프로필 사진을 업로드할 경우의 수정사항이다.


<?php
if(isset($_POST['idx']) && $_POST['idx']>0){
    $idx=$_POST['idx'];

    $file_path='./photos/'.$idx.'.jpg'; //이미지화일명은 인덱스번호로 지정
    if(move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $file_path)) {
        $result = array("result" => "success");
    } else{
        $result = array("result" => "error");
    }
    echo json_encode($result);
}
?>


안드로이드 수정사항

Button btn_UploadPhoto = (Button) findViewById(R.id.btn_Upload); // 사진 업로드
btn_UploadPhoto.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (TextUtils.isEmpty(imagePath)) {
            Toast.makeText(getApplicationContext(), "업로드할 사진을 선택해주세요", Toast.LENGTH_SHORT).show();
        } else {
            if (NetworkHelper.checkConnection(mContext)) { // 인터넷 연결 체크
                Log.d("Photo", "Photo Upload Task Start");
                String ImageUploadURL = Value.IPADDRESS + "/upload.php";
                SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
                String idx = pref.getString("idx", "");
                ImageUploadTask imageUploadTask = new ImageUploadTask();
                imageUploadTask.execute(ImageUploadURL, imagePath, idx);
            } else {
                Toast.makeText(mContext, "인터넷 연결을 확인하세요", Toast.LENGTH_LONG).show();
            }
        }
    }
});


 class ImageUploadTask extends AsyncTask<String, Integer, Boolean> {
    ProgressDialog progressDialog; // API 26에서 deprecated

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setMessage("이미지 업로드중....");
        progressDialog.show();
    }

    @Override
    protected Boolean doInBackground(String... params) {
        try {
            JSONObject jsonObject = JSONParser.uploadImage(params[0],params[1],params[2]);
            if (jsonObject != null)
                return jsonObject.getString("result").equals("success");

        } catch (JSONException e) {
            Log.i("TAG", "Error : " + e.getLocalizedMessage());
        }
        return false;
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
        if (progressDialog != null)
            progressDialog.dismiss();

        if (aBoolean){
            Toast.makeText(getApplicationContext(), "파일 업로드 성공", Toast.LENGTH_LONG).show();
            // 임시 파일 삭제 (카메라로 사진 촬영한 이미지)
            if(mImageCaptureUri != null){
                File file = new File(mImageCaptureUri.getPath());
                if(file.exists()) {
                    file.delete();
                }
                mImageCaptureUri = null;
            }
            imagePath = null;
        }  else{
            Toast.makeText(getApplicationContext(), "파일 업로드 실패", Toast.LENGTH_LONG).show();
        }

    }
}

 public class JSONParser {

    public static JSONObject uploadImage(String imageUploadUrl, String sourceImageFile, String idx) {
        final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/*");

        try {
            File sourceFile = new File(sourceImageFile);
            Log.d("TAG", "File...::::" + sourceFile + " : " + sourceFile.exists());
            String filename = sourceImageFile.substring(sourceImageFile.lastIndexOf("/")+1);

            // OKHTTP3
            RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("uploaded_file", filename, RequestBody.create(MEDIA_TYPE_PNG, sourceFile))
                    .addFormDataPart("idx", idx)
                    .build();

            Request request = new Request.Builder()
                    .url(imageUploadUrl)
                    .post(requestBody)
                    .build();

            OkHttpClient client = new OkHttpClient();
            Response response = client.newCall(request).execute();
            if (response != null) {
                if (response.isSuccessful()) {
                    String res = response.body().string();
                    Log.e("TAG", "Success : " + res);
                    return new JSONObject(res);
                }
            }
        } catch (UnknownHostException | UnsupportedEncodingException e) {
            Log.e("TAG", "Error: " + e.getLocalizedMessage());
        } catch (Exception e) {
            Log.e("TAG", "Other Error: " + e.getLocalizedMessage());
        }
        return null;
    }
}


블로그 이미지

Link2Me

,
728x90

안드로이드 스튜디오에서 사진촬영 및 특정부분 이미지를 CROP 해서 업로드하는 법과 갤러리(앨범)에서 사진을 선택해서 업로드하는 걸 테스트하고 있다.


 삼성 갤럭시 S7 (7.0)

 사진 촬영후 CROP 전송 OK,

 갤러리 사진 선택후 CROP하면 기존 파일을 덮어쓰는 현상(해결 필요)

 삼성 갤럭시 A5 (6.0.1)

 사진 촬영후 CROP 전송 OK, 갤러리 CROP 후 전송 OK

 삼성 갤럭시 S4 (5.0.1)

 사진 촬영후 CROP 전송 OK, 갤러리 CROP 후 전송 OK


안드로이드 7.0 이상 운영체제에서 문제가 있는 것인지 집중적인 테스트가 필요할 거 같다.

지난번 테스트보다는 기능이 개선되었지만, 좀 더 보완이 필요하다.

7.0 에서 테스트한 결과는 이미지를 축소 저장하는 saveCropImage 메소드가 없이 자체적으로(?) 이미지를 축소하기 때문에 원본 갤러리 파일에 문제가 생기는 거 같다.

사진 촬영후 전송할 경우 같은 myphoto.jpg 로 저장하도록 해서 문제 없이 서버에 축소된 이미지가 업로드된다.

문제는 갤러리 사진을 업로드할 경우 CROP 까지는 잘되는데, CROP 한 후에 원본 이미지를 훼손하는 문제가 생긴다. 갤럭시 S4, A5에서는 정상동작한다. 코드 상으로도 정상 동작되어야 맞는거 같은데.....

7.0 에서 뭔가 내가 놓치는 것이 뭘까??? 좀 더 찾아보고 해결해보련다.


음.... 확인해보니 Build.VERSION.SDK_INT 가 24이상인 경우에는 다른 방법으로 구현해야 하더라.

일일이 로그를 찍어가면서 확인해보니, CROP 이후에 값 자체가 넘어오지 않고 그냥 임시로 생성한 파일만 지정한 폴더(디렉토리)에 저장되는 걸 확인했다.

내가 구현한 방식은 한마디로 편법으로 7.0 에서 동작되었던 것이다.

임시로 저장된 파일을 다시 읽어서 그 파일을 서버로 업로드하는 거다.


 package com.tistory.link2me.imageupload;

import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.squareup.picasso.Picasso;
import com.tistory.link2me.common.BackPressHandler;
import com.tistory.link2me.common.JSONParser;
import com.tistory.link2me.common.NetworkHelper;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PhotoActivity extends Activity {
    Context mContext;

    private static final int PICK_FROM_CAMERA = 0; //카메라 촬영으로 사진 가져오기
    private static final int PICK_FROM_ALBUM = 1; //앨범에서 사진 가져오기
    private static final int CROP_FROM_CAMERA = 2; // 가져온 사진을 자르기 위한 변수
    private static final int PICK_FROM_File = 3; //앨범에서 사진 가져오기(원본 올리기)

    private static Uri mImageCaptureUri; // Stactic 으로 설정해야 에러 발생하지 않음.
    private String imagePath;
    private ImageView imageView;
    private Bitmap photoBitmap;
    String saveFolderName = "cameraTemp";
    File mediaFile = null;
    String saveFileName = "myphoto.jpg";

    ProgressDialog mProgressDialog;
    private BackPressHandler backPressHandler;

    private String[] permissions = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO
    };
    private static final int MULTIPLE_PERMISSIONS = 101;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_photo);
        mContext = this.getBaseContext();
        backPressHandler = new BackPressHandler(this); // 뒤로 가기 버튼 이벤트

        System.out.println("프로세스당 메모리 용량 : " + ((ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass());
        System.out.println("largeHeap 크기 : " + ((ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)).getLargeMemoryClass());
        System.out.println("패키지 설치 경로 : " + getApplicationContext().getFilesDir().getAbsolutePath().replace("files", ""));

        checkPermissions();
        initView();
    }

    private void initView() {
        imageView = (ImageView) findViewById(R.id.croppedImageView);
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                AlertDialog.Builder builder = new AlertDialog.Builder(PhotoActivity.this);
                builder.setMessage("업로드할 이미지 선택");
                DialogInterface.OnClickListener cameraListener = new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        doTakePhotoAction();
                    }
                };

                DialogInterface.OnClickListener albumListener = new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        getGallery(); // 갤러리(앨범)에서 이미지 가져오기
                    }
                };

                DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }

                };
                builder.setTitle("업로드할 이미지 선택");
                builder.setPositiveButton("사진촬영", cameraListener);
                builder.setNeutralButton("앨범선택", albumListener);
                builder.setNegativeButton("취소", cancelListener);
                builder.show();

            }
        });

        Button btn_UploadPhoto = (Button) findViewById(R.id.btn_Upload);
        btn_UploadPhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (TextUtils.isEmpty(imagePath)) {
                    Toast.makeText(getApplicationContext(), "업로드할 사진을 선택해주세요", Toast.LENGTH_SHORT).show();
                } else {
                    if (NetworkHelper.checkConnection(mContext)) { // 인터넷 연결 체크
                        Log.d("Photo", "Photo Upload Task Start");
                        String ImageUploadURL = Value.IPADDRESS + "/upload.php";
                        ImageUploadTask imageUploadTask = new ImageUploadTask();
                        imageUploadTask.execute(ImageUploadURL, imagePath);
                    } else {
                        Toast.makeText(mContext, "인터넷 연결을 확인하세요", Toast.LENGTH_LONG).show();
                    }
                }
            }
        });

    }

    private Boolean isAirModeOn() {
        Boolean isAirplaneMode;
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1){
            isAirplaneMode = Settings.System.getInt(getContentResolver(),
                    Settings.System.AIRPLANE_MODE_ON, 0) == 1;
        }else{
            isAirplaneMode = Settings.Global.getInt(getContentResolver(),
                    Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
        }
        return isAirplaneMode;
    }

    /**
     * 카메라에서 사진 촬영
     */
    private void doTakePhotoAction() {  // 카메라 앱을 이용하여 사진 찍기
        // Intent를 사용하여 안드로이드에서 기본적으로 제공해주는 카메라를 이용하는 방법 이용
        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 이미지가 저장될 파일은 카메라 앱이 구동되기 전에 세팅해서 넘겨준다.
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, getImageUri(saveFileName)); // null
        cameraIntent.putExtra("return-data", true);
        startActivityForResult(cameraIntent, PICK_FROM_CAMERA); // 7.0 에서는 에러가 발생함. (API26 으로 컴파일 한 경우)
    }

    // 사진 선택을 위해 갤러리를 호출
    private void getGallery() {
        // File System.
        final Intent galleryIntent = new Intent();
        galleryIntent.setType("image/*"); // 이미지 파일 호출
        galleryIntent.setAction(Intent.ACTION_PICK);

        // Chooser of file system options.
        final Intent chooserIntent = Intent.createChooser(galleryIntent, "Select Image");
        galleryIntent.setType(MediaStore.Images.Media.CONTENT_TYPE);
        //startActivityForResult(chooserIntent, PICK_FROM_File);
        startActivityForResult(chooserIntent, PICK_FROM_ALBUM);
    }

    private Uri getImageUri(String saveFile) {
        // 임시로 사용할 파일의 경로를 생성
        File mediaStorageDir = new File(Environment.getExternalStorageDirectory() + "/DCIM", saveFolderName);
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }

        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        if(saveFile != null){
            mediaFile = new File(mediaStorageDir.getPath() + File.separator + saveFile);
        } else {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator + "pic_"+ timeStamp + ".jpg");
        }
        mImageCaptureUri = Uri.fromFile(mediaFile);
        imagePath = mImageCaptureUri.getPath();
        Log.e("mImageCaptureUri : ", mImageCaptureUri.toString());
        Log.e("imagePath : ", imagePath);

        return mImageCaptureUri;
    }

    public String getRealPathFromURI(Uri contentUri) {
        String[] proj = { MediaStore.Images.Media.DATA };
        Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        return cursor.getString(column_index);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode != Activity.RESULT_OK) return;
        switch (requestCode){
            case PICK_FROM_ALBUM: {
                mImageCaptureUri = data.getData();
                Log.e("앨범이미지 CROP",mImageCaptureUri.getPath().toString());
                imagePath = getRealPathFromURI(mImageCaptureUri); // 실제 파일이 존재하는 경로
                Log.e("앨범이미지 경로",imagePath);
            }

            case PICK_FROM_CAMERA: {

                // 이미지를 가져온 이후의 리사이즈할 이미지 크기를 결정
                Intent intent = new Intent("com.android.camera.action.CROP");
                intent.setDataAndType(mImageCaptureUri, "image/*");

                // CROP할 이미지를 125*170 크기로 저장
                intent.putExtra("outputX", 125); // CROP한 이미지의 x축 크기
                intent.putExtra("outputY", 170); // CROP한 이미지의 y축 크기
                intent.putExtra("aspectX", 1); // CROP 박스의 X축 비율
                intent.putExtra("aspectY", 1); // CROP 박스의 Y축 비율
                intent.putExtra("scale", true);
                intent.putExtra("return-data", true);

                startActivityForResult(intent, CROP_FROM_CAMERA); // CROP_FROM_CAMERA case문 이동
                break;
            }

            case CROP_FROM_CAMERA:  {
                // CROP 된 이후의 이미지를 넘겨 받음
                final Bundle extras = data.getExtras();

                // CROP 된 이미지를 저장하기 위한 File 경로 설정
                String filePath = getImageUri(saveFileName).getPath();
                Log.e("mImageCaptureUri : ", "Croped " + mImageCaptureUri.toString());

                imagePath = filePath;

                if(extras != null) {
                    //photoBitmap = extras.getParcelable("data"); // CROP된 BITMAP
                    photoBitmap = (Bitmap)data.getExtras().get("data"); // CROP된 BITMAP
                    // 레이아웃의 이미지칸에 CROP된 BITMAP을 보여줌
                    saveCropImage(photoBitmap,imagePath); //  CROP 된 이미지를 외부저장소, 앨범에 저장한다.
                    // sendBroadcast를 통해 Crop된 사진을 앨범에 보이도록 갱신한다.
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        sendBroadcast(new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(mediaFile)) );
                    } else {
                        sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
                    }
                }

                Picasso.with(mContext).load(new File(imagePath))
                        .into(imageView); // 피카소 라이브러를 이용하여 선택한 이미지를 imageView에 출력

                break;
            }
            case PICK_FROM_File: {
                // URI 정보를 이용하여 이미지(사진) 정보를 가져온다.
                if (data == null) {
                    Toast.makeText(mContext, "Unable to Pickup Image", Toast.LENGTH_SHORT).show();
                    return;
                }
                Uri selectedImageUri = data.getData();
                Log.e("IMAGE SEL", "" + selectedImageUri);
                String[] filePathColumn = {MediaStore.Images.Media.DATA};

                Cursor cursor = getContentResolver().query(selectedImageUri, filePathColumn, null, null, null);

                if (cursor != null) {
                    cursor.moveToFirst();

                    imagePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));
                    Log.e("IMAGE SEL imagePath : ", imagePath);                 

                    Picasso.with(mContext).load(new File(imagePath))
                            .into(imageView); // 피카소 라이브러를 이용하여 선택한 이미지를 imageView에 출력
                    cursor.close();

                }

                break;
            }
        }

    }

    // Bitmap 을 저장하는 메소드
    private void saveCropImage(Bitmap bitmap, String filePath) {
        //read image file
        File copyFile = new File(filePath);
        BufferedOutputStream bos = null;
        try {
            copyFile.createNewFile();
            int quality = 100;
            bos = new BufferedOutputStream(new FileOutputStream(copyFile));
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos);
            // 이미지가 클 경우 OutOfMemoryException 발생이 예상되어 압축
            bos.flush();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    class ImageUploadTask extends AsyncTask<String, Integer, Boolean> {
        ProgressDialog progressDialog; // API 26에서 deprecated

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressDialog = new ProgressDialog(PhotoActivity.this);
            progressDialog.setMessage("이미지 업로드중....");
            progressDialog.show();
        }

        @Override
        protected Boolean doInBackground(String... params) {
            try {
                JSONObject jsonObject = JSONParser.uploadImage(params[0],params[1]);
                if (jsonObject != null)
                    return jsonObject.getString("result").equals("success");

            } catch (JSONException e) {
                Log.i("TAG", "Error : " + e.getLocalizedMessage());
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if (progressDialog != null)
                progressDialog.dismiss();

            if (aBoolean){
                Toast.makeText(getApplicationContext(), "파일 업로드 성공", Toast.LENGTH_LONG).show();
            }  else{
                Toast.makeText(getApplicationContext(), "파일 업로드 실패", Toast.LENGTH_LONG).show();
            }

            // 임시 파일 삭제 (카메라로 사진 촬영한 이미지)
            if(mImageCaptureUri != null){
                File file = new File(mImageCaptureUri.getPath());
                if(file.exists()) {
                    file.delete();
                }
                mImageCaptureUri = null;
            }

            if (aBoolean){
                imagePath = null;
            }
            
        }

    }

    @Override
    public void onBackPressed() {
        backPressHandler.onBackPressed();
    }

    public boolean checkPermissions() {
        int result;
        List<String> permissionList = new ArrayList<>();
        for (String pm : permissions) {
            result = ContextCompat.checkSelfPermission(this, pm);
            if (result != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(pm);
            }
        }
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), MULTIPLE_PERMISSIONS);
            return false;
        }
        return true;
    }

    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case MULTIPLE_PERMISSIONS: {
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++) {
                        if (permissions[i].equals(this.permissions[i])) {
                            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                                showToast_PermissionDeny();
                            }
                        }
                    }
                } else {
                    showToast_PermissionDeny();
                }
                return;
            }
        }
    }

    public void showToast_PermissionDeny() {
        Toast.makeText(this, "권한 요청에 동의 해주셔야 이용 가능합니다. 설정에서 권한 허용 하시기 바랍니다.", Toast.LENGTH_SHORT).show();
        finish();
    }
}


앱 build.Gradle 설정 내용

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "25.0.3"

    defaultConfig {
        applicationId "com.tistory.link2me.photoupload"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:22.0.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.squareup.okhttp3:okhttp:3.9.0'
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.google.firebase:firebase-messaging:10.2.6'
    testCompile 'junit:junit:4.12'
}


public class NetworkHelper {

    /** CHECK WHETHER INTERNET CONNECTION IS AVAILABLE OR NOT */
    public static boolean checkConnection(Context context) {
        return  ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo() != null;
    }
}

 public class OKHttpComm {
    //GET network request
    //@RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static String GET(String url) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .build();

        Response response = null;
        try {
            response = client.newCall(request).execute();
            return response.body().string();
        } catch (IOException e) {
            Log.e("OKHttp", "error in getting response get request okhttp");
        }
        return null;
    }

    //POST network request
    public static String POST(String url, RequestBody body) throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Response response = client.newCall(request).execute();
        return response.body().string();
    }   
}

 public class PHPComm extends Activity {

    // serverURL : JSON 요청을 받는 서버의 URL
    // postParams : POST 방식으로 전달될 입력 데이터
    // 반환 데이터 : 서버에서 전달된 JSON 데이터
    public static String getJson(String serverUrl, String postParams) throws Exception {

        BufferedReader bufferedReader = null;  
        try {  
            //Thread.sleep(100);
            URL url = new URL(serverUrl);  
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 세션 쿠키 전달
            String cookieString = CookieManager.getInstance().getCookie(Value.IPADDRESS);
            
            StringBuilder sb = new StringBuilder();  

            if(conn != null){ // 연결되었으면
                //add request header
                conn.setRequestMethod("POST");
                conn.setRequestProperty("USER-AGENT", "Mozilla/5.0");
                conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                conn.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
                if (cookieString != null) {
                   conn.setRequestProperty("Cookie", cookieString);
                   Log.e("PHP_getCookie", cookieString);
                 }
                conn.setConnectTimeout(10000);
                conn.setReadTimeout(10000);
                conn.setUseCaches(false);
                conn.setDefaultUseCaches(false);
                conn.setDoOutput(true); // POST 로 데이터를 넘겨주겠다는 옵션
                conn.setDoInput(true);

                // Send post request
                DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
                wr.writeBytes(postParams);
                wr.flush();
                wr.close();

                int responseCode = conn.getResponseCode();
                System.out.println("GET Response Code : " + responseCode);        
                if(responseCode == HttpURLConnection.HTTP_OK){ // 연결 코드가 리턴되면
                    bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                    String json;
                    while((json = bufferedReader.readLine())!= null){
                        sb.append(json + "\n");
                    }      
                }
                bufferedReader.close();
            }
            System.out.println("PHP Comm Out Size : " + sb.length());
            return sb.toString().trim();  
            // 수행이 끝나고 리턴하는 값은 다음에 수행될 onProgressUpdate 의 파라미터가 된다
        } catch(Exception e){  
            return new String("Exception: " + e.getMessage());
        }   
 
    }

}



2017.9.20 수정 사항

안드로이드 7.0 (삼성 갤럭시 기준)에서 이미지 저장하는 것이 기존 것과 달라서 다른 방법으로 시도를 해봤지만 compileSdkVersion 22 로 해서인지 다른 블로그에서 해보라는 방식으로 해도 안된다.

수정 테스트한 것만 발췌하여 적어둔다.




2017.9.21 LG LGM-X600K 테스트

LG폰을 구해서 사진 촬영 및 이미지 업로드 테스트를 했더니 기존 소스 코드로 해야만 동작이 되더라.

그래서 아래 부분을 보완했다.

대부분 KIKAT 이상 단말이므로 sendBroadcast(new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(mediaFile)) ); 부분만 코드에 추가해도 될 것으로 본다.


case CROP_FROM_CAMERA:  {
    // CROP 된 이후의 이미지를 넘겨 받음
    final Bundle extras = data.getExtras();

    // CROP 된 이미지를 저장하기 위한 File 경로 설정
    String filePath = getFileUri(saveFileName).getPath();
    imagePath = filePath;

    if(extras != null) {
        //photoBitmap = extras.getParcelable("data"); // CROP된 BITMAP
        photoBitmap = (Bitmap)data.getExtras().get("data"); // CROP된 BITMAP
        saveCropImage(photoBitmap,filePath); //  CROP 된 이미지를 외부저장소, 앨범에 저장한다.
        // sendBroadcast를 통해 Crop된 사진을 앨범에 보이도록 갱신한다.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            sendBroadcast(new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(mediaFile)) );
        } else {
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
        }
    }

    photoBitmap = BitmapFactory.decodeFile(imagePath );
    // 레이아웃의 이미지칸에 CROP된 BITMAP을 보여줌
    imageView.setImageBitmap(photoBitmap);

    break;
}


블로그 이미지

Link2Me

,
728x90

안드로이드 스튜디오에서 OKHttp 라이브러리를 활용하여 사진 촬영 및 이미지를 서버에 업로드하는 기능을 테스트하고 있다.


http://link2me.tistory.com/1354 보다 약간 내용을 보강했고, 사진 촬영에 대한 메소드를 추가했다.


테스트 환경 : 삼성 갤럭시 S7 (정상 동작), 삼성 갤럭시 S4(비정상 에러, 사진 촬영후 CROP으로 넘기는 부분)

사진 촬영을 하고 이미지 CROP 하는 부분에서 비정상적으로 처리를 하면서 문제가 발생한다.


OKHttp 라이브러리를 이용하면 서버로 업로드를 텍스트, 파일첨부 등을 완벽하고 쉽게 해준다.

public class JSONParser {

    public static JSONObject uploadImage(String imageUploadUrl, String sourceImageFile) {
        final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/*");

        try {
            File sourceFile = new File(sourceImageFile);
            Log.d("TAG", "File...::::" + sourceFile + " : " + sourceFile.exists());
            String filename = sourceImageFile.substring(sourceImageFile.lastIndexOf("/")+1);

            // OKHTTP3
            RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("uploaded_file", filename, RequestBody.create(MEDIA_TYPE_PNG, sourceFile))
                    .addFormDataPart("result", "photo_image")
                    .build();

            Request request = new Request.Builder()
                    .url(imageUploadUrl)
                    .post(requestBody)
                    .build();

            OkHttpClient client = new OkHttpClient();
            Response response = client.newCall(request).execute();
            if (response != null) {
                if (response.isSuccessful()) {
                    String res = response.body().string();
                    Log.e("TAG", "Error: " + res);
                    return new JSONObject(res);
                }
            }
        } catch (UnknownHostException | UnsupportedEncodingException e) {
            Log.e("TAG", "Error: " + e.getLocalizedMessage());
        } catch (Exception e) {
            Log.e("TAG", "Other Error: " + e.getLocalizedMessage());
        }
        return null;
    }
}


퍼미션 체크하는 부분은 좀 더 수정하여 완벽하게 동작하도록 만드는 것이 필요하다.


public class MainActivity extends AppCompatActivity {
   
    Context mContext;

    private static final int PICK_FROM_CAMERA = 1; //카메라 촬영으로 사진 가져오기
    private static final int PICK_FROM_ALBUM = 2; //앨범에서 사진 가져오기
    private static final int CROP_FROM_CAMERA = 3; // 가져온 사진을 자르기 위한 변수

    private Uri mImageCaptureUri;
    private Bitmap mImageBitmap;
    String imagePath;

    ImageView imageView;
    TextView textView;

    PermissionsChecker checker;
    Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //상태바 없애기 -- 아직 제대로 동작 안됨
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);


        setContentView(R.layout.activity_main);

        mContext = getApplicationContext();

        if (Build.VERSION.SDK_INT >= 23) {
            PermissionCheck permissionCheck = new PermissionCheck(MainActivity.this);
            if(permissionCheck.checkPermissions() == false){
                finish();
            }
        }

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        textView = (TextView) findViewById(R.id.textView);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DialogInterface.OnClickListener cameraListener = new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        // 카메라에서 사진 촬영
                        doTakePhotoAction();
                    }
                };
                DialogInterface.OnClickListener albumListener = new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        getGallery(); // 갤러리(앨범)에서 이미지 가져오기
                    }
                };
                DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.dismiss();
                    }
                };

                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("업로드할 이미지 선택")
                        .setPositiveButton("사진촬영",cameraListener)
                        .setNeutralButton("앨범선택",albumListener)
                        .setNegativeButton("취소",cancelListener)
                        .show();
            }
        });

        imageView = (ImageView) findViewById(R.id.imageView);

        // 선택된 사진을 받아 서버에 업로드한다.
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!TextUtils.isEmpty(imagePath)) {
                    if (NetworkHelper.checkConnection(mContext)) { // 인터넷 연결 체크
                        String ImageUploadURL = "http://192.168.0.100/upload/upload.php";
                        new ImageUploadTask().execute(ImageUploadURL, imagePath);
                    } else {
                        Toast.makeText(mContext, "인터넷 연결을 확인하세요", Toast.LENGTH_LONG).show();
                    }
                } else {
                    Toast.makeText(mContext, "먼저 업로드할 파일을 선택하세요", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private  class ImageUploadTask extends AsyncTask<String, Integer, Boolean> {
        ProgressDialog progressDialog; // API 26에서 deprecated

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            progressDialog = new ProgressDialog(MainActivity.this);
            progressDialog.setMessage("이미지 업로드중....");
            progressDialog.show();
        }

        @Override
        protected Boolean doInBackground(String... params) {

            try {
                JSONObject jsonObject = JSONParser.uploadImage(params[0],params[1]);
                if (jsonObject != null){
                    return jsonObject.getString("result").equals("success");
                }
            } catch (JSONException e) {
                Log.i("TAG", "Error : " + e.getLocalizedMessage());
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if (progressDialog != null)
                progressDialog.dismiss();

            if (aBoolean){
                Toast.makeText(getApplicationContext(), "파일 업로드 성공", Toast.LENGTH_LONG).show();
            }  else{
                Toast.makeText(getApplicationContext(), "파일 업로드 실패", Toast.LENGTH_LONG).show();
            }

            // 임시 파일 삭제 (카메라로 사진 촬영한 이미지)
            if(mImageCaptureUri != null){
                File file = new File(mImageCaptureUri.getPath());
                if(file.exists()) {
                    file.delete();
                }
                mImageCaptureUri = null;
            }

            imagePath = "";
            textView.setVisibility(View.VISIBLE);
            imageView.setVisibility(View.INVISIBLE);

        }
    }

    // 사진 선택을 위해 갤러리를 호출
    private void getGallery() {
        // File System.
        final Intent galleryIntent = new Intent();
        galleryIntent.setType("image/*");
        galleryIntent.setAction(Intent.ACTION_PICK);

        // Chooser of file system options.
        final Intent chooserIntent = Intent.createChooser(galleryIntent, "Select Image");
        startActivityForResult(chooserIntent, PICK_FROM_ALBUM);
    }

    private void doTakePhotoAction() {  // 카메라 앱을 이용하여 사진 찍기
        // Intent를 사용하여 안드로이드에서 기본적으로 제공해주는 카메라를 이용하는 방법 이용
        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        // 임시로 사용할 파일의 경로를 생성
        String url = "CameraPicture_" + String.valueOf(new SimpleDateFormat( "yyyyMMdd_HHmmss").format( new Date())) + ".jpg";
        mImageCaptureUri = Uri.fromFile(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), url));
        imagePath = mImageCaptureUri.getPath();
        // 이미지가 저장될 파일은 카메라 앱이 구동되기 전에 세팅해서 넘겨준다.
        cameraIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, mImageCaptureUri);
        cameraIntent.putExtra("return-data", true);

        startActivityForResult(cameraIntent, PICK_FROM_CAMERA); // 7.0 에서는 에러가 발생함. (API26 으로 컴파일 한 경우)
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode != RESULT_OK) return;
        switch (requestCode){
            case PICK_FROM_ALBUM: {
                // URI 정보를 이용하여 이미지(사진) 정보를 가져온다.
                if (data == null) {
                    Toast.makeText(mContext, "Unable to Pickup Image", Toast.LENGTH_SHORT).show();
                    return;
                }
                Uri selectedImageUri = data.getData();
                String[] filePathColumn = {MediaStore.Images.Media.DATA};

                Cursor cursor = getContentResolver().query(selectedImageUri, filePathColumn, null, null, null);

                if (cursor != null) {
                    cursor.moveToFirst();

                    imagePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]));

                    Picasso.with(mContext).load(new File(imagePath))
                            .into(imageView); // 피카소 라이브러를 이용하여 선택한 이미지를 imageView에 출력
                    cursor.close();

                } else {
                    Snackbar.make(findViewById(R.id.parentView), "Unable to Load Image", Snackbar.LENGTH_LONG).setAction("Try Again", new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            getGallery();
                        }
                    }).show();
                }

                textView.setVisibility(View.GONE);
                imageView.setVisibility(View.VISIBLE);
                break;
            }

            case PICK_FROM_CAMERA: {
                // 이미지를 가져온 이후의 리사이즈할 이미지 크기를 결정
                Intent intent = new Intent("com.android.camera.action.CROP");
                intent.setDataAndType(mImageCaptureUri, "image/*");

                // CROP할 이미지를 125*170 크기로 저장
//                intent.putExtra("outputX", 125); // CROP한 이미지의 x축 크기
//                intent.putExtra("outputY", 170); // CROP한 이미지의 y축 크기
//                intent.putExtra("aspectX", 1); // CROP 박스의 X축 비율
//                intent.putExtra("aspectY", 1); // CROP 박스의 Y축 비율
                intent.putExtra("scale", true);
                intent.putExtra("return-data", true);

                startActivityForResult(intent, CROP_FROM_CAMERA); // CROP_FROM_CAMERA case문 이동
                break;
            }

            case CROP_FROM_CAMERA:  {
                // CROP 된 이후의 이미지를 넘겨 받음
                final Bundle extras = data.getExtras();
                if(extras != null) {
                    //mImageBitmap = extras.getParcelable("data"); // CROP된 BITMAP
                    mImageBitmap = (Bitmap)data.getExtras().get("data"); // CROP된 BITMAP
                    // 레이아웃의 이미지칸에 CROP된 BITMAP을 보여줌
                    saveCropImage(mImageBitmap,imagePath); //  CROP 된 이미지를 외부저장소, 앨범에 저장
                    break;
                }

                Picasso.with(mContext).load(new File(imagePath))
                        .into(imageView); // 피카소 라이브러를 이용하여 선택한 이미지를 imageView에 출력

                textView.setVisibility(View.GONE);
                imageView.setVisibility(View.VISIBLE);
                break;
            }

        }

    }

    // 저장된 사진을 사진 갤러리에 추가 (아직 테스트를 제대로 못한 부분)
    private void galleryAddPic(){
        Intent mediaScanIntent = new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        File file = new File( imagePath);
        Uri contentUri = Uri.fromFile(file);
        mediaScanIntent.setData( contentUri);
        this.sendBroadcast( mediaScanIntent);
    }

    // Bitmap 을 저장하는 메소드
    private void saveCropImage(Bitmap bitmap, String filePath) {
        File copyFile = new File(filePath);
        BufferedOutputStream bos = null;
        try {
            copyFile.createNewFile();
            bos = new BufferedOutputStream(new FileOutputStream(copyFile));
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos); // 이미지가 클 경우 OutOfMemoryException 발생이 예상되어 압축
            // sendBroadcast를 통해 Crop된 사진을 앨범에 보이도록 갱신한다.
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(copyFile)));

            bos.flush();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
   
}


테스트에 사용한 소스와 Gradle 를 첨부한 파일


OKHttpUpload.zip


PHP 소스는 이전 게시글에 언급했으므로 그걸 이용하면 된다.


API 26 으로 컴파일 한 경우, 사진 촬영부분이 CROP 으로 넘기는 부분에서 에러가 발생하는 원인 등을 찾아서 완벽하게 수정하면 게시글 내용을 수정할 것이다.


증명사진 정도의 이미지로 잘라서 업로드하는 부분은 주석처리하는 것을 없애면 된다.


                // CROP할 이미지를 125*170 크기로 저장
                intent.putExtra("outputX", 125); // CROP한 이미지의 x축 크기
                intent.putExtra("outputY", 170); // CROP한 이미지의 y축 크기
                intent.putExtra("aspectX", 1); // CROP 박스의 X축 비율
                intent.putExtra("aspectY", 1); // CROP 박스의 Y축 비율
                intent.putExtra("scale", true);
                intent.putExtra("return-data", true);



PHP 서버의 upload.php 파일 코드를 여기에도 적는다. (2018.05.31)

json_encode 는 파일 Encoding Mode 가 UTF-8 이어야만 한다.


 <?php
if(isset($_POST['idx']) && $_POST['idx']>0){
    $idx=$_POST['idx'];

    $file_path='./photos/'.$idx.'.jpg';//이미지화일명은 인덱스번호
    //$file_path = "";
    //$file_path = $file_path . basename( $_FILES['uploaded_file']['name']);
    if(move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $file_path)) {
        $result = array("result" => "success");
    } else{
        $result = array("result" => "error");
    }
    echo json_encode($result);
}
?>


그리고 파일도 전체 테스트한 걸 모두 압축해서 올린다.

okhttpupload_1.zip


블로그 이미지

Link2Me

,
728x90

제목 : 개도국 배려한 페북 안드로이드 앱 업뎃 화제
관련기사 : http://www.zdnet.co.kr/news/news_view.asp?artice_id=20140620103700&type=det&re=

OkHttp는 안드로이드 및 자바 애플리케이션용 HTTP 및 SPDY 프로토콜 클라이언트다.
HTTP 프로토콜의 효율성을 높여주고 전체 네트워킹 속도를 향상시키면서 대역폭을 절감시켜준다.
스퀘어에서 만든 이 오픈소스 클라이언트는 전격적으로 페이스북 안드로이드앱에 채택됐다.
OkHttp로 교체하자 뉴스피드의 이미지 로드 실패가 줄어들었다.


안드로이드 스튜디오 기반 OKHttp 라이브러리를 이용하여 갤러리에서 이미지를 선택하여 서버(PHP)에 업로드하는 괜찮은 소스를 찾았다.


https://github.com/pratikbutani/OKHTTPUploadImage 에서 파일을 다운로드 받아서 Import 하면 예제를 실행해 볼 수 있다.


나중에 다른 소스에 활용하기 위해 내가 사용하는 코드 명칭으로 일부 수정했고, 해당 메소드에 대한 설명을 추가해서 테스트 했다.


gradle 추가

    compile 'com.squareup.okhttp3:okhttp:3.8.1'
    compile 'com.squareup.picasso:picasso:2.5.2'

서버에 업로드하려면 인터넷 연결이 되어야 하고 갤러리에 접속하려면 권한이 부여되어야 한다.

 <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>


// 선택된 사진을 받아 서버에 업로드한다.
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (!TextUtils.isEmpty(imagePath)) {
            if (NetworkHelper.checkConnection(mContext)) { // 인터넷 연결 체크
                String ImageUploadURL = "http://192.168.0.100/upload/upload.php";
                new ImageUploadTask().execute(ImageUploadURL, imagePath);
            } else {
                Toast.makeText(mContext, "인터넷 연결을 확인하세요", Toast.LENGTH_LONG).show();
            }
        } else {
            Toast.makeText(mContext, "먼저 업로드할 파일을 선택하세요", Toast.LENGTH_SHORT).show();
        }
    }
});

private  class ImageUploadTask extends AsyncTask<String, Integer, Boolean> {
    ProgressDialog progressDialog;

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setMessage("이미지 업로드중....");
        progressDialog.show();
    }

    @Override
    protected Boolean doInBackground(String... params) {

        try {
            JSONObject jsonObject = JSONParser.uploadImage(params[0],params[1]);
            if (jsonObject != null)
                return jsonObject.getString("result").equals("success");

        } catch (JSONException e) {
            Log.i("TAG", "Error : " + e.getLocalizedMessage());
        }
        return false;
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
        if (progressDialog != null)
            progressDialog.dismiss();

        if (aBoolean)
            Toast.makeText(getApplicationContext(), "파일 업로드 성공", Toast.LENGTH_LONG).show();
        else
            Toast.makeText(getApplicationContext(), "파일 업로드 실패", Toast.LENGTH_LONG).show();

        imagePath = "";
        textView.setVisibility(View.VISIBLE);
        imageView.setVisibility(View.INVISIBLE);
    }
}

// 사진 선택을 위해 갤러리를 호출
private void getGallery() {
    // File System.
    final Intent galleryIntent = new Intent();
    galleryIntent.setType("image/*");
    galleryIntent.setAction(Intent.ACTION_PICK);

    // Chooser of file system options.
    final Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.string_choose_image));
    startActivityForResult(chooserIntent, 1010);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // URI 정보를 이용하여 사진 정보를 가져온다.
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK && requestCode == 1010) {
        if (data == null) {
            Snackbar.make(findViewById(R.id.parentView), "Unable to Pickup Image", Snackbar.LENGTH_INDEFINITE).show();
            return;
        }
        Uri selectedImageUri = data.getData();
        String[] filePathColumn = {MediaStore.Images.Media.DATA};

        Cursor cursor = getContentResolver().query(selectedImageUri, filePathColumn, null, null, null);

        if (cursor != null) {
            cursor.moveToFirst();

            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
            imagePath = cursor.getString(columnIndex);

            Picasso.with(mContext).load(new File(imagePath))
                    .into(imageView); // 피카소 라이브러를 이용하여 선택한 이미지를 imageView에  전달.
            cursor.close();

        } else {
            Snackbar.make(findViewById(R.id.parentView), "Unable to Load Image", Snackbar.LENGTH_LONG).setAction("Try Again", new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getGallery();
                }
            }).show();
        }

        textView.setVisibility(View.GONE);
        imageView.setVisibility(View.VISIBLE);
    }
}



public class JSONParser {

    public static JSONObject uploadImage(String imageUploadUrl, String sourceImageFile) {

        try {
            File sourceFile = new File(sourceImageFile);
            Log.d("TAG", "File...::::" + sourceFile + " : " + sourceFile.exists());
            final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/*");
            String filename = sourceImageFile.substring(sourceImageFile.lastIndexOf("/")+1);

            // OKHTTP3
            RequestBody requestBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("uploaded_file", filename, RequestBody.create(MEDIA_TYPE_PNG, sourceFile))
                    .addFormDataPart("result", "photo_image")
                    .build();

            Request request = new Request.Builder()
                    .url(imageUploadUrl)
                    .post(requestBody)
                    .build();

            OkHttpClient client = new OkHttpClient();
            Response response = client.newCall(request).execute();
            String res = response.body().string();
            Log.e("TAG", "Error: " + res);
            return new JSONObject(res);

        } catch (UnknownHostException | UnsupportedEncodingException e) {
            Log.e("TAG", "Error: " + e.getLocalizedMessage());
        } catch (Exception e) {
            Log.e("TAG", "Other Error: " + e.getLocalizedMessage());
        }
        return null;
    }
}


MediaType.parse("image/*")  // add ImageFile
MediaType.parse("image/jpeg") // add JPEG ImageFile
MediaType.parse("text/plain") // add TextFile
MediaType.parse("application/zip")  // add ZipFile
MediaType.parse("application/json; charset=utf-8")


PHP 파일

<?php
    $file_path = "";
    $file_path = $file_path . basename( $_FILES['uploaded_file']['name']);
    if(move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $file_path)) {
        // 동일한 파일명이면 덮어쓰기를 한다.
        $result = array("result" => "success");
    } else{
        $result = array("result" => "error");
    }
    echo json_encode($result);
?>



=== OKHttp 라이브러리를 이용한 파일 전송 핵심코드 ===

final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/*");
String filename = ImagePath.substring(ImagePath.lastIndexOf("/") + 1);

RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("image", filename, RequestBody.create(MEDIA_TYPE_PNG, sourceFile))
        .addFormDataPart("result", "my_image")
        .addFormDataPart("username", name)
        .addFormDataPart("password", password)
        .addFormDataPart("email", email)
        .addFormDataPart("phone", phone)
        .build();

Request request = new Request.Builder()
        .url(BASE_URL + "signup")
        .post(requestBody)
        .build();

OkHttpClient client = new OkHttpClient();
okhttp3.Response response = client.newCall(request).execute();
res = response.body().string();

블로그 이미지

Link2Me

,
728x90

하나의 안드로이드 앱에서 다룰 수 있는 메모리의 한계 때문에 큰 사이즈 이미지는 OutofMemory exception이나 퍼포먼스 문제가 생길 수 있다. 작은 뷰의 크기에 맞지 않는 고해상도의 이미지를 불러오는 것은 괜한 리소스를 낭비하게 만든다.


=== BitMap.java ===

 import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.View;

public class BitMap extends View {
    public BitMap(Context context) {
        super(context);
    }

    protected void onDraw(Canvas canvas){
        canvas.drawColor(Color.CYAN);

        // Resource 폴더에 저장된 그림파일을 Bitmap 으로 만들어 리턴해준다
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.car1);
        // 이미지가 너무 크면 화면에서 잘려서 보임
        canvas.drawBitmap(bitmap,10,10,null); // 비트맵 이미지를 캔버스에 출력하는 함수
    }
}

 Bitmap myImage = BitmapFactory.decodeFile(“/sdcard/car1.jpg”);
 BitmapFactory.decodeFile() : 로컬에 존재하는 파일을 그대로 읽어올 때 쓴다.
 파일경로를 파라미터로 넘겨주면 FileInputStream 을 만들어서 decodeStream 을 한다.

 Canvas.drawBitmap() 은 비트맵 이미지를 캔버스에 출력하는 함수
 - 1번째 파라미터는 비트맵 이미지 객체, 2번째 파라미터는 원본 이미지의 영역좌표,
 - 3번째 파라미터는 이미지가 표시되는 화면상의 영역좌표, 4번째 파라미터는 Paint 객체
 - 2번째와 4번째파라미터는 생략 가능하다.

 === MainActivity.java ===

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);

        BitMap myView = new BitMap(this);
        setContentView(myView);
    }
}



https://ringsterz.wordpress.com/2014/11/27/imageview%EC%97%90-%EB%8C%80%EC%9A%A9%EB%9F%89-bitmap-%ED%9A%A8%EA%B3%BC%EC%A0%81%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EB%94%A9%ED%95%98%EA%B8%B0/

에 자동으로 줄이는 함수가 있어서 이걸 이용해서 테스트 해봤다.


BitmapFactory.Options : BitmapFactory 가 사용하는 옵션클래스이다.
Options 객체를 생성하고 설정하고자 하는 옵션을 넣은후 BitmapFactory 의 함수 실행시 파라미터로 넘기면된다.
inSampleSize : decode 시 얼마나 줄일지 설정하는 옵션인데 1보다 작을때는 1이 된다.
1보다 큰값일 때 1/N 만큼 이미지를 줄여서 decoding 하게 된다. 보통 2의 배수로 설정한다.


import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.View;

public class BitMap extends View {
    public BitMap(Context context) {
        super(context);
    }

    protected void onDraw(Canvas canvas){
        canvas.drawColor(Color.CYAN);

        // Resource 폴더에 저장된 그림파일을 Bitmap 으로 만들어 리턴해준다
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.car2);
        // 이미지가 너무 크면 화면에서 잘려서 보임
        canvas.drawBitmap(bitmap,10,10,null);

        Bitmap resize = resize_decodeResource(getResources(), R.drawable.car2, 100, 100);
        canvas.drawBitmap(resize,10,900,null); // 비트맵 이미지를 캔버스에 출력하는 함수
    }

    public static Bitmap resize_decodeResource(Resources res, int resId,

                                                                               int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}


이미지 출력 결과


Bitmap resize = resize_decodeResource(getResources(), R.drawable.car2, 100, 100);

에서 width 와 height 지정된 크기로 이미지가 줄어드는게 아니라 원래 이미지 크기가 width 와 height 중에서 작은 것 기준으로 맞춰서 줄여진다.


그래서 높이 기준으로 줄여주는 걸로 수정을 했다.

protected void onDraw(Canvas canvas){
    canvas.drawColor(Color.CYAN);

    Bitmap resize = autoresize_decodeResource(getResources(), R.drawable.car2, 100);
    canvas.drawBitmap(resize,10,300,null); // 비트맵 이미지를 캔버스에 출력하는 함수
}

public static Bitmap autoresize_decodeResource(Resources res, int resId, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInAutoSize(options, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

public static int calculateInAutoSize(BitmapFactory.Options options, int reqHeight) {
    // 원본 이미지의 높이와 너비
    final int height = options.outHeight;
    final int width = options.outWidth;

    float ratio = width / height;
    int reqWidth = Math.round((float) ratio * width);

    int inSampleSize = 1;
    if (height > reqHeight) {
        final int halfHeight = height / 2;
        while ((halfHeight / inSampleSize) > reqHeight) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
} 


블로그 이미지

Link2Me

,