Skip to content

네트워크 처리 및 오프라인 지원

개요

SORI SDK는 오디오 인식을 기기 내에서 처리합니다. 하지만 연관된 캠페인 수신, 관련 이미지 리소스 로딩, 웹훅 릴레이 등의 기능을 위해 인터넷 연결이 필요합니다. 오디오 인식은 성공했지만 처리 과정에서 네트워크를 사용할 수 없는 상황이 발생할 경우, SDK는 이러한 상황을 처리할 수 있는 여러 방법을 제공합니다.

주요 기능

  • 자동 네트워크 감지: SDK가 네트워크 연결 변경을 실시간으로 모니터링합니다
  • 요청 대기열: 네트워크 문제로 실패한 요청을 메모리에 대기열로 저장합니다
  • 자동 재시도: 네트워크가 복구되면 대기 중인 요청을 자동으로 처리합니다
  • 구성 가능한 동작: SDK의 네트워크 실패 처리 방식을 사용자 정의할 수 있습니다
  • 시간 기반 만료: 10분 이상 경과한 요청을 자동으로 삭제합니다

구성

SORIConfig 클래스를 사용하여 네트워크 처리 동작을 구성할 수 있습니다:

kotlin
import com.iplateia.sorisdk.SORIConfig
import com.iplateia.sorisdk.SORIAudioRecognizer

// 사용자 정의 구성 생성
val config = SORIConfig(
    enableDelayedSending = true,     // 요청 대기열 활성화
    maxPendingTimeMillis = 600000L,   // 10분 (기본값)
    autoRetryOnNetworkRecovery = true // 네트워크 복구 시 자동 재시도
)

// 인식기에 구성 적용
val sori = SORIAudioRecognizer(
    "YOUR_SORI_API_KEY",
    "YOUR_SORI_SECRET_KEY"
)
sori.setConfig(config)
java
import com.iplateia.sorisdk.SORIConfig;
import com.iplateia.sorisdk.SORIAudioRecognizer;

// 사용자 정의 구성 생성
SORIConfig config = new SORIConfig(
    true,     // enableDelayedSending
    600000L,  // maxPendingTimeMillis (10분)
    true      // autoRetryOnNetworkRecovery
);

// 인식기에 구성 적용
SORIAudioRecognizer sori = new SORIAudioRecognizer(
    "YOUR_SORI_API_KEY",
    "YOUR_SORI_SECRET_KEY"
);
sori.setConfig(config);

네트워크 이벤트 처리

SDK는 ISORIListener 인터페이스를 통해 네트워크 관련 이벤트에 대한 콜백을 제공합니다:

kotlin
import com.iplateia.sorisdk.ISORIListener
import com.iplateia.sorisdk.SORICampaign

class MyListener : ISORIListener {
    override fun onReady() {
        // SDK 준비 완료
    }
    
    override fun onStateChanged(state: String) {
        // 서비스 상태 변경됨
    }
    
    override fun onError(error: String) {
        // 오류 발생
    }
    
    override fun onCampaignFound(campaign: SORICampaign) {
        // 캠페인이 성공적으로 감지됨
    }
    
    override fun onNetworkError(materialId: String, error: String) {
        // 액티비티 전송 중 네트워크 오류 발생
        Log.w("SORI", "소재 $materialId에 대한 네트워크 오류: $error")
        // 여기에서 사용자 친화적인 메시지를 표시할 수 있습니다
    }
    
    override fun onRequestQueued(materialId: String, queueCount: Int) {
        // 요청이 나중 전송을 위해 대기열에 추가됨
        Log.i("SORI", "소재 $materialId 요청이 대기열에 추가됨. 대기열 크기: $queueCount")
        // 사용자에게 요청이 나중에 전송될 것임을 알릴 수 있습니다
    }
}

// 리스너 설정
sori.setListener(context, MyListener())
java
import com.iplateia.sorisdk.ISORIListener;
import com.iplateia.sorisdk.SORICampaign;
import android.util.Log;

