안드로이드 갤러리에 있는 JPG 이미지 파일을 PC의 자바 서버 프로그램으로 전송하는 예제입니다.
최근들어 관련 질문이 많아져서 만들어봤습니다. 참고하세요.
기존 포스팅의 코드를 수정해서 사용해야 합니다. 우선 다음 포스팅을 해본 후 이번 포스팅을 이어서 진행하세요.
최초 작성 - 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 이상의 경우 이미지에 접근하기 위해 런타임 퍼미션도 필요합니다. 코드 설명은 다음 포스팅을 참고하세요.
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); } }
|