본문 바로가기

Capacitor 앱에서 네트워크 상태를 감지하는 방법

Capacitor 앱에서 네트워크 상태 감지: 온라인/오프라인 상태 읽기, 변경 사항 리스닝, 실제 인터넷 연결 확인, 데이터 요금제 네트워크 처리.

Capacitor 모바일 앱에서 온라인/오프라인 네트워크 상태 감지하기

Capacitor 앱에서 네트워크 상태를 감지하려면 설치하세요 @capacitor/network, 호출 Network.getStatus() 현재 상태를 확인하고, networkStatusChange 업데이트를 구독하세요. 그러면 기기에 연결이 있는지, Wi-Fi인지 셀룰러인지 알 수 있습니다. 인터넷이 실제로 되는지, 아니면 종량제(metered) 연결인지 확인하려면 reachability 검사를 추가하거나 @capgo/capacitor-network-diagnostics. 이 가이드에서는 Capacitor 8 code을 통해 세 가지 레이어를 모두 다룹니다.

“사용자가 온라인인가?”라는 질문 뒤에 숨은 세 가지 질문

질문 API 참고 사항
활성 네트워크 인터페이스가 있는가? Network.getStatus().connected 빠르고 이벤트 기반이지만, 인터넷 접속 여부는 알 수 없음
어떤 종류의 연결인가? connectionType wifi, cellular, none, unknown 공식 플러그인에서
인터넷이 실제로 작동하는가? HTTP 체크 또는 internetReachable 요청 또는 OS 검증 필요
대용량 다운로드를 피해야 하나요? expensive, constrained 데이터 과금 네트워크와 iOS 저데이터 모드

대부분의 오프라인 버그는 첫 번째 행을 세 번째 행으로 오인할 때 발생합니다. 로그인 페이지가 있는 호텔 Wi-Fi는 connected: true 로 보고되지만 모든 API 호출은 타임아웃됩니다.

Network 플러그인 설치

bun add @capacitor/network
bunx cap sync

설정할 것이 없습니다. Android에서는 플러그인이 ACCESS_NETWORK_STATE 을 병합된 매니페스트에 추가하며, 이는 설치 시 자동으로 부여되는 일반 권한입니다. iOS에서는 아무것도 필요하지 않습니다.

현재 상태 읽기

import { Network } from '@capacitor/network';

const status = await Network.getStatus();
console.log(status.connected);      // true or false
console.log(status.connectionType); // 'wifi' | 'cellular' | 'none' | 'unknown'

캐시 또는 네트워크에서 로드할지 결정하기 전에 앱 시작 시 호출하세요. OS 상태를 읽기 때문에 트래픽을 보내지 않으며 몇 밀리초 만에 반환됩니다.

네트워크 변경 감시

import { Network, type ConnectionStatus } from '@capacitor/network';
import type { PluginListenerHandle } from '@capacitor/core';

let handle: PluginListenerHandle | undefined;

export async function watchNetwork(onChange: (s: ConnectionStatus) => void) {
  onChange(await Network.getStatus());
  handle = await Network.addListener('networkStatusChange', onChange);
}

export async function stopWatchingNetwork() {
  await handle?.remove();
}

실용적인 세부 사항 두 가지:

  • 이벤트 디바운스 처리 Wi-Fi에서 셀룰러로 전환할 때, 새 링크가 연결되기 전까지 두 플랫폼 모두 잠시 none 로 보고할 수 있습니다. '오프라인' 배너를 표시하기 전 1~2초 디바운스를 적용하면 깜빡임을 방지할 수 있습니다.
  • 리스너 제거 컴포넌트가 언마운트될 때 리스너를 제거하지 않으면, 핫 리로드 및 내비게이션 후 콜백이 여러 번 실행됩니다.

React 훅

import { useEffect, useState } from 'react';
import { Network, type ConnectionStatus } from '@capacitor/network';

export function useNetworkStatus() {
  const [status, setStatus] = useState<ConnectionStatus>({ connected: true, connectionType: 'unknown' });

  useEffect(() => {
    let timer: ReturnType<typeof setTimeout>;
    const listener = Network.addListener('networkStatusChange', (s) => {
      clearTimeout(timer);
      timer = setTimeout(() => setStatus(s), s.connected ? 0 : 1500);
    });
    Network.getStatus().then(setStatus);
    return () => {
      clearTimeout(timer);
      listener.then((l) => l.remove());
    };
  }, []);

  return status;
}

온라인 전환은 즉시 적용되고, 오프라인 전환은 1.5초 대기합니다. 동일한 패턴을 Vue composable 또는 Angular service로 사용할 수 있으며 BehaviorSubject.

“connected”가 온라인을 의미하지 않는 이유