public class MyListener implements ISORIListener {
    @Override
    public void onReady() {
        // SDK 준비 완료
    }
    
    @Override
    public void onStateChanged(String state) {
        // 서비스 상태 변경됨
    }
    
    @Override
    public void onError(String error) {
        // 오류 발생
    }
    
    @Override
    public void onCampaignFound(SORICampaign campaign) {
        // 캠페인이 성공적으로 감지됨
    }
    
    @Override
    public void onNetworkError(String materialId, String error) {
        // 액티비티 전송 중 네트워크 오류 발생
        Log.w("SORI", "소재 " + materialId + "에 대한 네트워크 오류: " + error);
        // 여기에서 사용자 친화적인 메시지를 표시할 수 있습니다
    }
    
    @Override
    public void onRequestQueued(String materialId, int queueCount) {
        // 요청이 나중 전송을 위해 대기열에 추가됨
        Log.i("SORI", "소재 " + materialId + 
               " 요청이 대기열에 추가됨. 대기열 크기: " + queueCount);
        // 사용자에게 요청이 나중에 전송될 것임을 알릴 수 있습니다
    }
}

// 리스너 설정
sori.setListener(context, new MyListener());

구성 옵션

SORIConfig 매개변수

매개변수타입기본값설명
enableDelayedSendingBooleantrue네트워크를 사용할 수 없을 때 요청 대기열 활성화/비활성화. false일 경우 실패한 요청을 즉시 삭제합니다.
maxPendingTimeMillisLong600000 (10분)대기 중인 요청을 삭제하기 전까지 보관할 최대 시간
autoRetryOnNetworkRecoveryBooleantrue네트워크가 복구되면 대기 중인 요청을 자동으로 재시도. false일 경우 요청은 대기열에 남아있지만 수동 처리가 필요합니다. enableDelayedSendingtrue일 때만 유효합니다.

구성 옵션 이해하기

두 Boolean 옵션은 서로 다른 목적을 가지고 있습니다:

  • enableDelayedSending: 네트워크를 사용할 수 없을 때 실패한 요청을 대기열에 저장할지 여부를 제어합니다

    • true: 네트워크를 사용할 수 없을 때 실패한 요청을 나중 전송을 위해 저장
    • false: 네트워크를 사용할 수 없을 때 실패한 요청을 즉시 삭제
    • 참고: 네트워크가 사용 가능한 경우, 이 설정값과 관계없이 요청은 항상 즉시 전송됩니다
  • autoRetryOnNetworkRecovery: 네트워크가 복구되었을 때의 동작을 제어합니다

    • true: 네트워크 복구 시 대기열에 있는 요청을 자동으로 처리
    • false: 요청을 대기열에 유지하지만 자동으로 처리하지 않음 (수동 개입 필요)
    • 참고: 이 설정은 enableDelayedSendingtrue일 때만 효과가 있습니다

동작 요약

네트워크 상태enableDelayedSending동작
사용 가능true 또는 false즉시 전송
사용 불가능true나중 전송을 위해 대기열에 저장
사용 불가능false요청 삭제

대부분의 애플리케이션에서는 두 설정을 같은 값으로 사용합니다 (안정적인 동작을 위해서는 true, 즉각적인 실패 처리를 위해서는 false).

모범 사례

1. 사용자 피드백

네트워크 문제가 발생하면 항상 사용자에게 피드백을 제공하세요:

kotlin
override fun onNetworkError(materialId: String, error: String) {
    runOnUiThread {
        Toast.makeText(
            context,
            "네트워크를 사용할 수 없습니다. 연결이 복원되면 재시도합니다.",
            Toast.LENGTH_SHORT
        ).show()
    }
}

2. 시간에 민감한 작업에 대해 대기열 비활성화

즉시 전송이 필요한 경우 대기열을 비활성화할 수 있습니다:

