반응형
🔗 Google Nearby Connections API 완전 정복 가이드
오늘날 모바일 애플리케이션에서 기기 간 통신은 점점 중요해지고 있습니다. Google의 Nearby Connections API는 인터넷 연결 없이도 가까운 거리의 기기들 간에 안전하고 빠른 데이터 통신을 가능하게 하는 강력한 도구입니다.

📱 Nearby Connections API란?
Nearby Connections API는 Google이 제공하는 크로스 플랫폼 API로, 다음과 같은 특징을 가집니다:
- 오프라인 통신: 인터넷 연결 없이 기기 간 직접 통신
- 다중 프로토콜 지원: Bluetooth, WiFi Direct, WiFi LAN 자동 선택
- 높은 보안성: 모든 통신은 암호화되어 전송
- 크로스 플랫폼: Android와 iOS 모두 지원
- 쉬운 구현: 복잡한 네트워크 설정 없이 간단한 API 호출로 구현
💡 실생활 활용 사례
- 멀티플레이어 게임 (오프라인 대전)
- 파일 공유 앱
- 협업 도구 (화이트보드, 프레젠테이션)
- IoT 기기 제어
- 비상 상황 통신 시스템
- 멀티플레이어 게임 (오프라인 대전)
- 파일 공유 앱
- 협업 도구 (화이트보드, 프레젠테이션)
- IoT 기기 제어
- 비상 상황 통신 시스템
🏗️ 기본 구조 및 개념
1. 주요 역할
- Advertiser (광고자): 다른 기기가 발견할 수 있도록 자신을 광고하는 기기
- Discoverer (발견자): 근처의 광고 중인 기기를 찾는 기기
🔄 동작 과정
1. Advertiser가 서비스를 광고 시작
2. Discoverer가 근처 기기를 탐색
3. 기기 발견 및 연결 요청
4. 연결 승인 후 데이터 송수신
5. 연결 종료
1. Advertiser가 서비스를 광고 시작
2. Discoverer가 근처 기기를 탐색
3. 기기 발견 및 연결 요청
4. 연결 승인 후 데이터 송수신
5. 연결 종료
⚙️ 프로젝트 설정
build.gradle (Module: app) 설정
// Nearby Connections API 의존성 추가
implementation 'com.google.android.gms:play-services-nearby:19.0.0'
android {
compileSdk 34
defaultConfig {
minSdk 21 // Nearby API 최소 요구사항
targetSdk 34
}
}
AndroidManifest.xml 권한 설정
<!-- 필수 권한들 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 12 이상을 위한 새로운 권한 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
⚠️ 중요한 주의사항
Android 6.0 (API 23) 이상에서는 런타임 권한 요청이 필수입니다. 특히 위치 권한은 Bluetooth와 WiFi 스캔에 필요합니다.
Android 6.0 (API 23) 이상에서는 런타임 권한 요청이 필수입니다. 특히 위치 권한은 Bluetooth와 WiFi 스캔에 필요합니다.
🚀 기본 구현 예제
1. MainActivity - 기본 설정 및 권한 처리
public class MainActivity extends AppCompatActivity {
private static final String TAG = "NearbyConnections";
// 서비스 ID - 같은 앱끼리 통신하기 위한 고유 식별자
private static final String SERVICE_ID = "com.yourapp.nearbyconnections";
// 연결 상태를 추적하기 위한 변수들
private String connectedEndpointId = "";
private boolean isAdvertising = false;
private boolean isDiscovering = false;
// UI 컴포넌트들
private Button btnStartAdvertising, btnStartDiscovery, btnSendMessage;
private TextView tvStatus, tvMessages;
private EditText etMessage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// UI 컴포넌트 초기화
initializeViews();
// 권한 요청
requestNecessaryPermissions();
// 버튼 클릭 리스너 설정
setupClickListeners();
}
/**
* UI 컴포넌트들을 초기화하는 메소드
*/
private void initializeViews() {
btnStartAdvertising = findViewById(R.id.btnStartAdvertising);
btnStartDiscovery = findViewById(R.id.btnStartDiscovery);
btnSendMessage = findViewById(R.id.btnSendMessage);
tvStatus = findViewById(R.id.tvStatus);
tvMessages = findViewById(R.id.tvMessages);
etMessage = findViewById(R.id.etMessage);
// 초기 상태 설정
updateUI();
}
/**
* 필요한 권한들을 요청하는 메소드
*/
private void requestNecessaryPermissions() {
String[] permissions = {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
};
// Android 12 이상에서 추가 권한 확인
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
permissions = new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.BLUETOOTH_ADVERTISE,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.NEARBY_WIFI_DEVICES
};
}
// 권한이 없는 경우 요청
ActivityCompat.requestPermissions(this, permissions, 1001);
}
2. 광고 시작 (Advertiser 역할)
/**
* 다른 기기들이 이 기기를 발견할 수 있도록 광고를 시작하는 메소드
*/
private void startAdvertising() {
// 광고 옵션 설정
AdvertisingOptions advertisingOptions = new AdvertisingOptions.Builder()
.setStrategy(Strategy.P2P_CLUSTER) // 1:N 연결 전략
.build();
// 연결된 기기의 이름 (다른 기기에서 보여질 이름)
String localEndpointName = Build.MODEL; // 기기 모델명 사용
Nearby.getConnectionsClient(this)
.startAdvertising(
localEndpointName, // 광고할 기기 이름
SERVICE_ID, // 서비스 ID
connectionLifecycleCallback, // 연결 생명주기 콜백
advertisingOptions // 광고 옵션
)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
// 광고 시작 성공
isAdvertising = true;
updateStatus("광고 시작됨 - 다른 기기에서 발견 가능");
updateUI();
Log.d(TAG, "광고 시작 성공");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// 광고 시작 실패
updateStatus("광고 시작 실패: " + e.getMessage());
Log.e(TAG, "광고 시작 실패", e);
}
});
}
3. 기기 탐색 시작 (Discoverer 역할)
/**
* 근처의 광고 중인 기기들을 탐색하는 메소드
*/
private void startDiscovery() {
// 탐색 옵션 설정
DiscoveryOptions discoveryOptions = new DiscoveryOptions.Builder()
.setStrategy(Strategy.P2P_CLUSTER) // 광고와 동일한 전략 사용
.build();
Nearby.getConnectionsClient(this)
.startDiscovery(
SERVICE_ID, // 찾을 서비스 ID
endpointDiscoveryCallback, // 기기 발견 콜백
discoveryOptions // 탐색 옵션
)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
// 탐색 시작 성공
isDiscovering = true;
updateStatus("탐색 시작됨 - 근처 기기 검색 중...");
updateUI();
Log.d(TAG, "탐색 시작 성공");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// 탐색 시작 실패
updateStatus("탐색 시작 실패: " + e.getMessage());
Log.e(TAG, "탐색 시작 실패", e);
}
});
}
4. 기기 발견 콜백
/**
* 기기 발견 시 호출되는 콜백
*/
private final EndpointDiscoveryCallback endpointDiscoveryCallback =
new EndpointDiscoveryCallback() {
@Override
public void onEndpointFound(@NonNull String endpointId,
@NonNull DiscoveredEndpointInfo info) {
// 새로운 기기 발견!
Log.d(TAG, "기기 발견: " + info.getEndpointName());
updateStatus("기기 발견: " + info.getEndpointName() + " - 연결 시도 중...");
// 자동으로 연결 요청 보내기
requestConnection(endpointId, info.getEndpointName());
}
@Override
public void onEndpointLost(@NonNull String endpointId) {
// 기기 연결 끊어짐
Log.d(TAG, "기기 연결 끊어짐: " + endpointId);
updateStatus("기기 연결이 끊어졌습니다.");
}
};
/**
* 발견된 기기에 연결을 요청하는 메소드
*/
private void requestConnection(String endpointId, String endpointName) {
Nearby.getConnectionsClient(this)
.requestConnection(
Build.MODEL, // 내 기기 이름
endpointId, // 연결할 기기 ID
connectionLifecycleCallback // 연결 생명주기 콜백
)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
// 연결 요청 전송 성공
Log.d(TAG, "연결 요청 전송됨: " + endpointName);
updateStatus("연결 요청 전송됨: " + endpointName);
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// 연결 요청 실패
Log.e(TAG, "연결 요청 실패", e);
updateStatus("연결 요청 실패: " + e.getMessage());
}
});
}
5. 연결 생명주기 관리
/**
* 연결의 생명주기를 관리하는 콜백
*/
private final ConnectionLifecycleCallback connectionLifecycleCallback =
new ConnectionLifecycleCallback() {
@Override
public void onConnectionInitiated(@NonNull String endpointId,
@NonNull ConnectionInfo connectionInfo) {
// 연결이 시작됨 - 사용자에게 승인/거절 선택권 제공
Log.d(TAG, "연결 요청 받음: " + connectionInfo.getEndpointName());
// 자동으로 연결 승인 (실제 앱에서는 사용자 확인 받는 것이 좋음)
Nearby.getConnectionsClient(MainActivity.this)
.acceptConnection(endpointId, payloadCallback);
updateStatus("연결 승인됨: " + connectionInfo.getEndpointName());
}
@Override
public void onConnectionResult(@NonNull String endpointId,
@NonNull ConnectionResolution result) {
switch (result.getStatus().getStatusCode()) {
case ConnectionsStatusCodes.STATUS_OK:
// 연결 성공!
Log.d(TAG, "연결 성공: " + endpointId);
connectedEndpointId = endpointId;
updateStatus("연결 완료! 메시지를 보낼 수 있습니다.");
// 탐색과 광고 중지 (1:1 연결이므로)
stopDiscovery();
stopAdvertising();
updateUI();
break;
case ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED:
// 연결 거절됨
Log.d(TAG, "연결 거절됨: " + endpointId);
updateStatus("연결이 거절되었습니다.");
break;
default:
// 기타 연결 실패
Log.d(TAG, "연결 실패: " + endpointId);
updateStatus("연결에 실패했습니다.");
break;
}
}
@Override
public void onDisconnected(@NonNull String endpointId) {
// 연결 끊어짐
Log.d(TAG, "연결 끊어짐: " + endpointId);
connectedEndpointId = "";
updateStatus("연결이 끊어졌습니다.");
updateUI();
}
};
6. 데이터 송수신
/**
* 메시지를 전송하는 메소드
*/
private void sendMessage(String message) {
if (connectedEndpointId.isEmpty()) {
updateStatus("연결된 기기가 없습니다.");
return;
}
// 텍스트 메시지를 바이트 배열로 변환
Payload bytesPayload = Payload.fromBytes(message.getBytes());
// 메시지 전송
Nearby.getConnectionsClient(this)
.sendPayload(connectedEndpointId, bytesPayload)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void unused) {
// 전송 성공
Log.d(TAG, "메시지 전송 성공: " + message);
appendMessage("나: " + message);
etMessage.setText(""); // 입력 필드 초기화
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// 전송 실패
Log.e(TAG, "메시지 전송 실패", e);
updateStatus("메시지 전송 실패: " + e.getMessage());
}
});
}
/**
* 데이터 수신을 처리하는 콜백
*/
private final PayloadCallback payloadCallback = new PayloadCallback() {
@Override
public void onPayloadReceived(@NonNull String endpointId, @NonNull Payload payload) {
// 데이터 수신됨
if (payload.getType() == Payload.Type.BYTES) {
// 바이트 데이터인 경우 (텍스트 메시지)
String receivedMessage = new String(payload.asBytes());
Log.d(TAG, "메시지 수신: " + receivedMessage);
// UI 업데이트는 메인 스레드에서 실행
runOnUiThread(() -> {
appendMessage("상대방: " + receivedMessage);
});
}
}
@Override
public void onPayloadTransferUpdate(@NonNull String endpointId,
@NonNull PayloadTransferUpdate update) {
// 전송 상태 업데이트 (파일 전송 시 진행률 표시 등에 사용)
if (update.getStatus() == PayloadTransferUpdate.Status.SUCCESS) {
Log.d(TAG, "페이로드 전송 완료");
} else if (update.getStatus() == PayloadTransferUpdate.Status.FAILURE) {
Log.e(TAG, "페이로드 전송 실패");
}
}
};
7. UI 업데이트 및 정리 메소드
/**
* UI 상태를 업데이트하는 메소드
*/
private void updateUI() {
runOnUiThread(() -> {
// 연결 상태에 따라 버튼 활성화/비활성화
btnSendMessage.setEnabled(!connectedEndpointId.isEmpty());
btnStartAdvertising.setEnabled(!isAdvertising);
btnStartDiscovery.setEnabled(!isDiscovering);
});
}
/**
* 상태 메시지를 업데이트하는 메소드
*/
private void updateStatus(String message) {
runOnUiThread(() -> {
tvStatus.setText(message);
});
}
/**
* 메시지를 채팅창에 추가하는 메소드
*/
private void appendMessage(String message) {
runOnUiThread(() -> {
tvMessages.append(message + "\n");
});
}
/**
* 탐색을 중지하는 메소드
*/
private void stopDiscovery() {
if (isDiscovering) {
Nearby.getConnectionsClient(this).stopDiscovery();
isDiscovering = false;
Log.d(TAG, "탐색 중지됨");
}
}
/**
* 광고를 중지하는 메소드
*/
private void stopAdvertising() {
if (isAdvertising) {
Nearby.getConnectionsClient(this).stopAdvertising();
isAdvertising = false;
Log.d(TAG, "광고 중지됨");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 앱 종료 시 모든 연결 해제 및 서비스 중지
Nearby.getConnectionsClient(this).disconnectFromAllEndpoints();
stopDiscovery();
stopAdvertising();
}
📋 레이아웃 파일 (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<!-- 상태 표시 -->
<TextView
android:id="@+id/tvStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="준비됨"
android:textSize="16sp"
android:padding="8dp"
android:background="#E3F2FD"
android:textColor="#1976D2" />
<!-- 제어 버튼들 -->
<Button
android:id="@+id/btnStartAdvertising"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="광고 시작 (다른 기기에서 발견 가능)"
android:layout_marginTop="16dp" />
<Button
android:id="@+id/btnStartDiscovery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="기기 탐색 시작"
android:layout_marginTop="8dp" />
<!-- 메시지 입력 -->
<EditText
android:id="@+id/etMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="보낼 메시지를 입력하세요"
android:layout_marginTop="16dp" />
<Button
android:id="@+id/btnSendMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="메시지 전송"
android:enabled="false"
android:layout_marginTop="8dp" />
<!-- 메시지 표시 -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/tvMessages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="메시지가 여기에 표시됩니다.\n"
android:textSize="14sp"
android:padding="8dp"
android:background="#F5F5F5" />
</ScrollView>
</LinearLayout>
🎯 고급 기능 및 팁
1. 파일 전송
/**
* 파일을 전송하는 메소드
*/
private void sendFile(Uri fileUri) {
try {
// 파일에서 Payload 생성
Payload filePayload = Payload.fromFile(fileUri);
// 파일 정보를 먼저 전송 (파일명, 크기 등)
String fileInfo = "FILE:" + getFileName(fileUri) + ":" + getFileSize(fileUri);
Payload infoPayload = Payload.fromBytes(fileInfo.getBytes());
// 정보 먼저 전송
Nearby.getConnectionsClient(this)
.sendPayload(connectedEndpointId, infoPayload);
// 그 다음 파일 전송
Nearby.getConnectionsClient(this)
.sendPayload(connectedEndpointId, filePayload);
} catch (Exception e) {
Log.e(TAG, "파일 전송 실패", e);
}
}
2. 연결 전략 선택
📊 연결 전략 비교
P2P_CLUSTER: 1:N 연결, 많은 기기와 동시 연결
P2P_STAR: 1:1 연결, 안정적이고 빠른 속도
P2P_POINT_TO_POINT: 1:1 전용, 최고 성능
P2P_CLUSTER: 1:N 연결, 많은 기기와 동시 연결
P2P_STAR: 1:1 연결, 안정적이고 빠른 속도
P2P_POINT_TO_POINT: 1:1 전용, 최고 성능
3. 에러 처리 및 디버깅
/**
* 상세한 에러 처리를 포함한 연결 메소드
*/
private void connectWithErrorHandling(String endpointId) {
Nearby.getConnectionsClient(this)
.requestConnection(Build.MODEL, endpointId, connectionLifecycleCallback)
.addOnFailureListener(exception -> {
// 구체적인 에러 타입별 처리
if (exception instanceof ApiException) {
ApiException apiException = (ApiException) exception;
switch (apiException.getStatusCode()) {
case ConnectionsStatusCodes.STATUS_ENDPOINT_UNKNOWN:
updateStatus("기기를 찾을 수 없습니다.");
break;
case ConnectionsStatusCodes.STATUS_NETWORK_NOT_CONNECTED:
updateStatus("네트워크에 연결되지 않았습니다.");
break;
case ConnectionsStatusCodes.STATUS_BLUETOOTH_ERROR:
updateStatus("블루투스 오류가 발생했습니다.");
break;
default:
updateStatus("연결 오류: " + apiException.getStatusCode());
break;
}
}
});
}
4. 보안 강화
/**
* 연결 시 보안 인증을 추가하는 예제
*/
private void secureConnectionHandling(String endpointId, ConnectionInfo info) {
// 연결 토큰 검증 (실제 앱에서는 더 복잡한 인증 로직 구현)
String authToken = info.getAuthenticationDigits();
// 사용자에게 인증 토큰 확인 요청
new AlertDialog.Builder(this)
.setTitle("연결 확인")
.setMessage("다음 기기와 연결하시겠습니까?\n" +
"기기명: " + info.getEndpointName() + "\n" +
"인증 코드: " + authToken)
.setPositiveButton("승인", (dialog, which) -> {
// 연결 승인
Nearby.getConnectionsClient(this)
.acceptConnection(endpointId, payloadCallback);
})
.setNegativeButton("거절", (dialog, which) -> {
// 연결 거절
Nearby.getConnectionsClient(this)
.rejectConnection(endpointId);
})
.show();
}
🚀 성능 최적화 팁
1. 배터리 최적화
/**
* 배터리 효율적인 설정
*/
private void optimizedAdvertising() {
AdvertisingOptions options = new AdvertisingOptions.Builder()
.setStrategy(Strategy.P2P_CLUSTER)
// 저전력 모드 사용
.setLowPower(true)
.build();
// 일정 시간 후 자동으로 광고 중지
Handler handler = new Handler();
handler.postDelayed(() -> {
stopAdvertising();
updateStatus("배터리 절약을 위해 광고가 중지되었습니다.");
}, 60000); // 1분 후 중지
}
2. 연결 품질 모니터링
/**
* 연결 품질을 모니터링하는 클래스
*/
public class ConnectionQualityMonitor {
private long lastMessageTime = 0;
private int failedMessages = 0;
private static final long TIMEOUT_MS = 5000; // 5초 타임아웃
public void onMessageSent() {
lastMessageTime = System.currentTimeMillis();
}
public void onMessageFailed() {
failedMessages++;
if (failedMessages > 3) {
// 연결 품질이 나쁘다고 판단, 재연결 시도
reconnectWithBetterStrategy();
}
}
private void reconnectWithBetterStrategy() {
// 더 안정적인 전략으로 재연결
// 예: P2P_STAR 전략 사용
}
}
🔧 문제해결 가이드
⚠️ 자주 발생하는 문제들
1. 기기를 발견하지 못하는 경우:
- 위치 권한이 허용되었는지 확인
- 블루투스와 WiFi가 켜져있는지 확인
- 같은 SERVICE_ID를 사용하는지 확인
2. 연결이 자주 끊어지는 경우:
- 기기 간 거리가 너무 멀지 않은지 확인
- 다른 무선 신호 간섭이 없는지 확인
- 적절한 연결 전략을 선택했는지 확인
3. 메시지가 전송되지 않는 경우:
- 연결 상태를 확인
- 페이로드 크기 제한 확인 (최대 32KB)
- 네트워크 상태 확인
1. 기기를 발견하지 못하는 경우:
- 위치 권한이 허용되었는지 확인
- 블루투스와 WiFi가 켜져있는지 확인
- 같은 SERVICE_ID를 사용하는지 확인
2. 연결이 자주 끊어지는 경우:
- 기기 간 거리가 너무 멀지 않은지 확인
- 다른 무선 신호 간섭이 없는지 확인
- 적절한 연결 전략을 선택했는지 확인
3. 메시지가 전송되지 않는 경우:
- 연결 상태를 확인
- 페이로드 크기 제한 확인 (최대 32KB)
- 네트워크 상태 확인
디버깅을 위한 로그 설정
/**
* 상세한 로깅을 위한 헬퍼 클래스
*/
public class NearbyLogger {
private static final String TAG = "NearbyConnections";
public static void logConnectionState(String endpointId, String state) {
Log.d(TAG, String.format("연결 상태 변경 - ID: %s, 상태: %s, 시간: %s",
endpointId, state, new Date()));
}
public static void logPayloadInfo(Payload payload) {
Log.d(TAG, String.format("페이로드 정보 - ID: %d, 타입: %s, 크기: %d",
payload.getId(),
payload.getType().name(),
payload.asBytes() != null ? payload.asBytes().length : 0));
}
}
🎨 UI/UX 개선 아이디어
연결 상태 시각화
/**
* 연결 상태를 시각적으로 표시하는 메소드
*/
private void updateConnectionStatus(boolean isConnected) {
runOnUiThread(() -> {
if (isConnected) {
// 연결됨 - 녹색 표시
tvStatus.setBackgroundColor(ContextCompat.getColor(this, R.color.green_light));
tvStatus.setTextColor(ContextCompat.getColor(this, R.color.green_dark));
tvStatus.setText("🟢 연결됨 - 메시지 전송 가능");
} else {
// 연결 안됨 - 회색 표시
tvStatus.setBackgroundColor(ContextCompat.getColor(this, R.color.grey_light));
tvStatus.setTextColor(ContextCompat.getColor(this, R.color.grey_dark));
tvStatus.setText("⚪ 연결 대기중");
}
});
}
/**
* 메시지에 타임스탬프 추가
*/
private void appendMessageWithTimestamp(String message) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
String timestamp = sdf.format(new Date());
String formattedMessage = String.format("[%s] %s", timestamp, message);
runOnUiThread(() -> {
tvMessages.append(formattedMessage + "\n");
// 스크롤을 맨 아래로 이동
scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN));
});
}
📱 실제 앱 적용 사례
멀티플레이어 게임 구현
/**
* 간단한 멀티플레이어 게임을 위한 데이터 구조
*/
public class GameData {
private String playerName;
private int playerScore;
private String gameAction;
// JSON으로 직렬화하여 전송
public String toJson() {
return new Gson().toJson(this);
}
public static GameData fromJson(String json) {
return new Gson().fromJson(json, GameData.class);
}
}
/**
* 게임 데이터 전송
*/
private void sendGameAction(String action, int score) {
GameData gameData = new GameData();
gameData.playerName = Build.MODEL;
gameData.playerScore = score;
gameData.gameAction = action;
sendMessage(gameData.toJson());
}
🎯 마무리 및 다음 단계
Nearby Connections API는 오프라인 환경에서도 강력한 기기 간 통신을 제공하는 훌륭한 도구입니다. 이 가이드를 통해 기본적인 구현부터 고급 기능까지 다뤄보았습니다.
핵심 포인트 요약:
- ✅ 적절한 권한 설정이 성공의 열쇠
- ✅ 연결 전략을 용도에 맞게 선택
- ✅ 에러 처리를 통한 안정성 확보
- ✅ 사용자 경험을 고려한 UI 설계
- ✅ 배터리 최적화로 실용성 향상
추천 학습 경로:
- 기본 예제로 동작 원리 이해
- 파일 전송 기능 추가
- 보안 강화 및 인증 구현
- 실제 앱에 통합 및 최적화
- iOS와의 크로스 플랫폼 통신 구현
🚀 다음 프로젝트 아이디어
- 오프라인 채팅 앱
- 파일 공유 도구
- 멀티플레이어 보드게임
- 협업 화이트보드
- IoT 기기 제어 앱
- 오프라인 채팅 앱
- 파일 공유 도구
- 멀티플레이어 보드게임
- 협업 화이트보드
- IoT 기기 제어 앱
Nearby Connections API의 무한한 가능성을 탐험하며 혁신적인 앱을 만들어보세요! 궁금한 점이나 더 자세한 구현 방법이 필요하다면 공식 문서를 참고하시기 바랍니다.
반응형
'모바일 앱(안드로이드)' 카테고리의 다른 글
| Kotlin으로 복식 경기 Round-Robin 매칭 구성하기 (3) | 2025.07.29 |
|---|---|
| Nearby Connections API에서 기기 이름이 다르게 나오는 이유 (2) | 2025.07.25 |
| Android에서 Kakao 로컬 API로 주소/좌표 변환하기 : 앱에 적용해 보기. (3) | 2025.07.21 |
| Android에서 Hilt + Room + Firebase Realtime Database를 함께 사용하는 구조 설계 (4) | 2025.07.15 |
| 배드민터 리그 매니저 (가칭) 화면 구성 초안 (2) | 2025.07.09 |