connected: true OS 네트워크 스택에서 가져옵니다. 다음 경우에도 true로 유지됩니다:

  • Wi-Fi 네트워크에 캡티브 포털이 있는 경우 (공항, 호텔, 기차)
  • 라우터에 업스트림 연결이 없는 경우
  • DNS가 손상되거나 필터링되는 경우
  • 기업 방화벽 또는 VPN이 API 도메인을 차단하는 경우
  • 자체 백엔드가 다운된 경우

해결 방법은 자신이 제어하는 대상에 대한 도달성 확인입니다:

export async function canReachApi(timeoutMs = 4000): Promise<boolean> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const res = await fetch('https://api.example.com/health', {
      method: 'HEAD',
      cache: 'no-store',
      signal: controller.signal,
    });
    return res.ok;
  } catch {
    return false;
  } finally {
    clearTimeout(timer);
  }
}

앱 시작 시, networkStatusChange 보고서에 따르면 connected: true그리고 요청이 실패한 후에 실행하세요. 몇 초마다 폴링하지 마세요. 배터리와 데이터를 소모합니다. 헬스 엔드포인트가 앱의 origin을 CORS에 허용하도록 확인하세요 (capacitor://localhost iOS에서는 https://localhost Android에서는 기본적으로) 또는 CapacitorHttp 를 사용해 네이티브 code에서 전송합니다.

네트워크 진단으로 더 풍부한 상태 확인

@capgo/capacitor-network-diagnostics OS가 파악하는 정보를 더 많이 읽고, WebView CORS를 우회하는 네이티브 체크를 실행할 수 있습니다:

bun add @capgo/capacitor-network-diagnostics
bunx cap sync
import { NetworkDiagnostics } from '@capgo/capacitor-network-diagnostics';

const status = await NetworkDiagnostics.getNetworkStatus();
// {
//   connected: true,
//   connectionType: 'wifi' | 'cellular' | 'ethernet' | 'vpn' | 'other' | 'unknown' | 'none',
//   internetReachable: true,
//   expensive: false,      // metered / cellular
//   constrained: false,    // iOS Low Data Mode
//   captivePortal: false,  // Android only
//   details: { ... }
// }

필드가 OS에 매핑되는 방식:

필드 Android iOS
internetReachable NET_CAPABILITY_VALIDATED (Android가 인터넷을 탐지함) 의 NWPathMonitor 경로 상태를 반영합니다
expensive isActiveNetworkMetered() NWPath.isExpensive
constrained 항상 false NWPath.isConstrained (저용량 데이터 모드)
captivePortal NET_CAPABILITY_CAPTIVE_PORTAL 미보고
vpn 타입 지원 지원

Android에서는, internetReachable OS가 이미 연결을 테스트했기 때문에 강력한 신호입니다. iOS에서는 더 가깝습니다 connected, 따라서 중요한 플로우에는 HTTP 체크를 유지하세요.

네이티브 도달성 체크

const api = await NetworkDiagnostics.testUrl({
  url: 'https://api.example.com/health',
  method: 'HEAD',
  timeoutMs: 4000,
});
// api.reachable, api.statusCode, api.durationMs, api.errorCode

const socket = await NetworkDiagnostics.testWebSocket({ url: 'wss://realtime.example.com' });
const port = await NetworkDiagnostics.testPort({ host: 'mqtt.example.com', port: 8883 });

네이티브 요청은 CORS에 해당하지 않으므로 서드파티 호스트도 테스트할 수 있습니다.

지원용 '네트워크 리포트' 화면

사용자가 “앱이 안 된다”고 할 때, 원탭 진단 리포트가 수 시간의 반복 커뮤니케이션을 절약해 줍니다:

const report = await NetworkDiagnostics.runDiagnostics({
  urls: [{ url: 'https://api.example.com/health' }, { url: 'https://cdn.example.com/ping' }],
  websockets: [{ url: 'wss://realtime.example.com' }],
  download: { url: 'https://cdn.example.com/1mb.bin', maxBytes: 1_048_576 },
  packetLoss: { url: 'https://api.example.com/health', count: 5 },
});

console.log(report.issues);        // human-readable list of problems
console.log(report.download?.mbps);
console.log(report.packetLoss?.lossPercent);

JSON을 지원 티켓에 첨부하세요. 문제가 DNS, 차단된 WebSocket, 캡티브 포털, 느린 링크 중 무엇인지 바로 확인할 수 있습니다.

데이터 요금제 및 Low Data Mode 연결에 대응하기

모바일 데이터 요금이 비싼 시장에서 데이터 제한을 준수하는 것은 리뷰 점수 향상의 빠른 방법입니다:

export async function shouldPrefetchMedia() {
  const s = await NetworkDiagnostics.getNetworkStatus();
  if (!s.connected) return false;
  if (s.constrained) return false; // user asked iOS to save data
  if (s.expensive) return false;   // cellular or metered Wi-Fi
  return true;
}

비디오 자동 재생, 이미지 품질, 백그라운드 동기화, 대용량 다운로드 결정에 사용하세요. Android에서는 사용자가 Wi-Fi 네트워크를 데이터 요금제로 표시할 수 있으므로 Wi-Fi가 무료라고 가정하지 마세요.

오프라인 배너 만들기

요청 실패보다 작은 배너가 더 명확합니다. 위의 훅을 사용하여:

export function OfflineBanner() {
  const { connected } = useNetworkStatus();
  if (connected) return null;
  return (
    <div role="status" aria-live="polite" className="offline-banner">
      You are offline. Changes will sync when you reconnect.
    </div>
  );
}

role="status" 포함 aria-live="polite" 스크린 리더가 변경 사항을 알리도록 합니다. 프레임워크별 UI에 대해서는 다음 가이드를 참고하세요: Vue, Angular, React에서 오프라인 화면 만들기.

오프라인 상태 동안 작업 대기열에 추가하기

감지는 오프라인 지원의 절반에 불과합니다. 상태가 오프라인으로 전환되면:

  1. 로컬 스토리지에서 읽기 요청을 처리합니다. SQLite 데이터베이스와 같은 @capgo/capacitor-fast-sql 는 localStorage.
  2. 사용자 작업을 타임스탬프와 멱등성 키(idempotency key)를 포함하여 outbox 테이블에 기록하세요.
  3. 웹 networkStatusChange 로 연결되면 canReachApi()을 실행한 후 outbox를 순서대로 flush하세요.
  4. 실패한 요청은 지수 백오프(exponential backoff)로 재시도하되, 최대 몇 분으로 제한하세요.

앱이 종료되어도 유지되어야 하는 업로드의 경우, 대신 네이티브 업로더에 위임하세요. fetch.

웹 fallback

@capacitor/network 은 브라우저에서도 실행되며, 이때는 navigator.onLine 및 online 및 offline events. 즉, code은 bun run dev 에서 변경 없이 PWA에서도 작동하지만 connectionType 웹에서는 종종 unknown 됩니다. Chrome의 navigator.connection 는 제공하며, effectiveType 및 saveData은 Safari에 없으므로 선택적 힌트로 취급하세요.

문제 해결

iOS 시뮬레이터에서는 리스너가 절대 발화되지 않습니다: 시뮬레이터는 Mac의 네트워크를 공유합니다. Mac의 Wi-Fi를 전환하거나 비행기 모드가 있는 기기에서 테스트하세요.

connectionType 이며 unknown VPN에서: 공식 플러그인은 단순히 보고만 합니다 wifi, cellular, none 및 unknown. 진단 플러그인을 사용하세요. 이 플러그인은 반환합니다 vpn 및 ethernet.

네트워크를 전환할 때 앱이 1초간 오프라인으로 표시됩니다: 위에서 보여준 대로 오프라인 전환을 디바운스하세요.

디바이스에서는 헬스 체크가 실패하지만 브라우저에서는 작동합니다: CORS입니다. 허용하세요 capacitor://localhost 및 https://localhost, 또는 NetworkDiagnostics.testUrl을(를) 사용하세요. 네이티브로 실행됩니다.

navigator.onLine 플러그인은 오프라인이라고 하지만 true입니다: 플러그인을 신뢰하세요. 일부 Android WebView는 업데이트를 navigator.onLine 신뢰적으로 수행하지 않습니다.

오프라인 수정 사항을 OTA로 배포하세요

오프라인 처리는 대부분 JavaScript입니다: 임계값, 배너, 재시도 로직, 캐시 규칙. Capgo Live Update 사용하기을(를) 사용하면 같은 날 모든 사용자에게 이를 조정할 수 있습니다. Capgo는 백그라운드에서 업데이트 번들을 다운로드하고 다운로드가 실패하면 현재 버전을 유지하므로, 불안정한 연결이 사용자에게 반쯤 설치된 업데이트를 남기지 않습니다. 네트워크 진단 문서 에 모든 옵션이 나열되어 있습니다.

Capacitor 앱용 Live updates

웹 레이어 버그가 라이브 상태라면, 앱 스토어 승인을 며칠씩 기다리지 말고 Capgo를 통해 수정 사항을 배포하세요. 네이티브 변경 사항은 정상 리뷰 절차를 그대로 따르는 반면, 사용자는 백그라운드에서 업데이트를 받습니다.

블로그 최신 글

Capgo는 정말 프로페셔널한 모바일 앱을 만드는 데 필요한 최고의 인사이트를 제공합니다.