kotlin
val config = SORIConfig(
    enableDelayedSending = false  // 대기열 비활성화
)

3. 대기열 상태 모니터링

사용자에게 알리기 위해 대기 중인 요청을 추적하세요:

kotlin
override fun onRequestQueued(materialId: String, queueCount: Int) {
    if (queueCount > 5) {
        // 많은 요청이 대기 중이므로 사용자에게 알림
        showPendingRequestsNotification(queueCount)
    }
}

작동 방식

  1. 감지 단계: 오디오가 인식되면 SDK는 서버에 활동을 전송하려고 시도합니다
  2. 네트워크 확인: 전송 전에 SDK는 네트워크 가용성을 확인합니다
  3. 실패 시 대기열에 추가: 네트워크를 사용할 수 없는 경우 요청이 메모리 내 대기열에 추가됩니다
  4. 네트워크 모니터링: SDK는 Android의 ConnectivityManager를 통해 네트워크 상태 변경을 모니터링합니다
  5. 자동 재시도: 네트워크가 사용 가능해지면 대기 중인 요청이 자동으로 처리됩니다
  6. 만료 확인: 구성된 시간 제한보다 오래된 요청은 삭제됩니다

필요한 권한

SDK는 네트워크 처리를 위해 다음 권한이 필요합니다:

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

이러한 권한은 이미 SDK의 매니페스트에 포함되어 있으며 앱에 자동으로 병합됩니다.

문제 해결

요청이 대기열에 추가되지 않음

지연 전송이 활성화되어 있는지 확인하세요:

kotlin
val config = SORIConfig(enableDelayedSending = true)
sori.setConfig(config)

대기 중인 요청이 전송되지 않음

  1. autoRetryOnNetworkRecovery가 활성화되어 있는지 확인
  2. 앱에 ACCESS_NETWORK_STATE 권한이 있는지 확인
  3. 요청이 만료되지 않았는지 확인 (기본값: 10분 이상 경과)

메모리 고려사항

요청 대기열은 메모리에 유지됩니다. 시스템에 의해 앱이 종료되면 대기 중인 요청이 손실됩니다. 중요한 작업의 경우 자체 영구 저장소 메커니즘을 구현하는 것을 고려하세요.

예제: 완전한 구현

네트워크 처리를 구현하는 완전한 예제입니다:

