반응형



안드로이드 갤러리에 있는 JPG 이미지 파일을 PC의 자바 서버 프로그램으로 전송하는 예제입니다.

최근들어 관련 질문이 많아져서 만들어봤습니다. 참고하세요.



기존 포스팅의 코드를 수정해서 사용해야 합니다. 우선 다음 포스팅을 해본 후 이번 포스팅을 이어서 진행하세요.



Android와 PC JAVA 프로그램 간 블루투스 통신 예제

http://webnautes.tistory.com/849




최초 작성 - 2018. 9. 13


실행 결과

1. 자바 서버 프로그램을  먼저 실행해둡니다.


[Thu Sep 13 13:01:05 KST 2018] Local Bluetooth device...

BlueCove version 2.1.1-SNAPSHOT on winsock
[Thu Sep 13 13:01:06 KST 2018] address: F8633F2710E0
[Thu Sep 13 13:01:06 KST 2018] name: WEBNAUTES-PC
[Thu Sep 13 13:01:06 KST 2018] Opened connection successful.
[Thu Sep 13 13:01:06 KST 2018] Server is now running.
[Thu Sep 13 13:01:06 KST 2018] wait for client requests...




2. 안드로이드 앱에서 연결합니다.





3. IMAGE 버튼을 선택합니다.




4. 갤러리에서 전송할 이미지를 선택합니다.





5. 전송이 완료되었습니다.





6. 서버에서도 전송결과를 확인할 수 있습니다.


[Thu Sep 13 13:02:07 KST 2018] Me : 에코 서버에 접속하셨습니다.
[Thu Sep 13 13:02:07 KST 2018] Me : 보내신 문자를 에코해드립니다.
[Thu Sep 13 13:02:07 KST 2018] ready
[Thu Sep 13 13:02:21 KST 2018] D013FDEE432E: [Start28874]
[Thu Sep 13 13:02:21 KST 2018] start recv image 28874
read 1011/28874  bytes.
read 2022/28874  bytes.
read 3033/28874  bytes.
read 4096/28874  bytes.
read 5107/28874  bytes.
read 6118/28874  bytes.
read 7129/28874  bytes.
read 8140/28874  bytes.
read 9151/28874  bytes.
read 10162/28874  bytes.
read 11173/28874  bytes.
read 12184/28874  bytes.
read 13195/28874  bytes.
read 14206/28874  bytes.
read 15217/28874  bytes.
read 16228/28874  bytes.
read 17239/28874  bytes.
read 18250/28874  bytes.
read 19261/28874  bytes.
read 20272/28874  bytes.
read 21283/28874  bytes.
read 22294/28874  bytes.
read 23305/28874  bytes.
read 24316/28874  bytes.
read 25327/28874  bytes.
read 26338/28874  bytes.
read 27349/28874  bytes.
read 28360/28874  bytes.
read 28874/28874  bytes.
[Thu Sep 13 13:02:21 KST 2018] end
[Thu Sep 13 13:02:21 KST 2018] Me : recv Image
[Thu Sep 13 13:02:21 KST 2018] ready




7. 서버 프로그램 폴더에서 전송받은 이미지를 확인할 수 있습니다.





안드로이드 코드 수정

갤러리에서 이미지 파일을 가져오는 코드와 런타임 퍼미션 코드를  추가합니다.


1. 메니페스트 파일에  다음 권한을 추가합니다.


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




2. build.gradle에 com.android.support:design를 추가합니다.  퍼미션 관련 코드에서 Snackbar를 사용하기 위해 필요합니다.


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'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
   implementation 'com.android.support:design:27.1.1'
}




3. 레이아웃 파일 activity_main.xml에 이미지 전송을 위한 버튼을 추가합니다. Snackbar를 사용하기 위해 레이아웃에 id도 추가해야 합니다.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="vertical"
   android:paddingBottom="10dp"
   android:paddingTop="10dp"
   android:id="@+id/layout_main"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

   <LinearLayout
       android:weightSum="1"
       android:orientation="horizontal"
       android:layout_width="match_parent"
       android:layout_height="wrap_content">

       <EditText
           android:id="@+id/input_string_edittext"
           android:hint="input text here"
           android:layout_weight="0.6"
           android:layout_width="0dp"
           android:layout_height="wrap_content"/>

       <Button
           android:id="@+id/send_button"
           android:layout_weight="0.2"
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:text="Send" />

       <Button
           android:id="@+id/send_image_button"
           android:layout_weight="0.2"
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:text="Image" />
   </LinearLayout>

  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .





4. MainActivity.java 파일에 갤러리로부터 파일을 가져와 전송하는 코드를 추가합니다.


public class MainActivity extends AppCompatActivity
       implements ActivityCompat.OnRequestPermissionsResultCallback {

   private final int REQUEST_BLUETOOTH_ENABLE = 100;
   private final int GET_GALLERY_IMAGE = 200;


   private View mLayout;  // Snackbar 사용하기 위해서는 View가 필요합니다.

   private TextView mConnectionStatus;



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

       mLayout = findViewById(R.id.layout_main);




       Button sendButton = (Button)findViewById(R.id.send_button);
       sendButton.setOnClickListener(new View.OnClickListener(){
           public void onClick(View v){
               String sendMessage = mInputEditText.getText().toString();
               if ( sendMessage.length() > 0 ) {
                   sendMessage(sendMessage);
               }
           }
       });

       Button sendImageButton = (Button)findViewById(R.id.send_image_button);
       sendImageButton.setOnClickListener(new View.OnClickListener(){
           public void onClick(View v){
               Intent intent = new Intent(Intent.ACTION_PICK);
               intent.setData(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
               intent.setType("image/*");
               startActivityForResult(intent, GET_GALLERY_IMAGE);

           }
       });

       
       mConnectionStatus = (TextView)findViewById(R.id.connection_status_textview);



   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {

       if(requestCode == REQUEST_BLUETOOTH_ENABLE){
           if (resultCode == RESULT_OK){
               //BlueTooth is now Enabled
               showPairedDevicesListDialog();
           }
           if(resultCode == RESULT_CANCELED){
               showQuitDialog( "You need to enable bluetooth");
           }
       }else if ( requestCode == GET_GALLERY_IMAGE){
           sendPicture(data.getData());

       }
   }



   private void sendPicture(Uri imgUri) {

       String imagePath = getRealPathFromURI(imgUri);

       Log.e(TAG, imagePath);

       File file = new File(imagePath);
       sendMessage("Start"+file.length());


       try
       {
           FileInputStream fis = new FileInputStream(imagePath);

           byte[] buffer = new byte[4096];

           int totalSize = fis.available();
           int readSize = 0;
           while ( (readSize=fis.read(buffer)) > 0) {

               mConnectedTask.mOutputStream.write(buffer, 0, readSize);
           }

           fis.close();
;
       }
       catch (Exception e)
       {
           e.printStackTrace();
       }

       mConversationArrayAdapter.insert("Me:  " + "Send Image" , 0);


   }


   private String getRealPathFromURI(Uri contentUri) {

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

       return cursor.getString(column_index);
   }





5.  안드로이드 6.0 이상의 경우 이미지에 접근하기 위해 런타임 퍼미션도 필요합니다. 코드 설명은 다음 포스팅을 참고하세요.



안드로이드 런타임 퍼미션(Runtime Permission) 예제

http://webnautes.tistory.com/1225




      Button sendImageButton = (Button)findViewById(R.id.send_image_button);
       sendImageButton.setOnClickListener(new View.OnClickListener(){
           public void onClick(View v){
               Intent intent = new Intent(Intent.ACTION_PICK);
               intent.setData(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
               intent.setType("image/*");
               startActivityForResult(intent, GET_GALLERY_IMAGE);

           }
       });


       int writeExternalStoragePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
       if ( writeExternalStoragePermission != PackageManager.PERMISSION_GRANTED){
           
           if (ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[0])) {

              
               Snackbar.make(mLayout, "이 앱을 실행하려면 카메라와 외부 저장소 접근 권한이 필요합니다.",
                       Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() {

                   @Override
                   public void onClick(View view) {

                       ActivityCompat.requestPermissions( MainActivity.this, REQUIRED_PERMISSIONS,
                               PERMISSIONS_REQUEST_CODE);
                   }
               }).show();


           } else {

               ActivityCompat.requestPermissions( this, REQUIRED_PERMISSIONS,
                       PERMISSIONS_REQUEST_CODE);
           }

       }


       mConnectionStatus = (TextView)findViewById(R.id.connection_status_textview);



           else {

           Log.d(TAG, "Initialisation successful.");


           showPairedDevicesListDialog();

       }

   }



   private static final int PERMISSIONS_REQUEST_CODE = 100;
   String[] REQUIRED_PERMISSIONS  = {Manifest.permission.WRITE_EXTERNAL_STORAGE};

   @Override
   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grandResults) {

       if ( requestCode == PERMISSIONS_REQUEST_CODE && grandResults.length == REQUIRED_PERMISSIONS.length) {
  

           boolean check_result = true;



           for (int result : grandResults) {
               if (result != PackageManager.PERMISSION_GRANTED) {
                   check_result = false;
                   break;
               }
           }



           if ( check_result ) {
               ;
           }
           else {
         
               if (ActivityCompat.shouldShowRequestPermissionRationale(this, REQUIRED_PERMISSIONS[0])) {



                   Snackbar.make(mLayout, "퍼미션이 거부되었습니다. 앱을 다시 실행하여 퍼미션을 허용해주세요. ",
                           Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() {

                       @Override
                       public void onClick(View view) {

                           finish();
                       }
                   }).show();

               }else {


                   Snackbar.make(mLayout, "퍼미션이 거부되었습니다. 설정(앱 정보)에서 퍼미션을 허용해야 합니다. ",
                           Snackbar.LENGTH_INDEFINITE).setAction("확인", new View.OnClickListener() {

                       @Override
                       public void onClick(View view) {

                           finish();
                       }
                   }).show();
               }
           }

       }


   }


   @Override
   protected void onDestroy() {




자바 전체 코드

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Date;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;


public class Server{
 
   public static void main(String[] args){
   
   
log("Local Bluetooth device...\n");
       
    LocalDevice local = null;
try {

local = LocalDevice.getLocalDevice();
} catch (BluetoothStateException e2) {

}   

    log( "address: " + local.getBluetoothAddress() );
    log( "name: " + local.getFriendlyName() );
   
   
    Runnable r = new ServerRunable();
    Thread thread = new Thread(r);
    thread.start();
   
   }
   
     
   private static void log(String msg) {  
   
       System.out.println("["+(new Date()) + "] " + msg);  
   }

}


class ServerRunable implements Runnable{
 
//UUID for SPP
final UUID uuid = new UUID("0000110100001000800000805F9B34FB", false);
   final String CONNECTION_URL_FOR_SPP = "btspp://localhost:"
    + uuid +";name=SPP Server";
 
   private StreamConnectionNotifier mStreamConnectionNotifier = null;  
   private StreamConnection mStreamConnection = null;
   private int count = 0;
   private boolean recvImage = false;
  
   
@Override
public void run() {

    try {
   
mStreamConnectionNotifier = (StreamConnectionNotifier) Connector
.open(CONNECTION_URL_FOR_SPP);

log("Opened connection successful.");
} catch (IOException e) {

log("Could not open connection: " + e.getMessage());
return;
}
  

    log("Server is now running.");

   
   
       while(true){
       
        log("wait for client requests...");

try {

mStreamConnection = mStreamConnectionNotifier.acceptAndOpen();
} catch (IOException e1) {

log("Could not open connection: " + e1.getMessage() );
}

       
count++;
log("현재 접속 중인 클라이언트 수: " + count);


       new Receiver(mStreamConnection).start();
       }

}

       
   
   class Receiver extends Thread {
   
    private InputStream mInputStream = null;
       private OutputStream mOutputStream = null;
       private String mRemoteDeviceString = null;
       private StreamConnection mStreamConnection = null;
       
       
       Receiver(StreamConnection streamConnection){
       
        mStreamConnection = streamConnection;

try {
   
mInputStream = mStreamConnection.openInputStream();
mOutputStream = mStreamConnection.openOutputStream();

log("Open streams...");
} catch (IOException e) {

log("Couldn't open Stream: " + e.getMessage());

Thread.currentThread().interrupt();
return;
}


try {
       
RemoteDevice remoteDevice
= RemoteDevice.getRemoteDevice(mStreamConnection);

       mRemoteDeviceString = remoteDevice.getBluetoothAddress();
       
log("Remote device");
log("address: "+ mRemoteDeviceString);
       
} catch (IOException e1) {

log("Found device, but couldn't connect to it: " + e1.getMessage());
return;
}

log("Client is connected...");
       }
       
       
     
       
       
    @Override
public void run() {
   
try {

    Reader mReader = new BufferedReader(new InputStreamReader
        ( mInputStream, Charset.forName(StandardCharsets.UTF_8.name())));

    boolean isDisconnected = false;
    StringBuilder imageStringBuilder = null;
   
Sender("에코 서버에 접속하셨습니다.");
Sender( "보내신 문자를 에코해드립니다.");
   
while(true){

log("ready");

       
           StringBuilder stringBuilder = new StringBuilder();
           int c = 0;
           
           
while ( '\n' != (char)( c = mReader.read()) ) {

if ( c == -1){

log("Client has been disconnected");

count--;
log("현재 접속 중인 클라이언트 수: " + count);

isDisconnected = true;
Thread.currentThread().interrupt();

break;
}

stringBuilder.append((char) c);
}

           if ( isDisconnected ) break;
           
           String recvMessage = stringBuilder.toString();
       log( mRemoteDeviceString + ": [" + recvMessage +"]" );
       
       if (recvMessage.matches("Start.*")) {
        recvImage = true;

        log("start recv image "+ recvMessage.substring(5));
        int size = Integer.parseInt(recvMessage.substring(5));
       
    DataInputStream dis = new DataInputStream(mInputStream);
    FileOutputStream fos = new FileOutputStream("testfile.jpg");
    byte[] buffer = new byte[4096];
   
    int read = 0;
    int totalRead = 0;

    while((read = dis.read(buffer)) > 0) {
    totalRead += read;

    System.out.println("read " + totalRead + "/" + size + "  bytes.");
   
    fos.write(buffer, 0, read);
   
    if ( totalRead >= size) break;
    }
   
    log("end");
   
    fos.flush();
    fos.close();
   
    recvImage = false;
    recvMessage = "recv Image";
       }
       


       if (recvImage == false )
        Sender(recvMessage);

}

} catch (IOException e) {

log("Receiver closed" + e.getMessage());
}
}
   

    void Sender(String msg){
       
           PrintWriter printWriter = new PrintWriter(new BufferedWriter
            (new OutputStreamWriter(mOutputStream,
            Charset.forName(StandardCharsets.UTF_8.name()))));
       
    printWriter.write(msg+"\n");
    printWriter.flush();
   
    log( "Me : " + msg );
    }
}
   
   
   private static void log(String msg) {  
   
       System.out.println("["+(new Date()) + "] " + msg);  
   }
       
}  





반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. ma 2018.09.17 23:03

    좋은 정보 감사합니다. 어플을 다 만들어서 보내는데 겔러리에서 사진 선택까지는 되는데 선택 후 파일 보내지는 처리가 아무런 변화가 없습니다... 조언좀 해주세요

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.09.17 23:29 신고

      양쪽에서 메시지 변화는 있나요?

    • ma 2018.09.17 23:53

      변화 없습니다..

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.09.17 23:54 신고

      메시지 전송은 되었나요?

    • ma 2018.09.17 23:59

      메세지 전송은 됩니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.09.18 00:01 신고

      혹시 테스트한 안드로이드 버전이 어떻게 되나요?

    • ma 2018.09.18 00:01

      최신버전입니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.09.18 00:05 신고

      외장 저장소에서 이미지읽어오는 권한문제 같은데.. 정확히는 로그캣에서 어떤 에러가 있는지 봐야할듯 합니다.

  2. nam 2018.09.18 12:14

    어플 예제를 따라 하여 잘되어서 좋습니다 ㅎㅎ 근데
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setData(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    intent.setType("image/*");
    startActivityForResult(intent, GET_GALLERY_IMAGE);

    이부분이 버튼이 눌리면 이 함수가 겔러리를 호출하는걸 알겠습니다
    겔러리에서 호출말고 이부분을 경로에 있는 걸 호출 하고 싶은데 조언 부탁드립니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2018.09.18 12:21 신고

      버튼 누르면 파일 경로를 인자로 sendPicture함수를 호출하도록 하세요.

      sendPicture 함수 수정이 필요할수도 있습니다.

  3. 발빠른감자 2019.07.21 01:25

    안녕하세요 글 정말 잘봤습니다!

    혹시 궁금한 점이 있는데 안드로이드에서 블루투스 없이 동영상을 파이썬 서버로 보내려면 어떻게 해야할지 정말 궁금합니다.

    파이썬 서버에서 딥러닝알고리즘을 돌리려고 합니다!

    블로그 즐겨찾기하고 잘보겠습니다!

    감사합니다!

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.21 09:35 신고

      간단하게..

      소켓을 사용하여 이미지 한장씩 안드로이드에서 파이썬 서버로 전송하면 될듯합니다.

      연속적으로 이미지를 보내고 받는 쪽에서 연속적으로 수신된 이미지를 보여주면 동영상처럼 보입니다.



    • 발빠른동전 2019.07.21 17:04

      답변 정말 감사드립니다.

      바로 시도해보겠습니다!

    • 궁금 2020.06.18 12:41

      소켓통신?을 이용하면 안드로이드 폰에서 파이썬 서버로 이미지를 바로 전송하는게 가능한건가요?? 자료가 많이 없어서 가능한 방법인지 궁금합니다 ㅠㅠ

  4. 아부츠 2019.09.22 20:00

    안녕하세요 설명을 친절하게 해주셔서 고민이 많았던 부분을 해결했습니다!
    감사합니다 ㅎㅎ

    그런데 안드로이드에서 서버가 아닌 반대방향
    즉, 서버에서 안드로이드로 보내는 것도 가능한건가요?

  5. wha_D 2019.12.13 16:44

    이미지를 선택하면 어플이 종료되고 서버측에서도 온갖문자만 나오고 전송이 완료되지 않는데 혹시 무슨문제인지 알 수 있을까요? 안스를 처음써봐서 많이 어렵네요 ㅠㅠ

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.12.15 22:17 신고

      다른 이미지를 선택해서 해보세요. 원인을 찾으려면 로그캣의 에러메시지를 보는 수 밖에 없습니다.

  6. edw 2020.02.13 10:12

    혹시 스마트폰 - pc 말구 스마트폰 - 스마트폰 간의 이미지 전송도 될까요?
    단순 메시지 전송은 되는데 이미지를 전송하려나 감이 잘 안잡히네요..

  7. ㅈㅇㅅ 2020.03.10 21:50

    늘 감사합니다. 서버로 전송된 이미지를 다른 클라이언트(안드로이드)에서 볼 수 있게끔 기능을 작동시켜주는 예제도 있나요?

  8. 2020.03.11 01:21

    블루투스없이 와이파이만을 사용해서 안드로이드의 갤러리에 있는 이미지를 PC JAVA 서버로 보내는것과 관련된 글도 혹시 쓰셨나요?

  9. hi 2020.09.24 17:51

    mConnectedTask.mOutputStream.write(buffer, 0, readSize);
    이 부분이 안되는데
    mConnectedTask 클래스를 보여주시면 안될까요?
    대충 그 클래스랑 비슷하게 구현해 봤는데 그 부분에서 안되네요.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.09.24 20:52 신고

      다음 포스팅 코드를 수정하여 진행한 코드입니다.

      https://webnautes.tistory.com/849

    • BlogIcon hi 2020.09.25 13:16

      감사합니다

+ Recent posts