kotlin
class MainActivity : AppCompatActivity() {
    private lateinit var sori: SORIAudioRecognizer
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 사용자 정의 구성으로 SORI 초기화
        initializeSori()
    }
    
    private fun initializeSori() {
        // 구성 생성
        val config = SORIConfig(
            enableDelayedSending = true,
            maxPendingTimeMillis = 600000L, // 10분
            autoRetryOnNetworkRecovery = true
        )
        
        // 인식기 생성
        sori = SORIAudioRecognizer(
            BuildConfig.SORI_API_KEY,
            BuildConfig.SORI_SECRET_KEY
        )
        
        // 구성 적용
        sori.setConfig(config)
        
        // 리스너 설정
        sori.setListener(this, object : ISORIListener {
            override fun onReady() {
                Log.d("SORI", "SDK 준비 완료")
            }
            
            override fun onStateChanged(state: String) {
                Log.d("SORI", "상태 변경: $state")
            }
            
            override fun onError(error: String) {
                Log.e("SORI", "오류: $error")
            }
            
            override fun onCampaignFound(campaign: SORICampaign) {
                Log.i("SORI", "캠페인 발견: ${campaign.name}")
                // 캠페인 처리
                handleCampaign(campaign)
            }
            
            override fun onNetworkError(materialId: String, error: String) {
                Log.w("SORI", "네트워크 오류: $error")
                // 사용자 친화적인 메시지 표시
                showNetworkErrorMessage()
            }
            
            override fun onRequestQueued(materialId: String, queueCount: Int) {
                Log.i("SORI", "요청이 대기열에 추가됨. 총 대기 중: $queueCount")
                // 대기 상태를 표시하도록 UI 업데이트
                updatePendingRequestsUI(queueCount)
            }
        })
        
        // 인식 시작
        sori.startRecognition(this)
    }
    
    private fun handleCampaign(campaign: SORICampaign) {
        // 캠페인 처리 로직
    }
    
    private fun showNetworkErrorMessage() {
        runOnUiThread {
            Toast.makeText(
                this,
                "네트워크를 사용할 수 없습니다. 연결이 복원되면 캠페인이 처리됩니다.",
                Toast.LENGTH_LONG
            ).show()
        }
    }
    
    private fun updatePendingRequestsUI(count: Int) {
        runOnUiThread {
            // 대기 중인 요청을 표시하도록 UI 업데이트
            val pendingIndicator = findViewById<TextView>(R.id.pendingIndicator)
            if (count > 0) {
                pendingIndicator.visibility = View.VISIBLE
                pendingIndicator.text = "${count}개 대기 중"
            } else {
                pendingIndicator.visibility = View.GONE
            }
        }
    }
}
java
public class MainActivity extends AppCompatActivity {
    private SORIAudioRecognizer sori;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 사용자 정의 구성으로 SORI 초기화
        initializeSori();
    }
    
    private void initializeSori() {
        // 구성 생성
        SORIConfig config = new SORIConfig(
            true,     // enableDelayedSending
            600000L,  // maxPendingTimeMillis (10분)
            true      // autoRetryOnNetworkRecovery
        );
        
        // 인식기 생성
        sori = new SORIAudioRecognizer(
            BuildConfig.SORI_API_KEY,
            BuildConfig.SORI_SECRET_KEY
        );
        
        // 구성 적용
        sori.setConfig(config);
        
        // 리스너 설정
        sori.setListener(this, new ISORIListener() {
            @Override
            public void onReady() {
                Log.d("SORI", "SDK 준비 완료");
            }
            
            @Override
            public void onStateChanged(String state) {
                Log.d("SORI", "상태 변경: " + state);
            }
            
            @Override
            public void onError(String error) {
                Log.e("SORI", "오류: " + error);
            }
            
            @Override
            public void onCampaignFound(SORICampaign campaign) {
                Log.i("SORI", "캠페인 발견: " + campaign.getName());
                // 캠페인 처리
                handleCampaign(campaign);
            }
            
            @Override
            public void onNetworkError(String materialId, String error) {
                Log.w("SORI", "네트워크 오류: " + error);
                // 사용자 친화적인 메시지 표시
                showNetworkErrorMessage();
            }
            
            @Override
            public void onRequestQueued(String materialId, int queueCount) {
                Log.i("SORI", "요청이 대기열에 추가됨. 총 대기 중: " + queueCount);
                // 대기 상태를 표시하도록 UI 업데이트
                updatePendingRequestsUI(queueCount);
            }
        });
        
        // 인식 시작
        sori.startRecognition(this);
    }
    
    private void handleCampaign(SORICampaign campaign) {
        // 캠페인 처리 로직
    }
    
    private void showNetworkErrorMessage() {
        runOnUiThread(() -> {
            Toast.makeText(
                this,
                "네트워크를 사용할 수 없습니다. 연결이 복원되면 캠페인이 처리됩니다.",
                Toast.LENGTH_LONG
            ).show();
        });
    }
    
    private void updatePendingRequestsUI(int count) {
        runOnUiThread(() -> {
            // 대기 중인 요청을 표시하도록 UI 업데이트
            TextView pendingIndicator = findViewById(R.id.pendingIndicator);
            if (count > 0) {
                pendingIndicator.setVisibility(View.VISIBLE);
                pendingIndicator.setText(count + "개 대기 중");
            } else {
                pendingIndicator.setVisibility(View.GONE);
            }
        });
    }
}