ConnectivityManager.CONNECTIVITY_ACTION เลิกใช้งานแล้ว


95

ใน Android N มีการกล่าวถึงบนเว็บไซต์อย่างเป็นทางการว่า "แอปที่กำหนดเป้าหมาย Android N ไม่ได้รับการออกอากาศ CONNECTIVITY_ACTION" และยังมีการกล่าวถึงที่JobSchedulerสามารถใช้เป็นทางเลือก แต่JobSchedulerไม่ได้ให้ลักษณะการทำงานเหมือนกับการCONNECTIVITY_ACTIONออกอากาศ

ในแอปพลิเคชัน Android ของฉันฉันใช้การถ่ายทอดสัญญาณนี้เพื่อทราบสถานะเครือข่ายของอุปกรณ์ ฉันต้องการทราบว่าสถานะนี้เป็นCONNECTINGหรือCONNECTEDด้วยความช่วยเหลือของการCONNECTIVITY_ACTIONออกอากาศและเหมาะที่สุดสำหรับความต้องการของฉัน

ตอนนี้เลิกใช้งานแล้วมีใครช่วยแนะนำวิธีอื่นในการรับสถานะเครือข่ายปัจจุบันได้ไหม


10
แล้วถ้าสักวัน OP ต้องการพฤติกรรมบางอย่างที่ต้องเพิ่มค่าtargetSdkVersionเป็น N หรือใหม่กว่า?
Michael

1
ฉันก็รู้เช่นกันว่าหากฉันไม่กำหนดเป้าหมายแอปพลิเคชันของฉันไปที่ Android NI จะได้รับการถ่ายทอด แต่แอปพลิเคชันของฉันต้องรองรับ Android N ฉันจะรับพฤติกรรมการออกอากาศแบบเดียวกันใน Android N ได้อย่างไร มีแนวทางอื่นที่ฉันสามารถลองได้หรือไม่? @DavidWasser
Raghuram db

บางครั้งฉันคิดว่ามันสมเหตุสมผลมากกว่าที่จะกังวลเกี่ยวกับอนาคตในอนาคต นี่เป็นแนวทางปฏิบัติในการเขียนโปรแกรมเท่านั้น แน่นอนว่าคุณสามารถพยายามตรวจสอบให้แน่ใจว่าโค้ดของคุณไม่ได้ใช้คุณสมบัติใด ๆ ที่เลิกใช้แล้ว ในทางกลับกันคุณลักษณะที่เลิกใช้งานมักจะอยู่เป็นเวลานานและอาจเป็นไปได้ว่าแอปของคุณจะหมดอายุการใช้งานก่อนที่ฟีเจอร์ที่เลิกใช้งานจะหายไป Android N เป็นรุ่นใหม่ที่ฉันจะไม่ใช้เวลากังวลกับมันมากนัก ยัง. แค่ 2 เซ็นต์ของฉัน โปรดทราบว่าฉันเขียนความคิดเห็นสำหรับคำถามและไม่ได้แนะนำว่า "อย่าทำอย่างนั้น" เป็นคำตอบที่ถูกต้อง
David Wasser

2
@Raghuramdb แอปของคุณสามารถทำงานบน Android N ได้แม้ว่าคุณจะไม่ได้กำหนดเป้าหมายแอปของคุณไปที่ Android N แต่คุณต้องกำหนดเป้าหมายเป็น Android N หากคุณต้องการใช้คุณลักษณะที่มีเฉพาะใน Android N เท่านั้น
David Wasser

2
คุณยังสามารถใช้BroadcastReceiverกับandroid.net.conn.CONNECTIVITY_CHANGEตัวกรองความตั้งใจแม้เมื่อกำหนดเป้าหมาย API29 Application.OnCreateคุณเพียงแค่ต้องลงทะเบียนใน คุณจะไม่ได้รับการอัปเดตใด ๆ เมื่อปิดแอป
ปิแอร์

คำตอบ:


102

สิ่งที่จะเลิกใช้คือความสามารถของแอปพลิเคชันที่อยู่เบื้องหลังในการรับการเปลี่ยนแปลงสถานะการเชื่อมต่อเครือข่าย

ดังที่David Wasserกล่าวว่าคุณยังคงได้รับการแจ้งเตือนเกี่ยวกับการเปลี่ยนแปลงการเชื่อมต่อหากส่วนประกอบของแอปถูกสร้างอินสแตนซ์ (ไม่ได้ถูกทำลาย) และคุณได้ลงทะเบียนผู้รับของคุณโดยใช้โปรแกรมตามบริบทแทนที่จะทำในไฟล์ Manifest

หรือคุณสามารถใช้NetworkCallbackแทน โดยเฉพาะอย่างยิ่งคุณจะต้องแทนที่onAvableสำหรับการเปลี่ยนแปลงสถานะที่เชื่อมต่อ

ให้ฉันร่างตัวอย่างข้อมูลอย่างรวดเร็ว:

public class ConnectionStateMonitor extends NetworkCallback {

   final NetworkRequest networkRequest;

   public ConnectionStateMonitor() {
       networkRequest = new NetworkRequest.Builder()
           .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
           .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
           .build();
   }

   public void enable(Context context) {
       ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
       connectivityManager.registerNetworkCallback(networkRequest, this);
   }

   // Likewise, you can have a disable method that simply calls ConnectivityManager.unregisterNetworkCallback(NetworkCallback) too.

   @Override
   public void onAvailable(Network network) {
       // Do what you need to do here
   }
}

2
เนื่องจากเทคนิคนี้จะใช้ได้ผลก็ต่อเมื่อแอปทำงานอยู่เบื้องหน้า หมายความว่าเราไม่มีความสามารถในการฟังเหตุการณ์การเชื่อมต่ออีกต่อไปเมื่อแอปไม่ทำงานอยู่เบื้องหน้า? การมี <action android: name = "android.net.conn.CONNECTIVITY_CHANGE" /> ใน manifest.xml จะไม่มีผลใน Android N อีกต่อไป
Cheok Yan Cheng

2
@CheokYanCheng AFAIK ที่ถูกต้อง คุณต้องมีกระบวนการที่ทำงานอยู่เบื้องหน้าเพื่อฟังเหตุการณ์การเชื่อมต่อ ดูเหมือนว่าข้อสันนิษฐานที่เกิดขึ้นโดยวิศวกรเฟรมเวิร์กของ Android คือการฟังเหตุการณ์การเชื่อมต่อส่วนใหญ่ทำเพื่อให้ทราบว่าเมื่อใดควรเริ่มการซิงค์ข้อมูลระหว่างไคลเอนต์และเซิร์ฟเวอร์ ดังนั้น JobScheduler จึงเป็นวิธีที่แนะนำสำหรับกรณีการใช้งานนั้น
Amokrane Chentir

29
ฮ่า ๆ สิ่งที่นรกอัปเดต Android อีก 10 รายการและสิ่งที่เราจะสามารถเขียนได้คือแอป hello world
DennisVA

1
ฉันจำเป็นต้องยกเลิกการลงทะเบียน NetworkCallback (เช่นในเมธอด onDestroy ของกิจกรรม) หรือไม่
Ruslan Berozov

2
@Ruslan ใช่แน่นอนมิฉะนั้นคุณจะรั่วไหลสิ่งที่ลงทะเบียน
DennisVA

35

ฉันจะอัปเดตSayem'sคำตอบสำหรับการแก้ไขปัญหาผ้าสำลีที่แสดงให้ฉันเห็น

class ConnectionLiveData(val context: Context) : LiveData<Boolean>() {

    private var connectivityManager: ConnectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

    private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback

    private val networkRequestBuilder: NetworkRequest.Builder = NetworkRequest.Builder()
        .addTransportType(android.net.NetworkCapabilities.TRANSPORT_CELLULAR)
        .addTransportType(android.net.NetworkCapabilities.TRANSPORT_WIFI)

    override fun onActive() {
        super.onActive()
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(getConnectivityMarshmallowManagerCallback())
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> marshmallowNetworkAvailableRequest()
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> lollipopNetworkAvailableRequest()
            else -> {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    context.registerReceiver(networkReceiver, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")) // android.net.ConnectivityManager.CONNECTIVITY_ACTION
                }
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
        } else {
            context.unregisterReceiver(networkReceiver)
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private fun lollipopNetworkAvailableRequest() {
        connectivityManager.registerNetworkCallback(networkRequestBuilder.build(), getConnectivityLollipopManagerCallback())
    }

    @TargetApi(Build.VERSION_CODES.M)
    private fun marshmallowNetworkAvailableRequest() {
    connectivityManager.registerNetworkCallback(networkRequestBuilder.build(), getConnectivityMarshmallowManagerCallback())
    }

    private fun getConnectivityLollipopManagerCallback(): ConnectivityManager.NetworkCallback {
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
           connectivityManagerCallback = object : ConnectivityManager.NetworkCallback() {
               override fun onAvailable(network: Network?) {
                   postValue(true)
               }

               override fun onLost(network: Network?) {
                   postValue(false)
               }
           }
           return connectivityManagerCallback
       } else {
           throw IllegalAccessError("Accessing wrong API version")
       }
    }

    private fun getConnectivityMarshmallowManagerCallback(): ConnectivityManager.NetworkCallback {
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          connectivityManagerCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onCapabilitiesChanged(network: Network?, networkCapabilities: NetworkCapabilities?) {
                networkCapabilities?.let { capabilities ->
                    if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                        postValue(true)
                    }
                }
            }
            override fun onLost(network: Network?) {
                postValue(false)
            }
        }
        return connectivityManagerCallback
    } else {
        throw IllegalAccessError("Accessing wrong API version")
    }

    private val networkReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            updateConnection()
        }
    }

    private fun updateConnection() {
        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        postValue(activeNetwork?.isConnected == true)
    }
}

และการใช้งานเดียวกัน:

    val connectionLiveData = ConnectionLiveData(context)
        connectionLiveData.observe(this, Observer { isConnected ->
           isConnected?.let {
             // do job
           }
    })

ขอบคุณ sayem สำหรับการแก้ปัญหาของคุณ


2
ทางออกที่น่าทึ่ง!
box

2
ทางออกที่ดีมากในการใช้ข้อมูลสดและรองรับเวอร์ชันเก่า
Prakash Shukla

นี่คือทางออกที่ดีที่สุดบนอินเทอร์เน็ต
Karan Sharma

ทางออกที่ดีมาก! แต่มีวิธีหนึ่ง "ไม่" - เป็นวิธีที่ผิดในการใช้เมธอด onAvailable (เครือข่าย: เครือข่าย?) เนื่องจากเรียกใช้แม้แต่อินเทอร์เน็ตก็ไม่สามารถใช้งานได้ จะดีกว่าถ้าใช้ onCapabilitiesChanged (network: Network, networkCapabilities: NetworkCapabilities) และตรวจสอบ networkCapabilities.hasCapability (NET_CAPABILITY_INTERNET) และ networkCapabilities.hasCapability (NET_CAPABILITY_VALIDATED)
DmitryKanunnikoff

วิธีรับ ip และประเภทเครือข่ายภายในฐานรหัสนี้
A_rmas

29

เอกสารสำหรับสถานะ Android N:

แอปที่กำหนดเป้าหมายไปที่ Android N ไม่ได้รับการแพร่ภาพ CONNECTIVITY_ACTION แม้ว่าจะมีรายการรายการเพื่อขอการแจ้งเตือนเหตุการณ์เหล่านี้ก็ตาม แอปที่ทำงานอยู่เบื้องหน้ายังคงสามารถฟัง CONNECTIVITY_CHANGE บนเธรดหลักได้หากพวกเขาร้องขอการแจ้งเตือนด้วย BroadcastReceiver

ซึ่งหมายความว่าคุณยังคงสามารถลงทะเบียนBroadcastReceiverหากแอปของคุณทำงานอยู่เบื้องหน้าเพื่อตรวจจับการเปลี่ยนแปลงในการเชื่อมต่อเครือข่าย


จับละเอียดดี :)
Amokrane Chentir

หมายความว่าแอปจะหยุดรับการแพร่ภาพเมื่อไม่ได้อยู่เบื้องหน้าใช่หรือไม่ (ดังนั้นฉันไม่สามารถฟังมันในบริการได้หรือไม่)
sundie

1
ฉันไม่รู้แน่ชัดฉันจะต้องทดสอบเพื่อความแน่ใจ Intentอย่างไรก็ตามการอ่านเอกสารจะปรากฏว่าถ้าแอปของคุณไม่ได้อยู่ในเบื้องหน้าแล้วคุณจะไม่ได้รับการออกอากาศ
David Wasser

2
แต่ในการตรวจจับการเปลี่ยนแปลงการเชื่อมต่อในพื้นหลังเป็นสิ่งจำเป็นสำหรับ sip-apps (VoIP) ใด ๆ ... โดยปกติแล้วแอปเหล่านั้นจะทำงานในพื้นหลังเป็นเวลาหลายวันและข้ามไปที่เบื้องหน้าก็ต่อเมื่อมีสายเข้ามาเท่านั้น (เช่นเดียวกับแป้นหมุนหมายเลขโทรศัพท์ของคุณ) .. แอปเหล่านั้นจำเป็นต้องเชื่อมต่อใหม่โดยอัตโนมัติในพื้นหลัง สิ่งนี้จะฆ่าแอพเหล่านั้นทั้งหมด (ที่ไม่มีเซิร์ฟเวอร์พุชของตัวเอง) จากแพลตฟอร์ม Android เนื่องจากจะออฟไลน์ เสมอ.
Grisgram

เพียงใช้บริการผลักดัน firebase
ปิแอร์

21

โปรดตรวจสอบคำตอบของ @Amokrane Chentir ก่อนสำหรับการสนับสนุน Android N

สำหรับผู้ที่ต้องการสนับสนุนในทุกระดับ api & สังเกตใน ui โปรดตรวจสอบรหัสร้อง

LiveData ของ NetworkConnection:

class ConnectionLiveData(val context: Context) : LiveData<Boolean>(){

    var  intentFilter = IntentFilter(CONNECTIVITY_ACTION)
    private var  connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
    private lateinit var networkCallback : NetworkCallback

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            networkCallback = NetworkCallback(this)
        }
    }

    override fun onActive() {
        super.onActive()
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(networkCallback)
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
                val builder = NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI)
                connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
            }
            else -> {
                context.registerReceiver(networkReceiver, intentFilter)
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            connectivityManager.unregisterNetworkCallback(networkCallback)
        } else{
            context.unregisterReceiver(networkReceiver)
        }
    }


    private val networkReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            updateConnection()
        }
    }

    fun updateConnection() {
        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        postValue(activeNetwork?.isConnectedOrConnecting == true)
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    class NetworkCallback(val liveData : ConnectionLiveData) : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network?) {
            liveData.postValue(true)
        }

        override fun onLost(network: Network?) {
            liveData.postValue(false)
        }
    }
}

สังเกตใน UI (กิจกรรม / ส่วนย่อย):

val connectionLiveData = ConnectionLiveData(context)
    connectionLiveData.observe(this, Observer { 
       // do whatever you want with network connectivity change 
})

btw คุณไม่จำเป็นต้องกำหนดIntentFilterอย่างชัดเจน เช่นนั้น:var intentFilter = IntentFilter(CONNECTIVITY_ACTION)
Ryan Amaral

ขอบคุณสำหรับคำแนะนำของคุณ ฉันไม่ต้องการสร้างวัตถุทุกครั้งใน onActive
Sayem

ฉันหมายความว่าตัวแปร / คุณสมบัติส่วนกลาง 2 ตัว ( intentFilterและconnectivityManager) คุณไม่จำเป็นต้องกำหนดประเภทของมันอย่างชัดเจน ( IntentFilterและConnectivityManagerตามลำดับ)
Ryan Amaral

ของส่วนใหญ่ (น่าเสียดาย) เลิกใช้แล้ว ..
Andrew

7

ฉันพบปัญหาเดียวกันเมื่อสองสามวันก่อนและฉันตัดสินใจใช้ไลบรารีAndroid-Job นี้

นี้ใช้ห้องสมุดJobSchedular, GcmNetworkManagerและBroadcastReceiverขึ้นอยู่กับรุ่นของ Android app ที่ทำงานอยู่ใน

การเริ่มงานเป็นเรื่องง่ายพอสมควร

new JobRequest.Builder(DemoSyncJob.TAG)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(false)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) // this is what gets the job done
            .build()
            .schedule();

1
ฉันได้ลอง schedler เดียวกันและได้รับข้อยกเว้นเช่นนี้คุณกำลังพยายามสร้างงานโดยไม่มีข้อ จำกัด ไม่อนุญาตให้ทำเช่นนี้ คุณช่วยเราแก้ปัญหานี้ได้ไหม ??
Sanket Kachhela

การใช้ Android-Job เพื่อจุดประสงค์นี้ไม่ใช่วิธีการแก้ปัญหาที่ดีนัก หมายถึงการเรียกใช้สิ่งต่างๆในเวลาที่กำหนดไม่ว่าจะครั้งเดียวหรือเป็นระยะ ๆ มีวัตถุประสงค์เพื่อนำการสนับสนุนความเข้ากันได้ย้อนยุคสำหรับ Alarms และอื่น ๆ สิ่งนี้สวนทางกับแนวคิดทั้งหมดที่ว่าเหตุใด API จึงเปลี่ยนไปและอ่าน: developer.android.com/training/monitoring-device-state/… คุณสามารถทราบสาเหตุได้อย่างรวดเร็ว
pedronveloso

ปัญหาเดียวคือใน Android N สามารถกำหนดเวลาอย่างน้อย 15 นาทีในอนาคต
Fire Crow

4

ผมเขียนการดำเนิน Kotlin ซึ่งจะขึ้นอยู่กับคำตอบของสยามLiveDataแต่ไม่มี ฉันตัดสินใจเรียกใช้ (ณ เวลานี้) เมธอด API ล่าสุด ( ConnectivityManager#registerDefaultNetworkCallback) ซึ่งกำหนดเป้าหมายไปที่ Android Nougat

/**
 * Observes network connectivity by consulting the [ConnectivityManager].
 * Observing can run infinitely or automatically be stopped after the first response is received.
 */
class ConnectivityObserver @JvmOverloads constructor(

        val context: Context,
        val onConnectionAvailable: () -> Unit,
        val onConnectionLost: () -> Unit = {},
        val shouldStopAfterFirstResponse: Boolean = false

) {

    private val connectivityManager
        get() = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    @Suppress("DEPRECATION")
    private val intentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)

    private val broadCastReceiver = object : BroadcastReceiver() {

        @Suppress("DEPRECATION")
        override fun onReceive(context: Context?, intent: Intent?) {
            if (ConnectivityManager.CONNECTIVITY_ACTION != intent?.action) {
                return
            }
            val networkInfo = connectivityManager.activeNetworkInfo
            if (networkInfo != null && networkInfo.isConnectedOrConnecting) {
                onConnectionAvailable.invoke()
            } else {
                onConnectionLost.invoke()
            }
            if (shouldStopAfterFirstResponse) {
                stop()
            }
        }

    }

    private lateinit var networkCallback: ConnectivityManager.NetworkCallback

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            networkCallback = object : ConnectivityManager.NetworkCallback() {

                override fun onAvailable(network: Network) {
                    super.onAvailable(network)
                    onConnectionAvailable.invoke()
                    if (shouldStopAfterFirstResponse) {
                        stop()
                    }
                }

                override fun onLost(network: Network?) {
                    super.onLost(network)
                    onConnectionLost.invoke()
                    if (shouldStopAfterFirstResponse) {
                        stop()
                    }
                }
            }
        }
    }

    fun start() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            // Decouple from component lifecycle, use application context.
            // See: https://developer.android.com/reference/android/content/Context.html#getApplicationContext()
            context.applicationContext.registerReceiver(broadCastReceiver, intentFilter)
        } else {
            connectivityManager.registerDefaultNetworkCallback(networkCallback)
        }
    }

    fun stop() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            context.applicationContext.unregisterReceiver(broadCastReceiver)
        } else {
            connectivityManager.unregisterNetworkCallback(networkCallback)
        }
    }

}

การใช้งาน:

val onConnectionAvailable = TODO()
val connectivityObserver = ConnectivityObserver(context, onConnectionAvailable)
connectivityObserver.start()
connectivityObserver.stop()

หรือ:

val onConnectionAvailable = TODO()
val onConnectionLost = TODO()
ConnectivityObserver(context, 
    onConnectionAvailable, 
    onConnectionLost, 
    shouldStopAfterFirstResponse = true
).start()

อย่าลืมเพิ่มACCESS_NETWORK_STATEสิทธิ์ในAndroidManifest.xmlของคุณ:

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

ฉันรอคอยที่จะอ่านความคิดเห็นที่เป็นประโยชน์และการปรับปรุงจากคุณ


1
ฉันมีการเปลี่ยนแปลงบางสิ่งบางอย่างสำหรับการเรียกกลับเพื่อให้สามารถ "มุมมองการสัมผัส" ในกิจกรรม (บริบท) ในหัวข้อหลักแทน(context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } }) onConnectionAvailable.invoke()เช่นเดียวกันสำหรับonConnectionLost.invoke().
АндрейВоробьев

ใช่ขึ้นอยู่กับกรณีการใช้งานของคุณคุณอาจต้องเปลี่ยนเธรด ฉันจะไม่ทำให้เป็นส่วนหนึ่งของชั้นเรียน แต่ให้ผู้บริโภคในชั้นเรียนดูแลเรื่องนี้แทน แต่ขอบคุณสำหรับคำใบ้.
JJD

4

อ้างอิงจากคำตอบของ @ KebabKrabby:

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Context.CONNECTIVITY_SERVICE
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.ConnectivityManager.CONNECTIVITY_ACTION
import android.net.ConnectivityManager.EXTRA_NO_CONNECTIVITY
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.os.Build
import androidx.lifecycle.LiveData

class ConnectivityWatcher(
    private val context: Context
): LiveData<Boolean>() {

    private lateinit var networkCallback: ConnectivityManager.NetworkCallback
    private lateinit var broadcastReceiver: BroadcastReceiver

    override fun onActive() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
            networkCallback = createNetworkCallback()
            cm.registerDefaultNetworkCallback(networkCallback)
        } else {
            val intentFilter = IntentFilter(CONNECTIVITY_ACTION)
            broadcastReceiver = createBroadcastReceiver()
            context.registerReceiver(broadcastReceiver, intentFilter)
        }
    }

    override fun onInactive() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
            cm.unregisterNetworkCallback(networkCallback)
        } else {
            context.unregisterReceiver(broadcastReceiver)
        }
    }

    private fun createNetworkCallback() = object : ConnectivityManager.NetworkCallback() {

        override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
        ) {
            val isInternet = networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)
            val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
            postValue(isInternet && isValidated)
        }

        override fun onLost(network: Network) {
            postValue(false)
        }
    }

    private fun createBroadcastReceiver() = object : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            val isNoConnectivity = intent?.extras?.getBoolean(EXTRA_NO_CONNECTIVITY) ?: true
            postValue(!isNoConnectivity)
        }
    }
}

และใช้มันเกือบจะเหมือนกับคำตอบเดิม (หากสังเกตจากกิจกรรมเป็นต้น):

ConnectivityWatcher(this).observe(this, Observer {
    Log.i("*-*-*", "is internet available? - ${if (it) "Yes" else "No"}")
})

2

แอปที่กำหนดเป้าหมาย Android N (Nougat) ไม่ได้รับการCONNECTIVITY_ACTIONออกอากาศที่กำหนดไว้ในรายการ (ดูSvelte )

การแก้ปัญหาที่เป็นไปได้:

ดูเพิ่มเติมที่Android O - ตรวจจับการเปลี่ยนแปลงการเชื่อมต่อในพื้นหลัง


1

ฉันเห็นด้วยกับคำตอบที่ @rds แนะนำ

โปรดทราบว่าCONNECTIVITY_ACTIONเลิกใช้งานแล้วใน API ระดับ 28

หากคุณมีข้อกำหนดว่าควรตรวจพบสถานะ Wifi (เชื่อมต่อ / ตัดการเชื่อมต่อ) แม้ว่าแอปจะถูกฆ่าและคุณต้องการกำหนดเป้าหมายเป็นเวอร์ชันล่าสุดแสดงว่าคุณไม่มีทางเลือกมากนัก

คุณจำเป็นต้องใช้ connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

คำถามคือคุณไม่สามารถใช้ BroadcastReceiver ได้แล้วอย่างไร?

คุณสามารถใช้ JobScheduler หรือดีกว่าถ้า WorkManager (คำขอเป็นระยะ) ทำไมต้องเป็น Periodic เพราะถ้าเป็น OneTimeRequest มันจะทำงานได้เพียงครั้งเดียวและฟังต่อไปในขณะที่แอพของคุณอยู่เบื้องหน้า

เอกสารระบุว่า:

การโทรกลับจะถูกเรียกต่อไปจนกว่าแอปพลิเคชันจะออกหรือลิงก์ #unregisterNetworkCallback (NetworkCallback)}

เมื่อแอพถูกฆ่าหรือลบออกจากรายการแอพล่าสุด NetworkCallback จะไม่สามารถฟังได้

ดังนั้นคุณต้องมีงานเป็นระยะเพื่อให้แอปฟังอย่างต่อเนื่อง ควรมีระยะเวลาเท่าไร? ขึ้นอยู่กับคุณและขึ้นอยู่กับกรณีไป

ฉันรู้ว่ามันเป็นวิธีที่น่าเกลียด แต่นี่คือวิธีที่เป็นอยู่ ความท้าทายอย่างหนึ่งอาจเป็นได้ว่าหากอุปกรณ์ของผู้ใช้อยู่ในโหมด Doze หรือแอปอยู่ในสถานะสแตนด์บายงานของคุณอาจล่าช้า


นอกจากนี้โปรดทราบว่าใน EMUI ที่กำหนดเองอย่างหนัก MIUI Android OS workManager (งานเป็นระยะ) ไม่จำเป็นต้องทำงานอย่างถูกต้องเสมอไป
Kebab Krabby

1

เมื่อเราลงทะเบียนการโทรกลับเครือข่ายโดยใช้registerNetworkCallbackวิธีนี้บางครั้งมันไม่ได้ทริกเกอร์และบางครั้งมันก็ทริกเกอร์บวกเท็จ:

  1. หากเราเริ่มแอพด้วยการเชื่อมต่ออินเทอร์เน็ตonAvailableเมธอดจะทริกเกอร์
  2. แต่ถ้าไม่มีการเชื่อมต่ออินเทอร์เน็ตบนอุปกรณ์เมื่อเราเริ่มแอพก็ไม่มีอะไรNetworkCallbackเรียกเลย (มันแปลกมากเพราะหน้า 1)
  3. หากเรามีการเชื่อมต่อ wifi แต่ไม่มีonAvailableทริกเกอร์วิธีการเชื่อมต่ออินเทอร์เน็ต และฉันคิดว่ามันเป็นพฤติกรรมที่ผิดพลาดเพราะเราคาดว่าการเชื่อมต่ออินเทอร์เน็ตจะสังเกตได้

ดังที่คุณเห็นในรหัสด้านล่างโดยการเชื่อมต่ออินเทอร์เน็ตเริ่มต้นพร้อมใช้งานและจะทริกเกอร์ก็ต่อเมื่อมีการเปลี่ยนแปลง ไม่มีทริกเกอร์บวกที่ผิดพลาด

สรุปสิ่งนี้และคำตอบนี้ (แต่สำหรับ API> = 21 เท่านั้น):

class ConnectionManager @Inject constructor(
    private val connectivityManager: ConnectivityManager,
    private val disposable: CompositeDisposable,
    private val singleTransformer: SingleTransformer<*, *>
) : LiveData<Boolean>() {

    private var isNetworkAvailable = true

    private val builder = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)

    private val callback = object : ConnectivityManager.NetworkCallback() {

        override fun onAvailable(network: Network) {
            ping()
        }

        override fun onLost(network: Network) {
            ping()
        }
    }

    private fun ping() {
        disposable.add(
            Single.fromCallable {
                try {
                    val timeoutMs = 1500
                    val socket = Socket()
                    val socketAddress = InetSocketAddress("8.8.8.8", 53)

                    socket.connect(socketAddress, timeoutMs)
                    socket.close()
                    true
                } catch (e: IOException) {
                    false
                }
            }
                .compose(singleTransformer as SingleTransformer<Boolean, Boolean>)
                .subscribeBy {
                    if (isNetworkAvailable != it){
                        value = it
                        isNetworkAvailable = it
                    }
                }
        )
    }

    override fun onActive() {
        ping()
        connectivityManager.registerNetworkCallback(builder.build(), callback)
    }

    override fun onInactive() {
        disposable.clear()
        connectivityManager.unregisterNetworkCallback(callback)
    }
}

วิธีการให้การอ้างอิง

@Provides
fun provideTransformer(): SingleTransformer<Boolean, Boolean> {
    return SingleTransformer<Boolean, Boolean> { upstream: Single<Boolean> ->
        upstream.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
    }
}

@Singleton
@Provides
fun provideConnectivityManager(context: Context): ConnectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

@Singleton
@Provides
fun provideConnectionManager(connectivityManager: ConnectivityManager, singleTransformer: SingleTransformer<Boolean, Boolean>): ConnectionManager =
        ConnectionManager(connectivityManager, singleTransformer)

และวิธีใช้:

@Inject
lateinit var connectionManager: ConnectionManager

//....

viewLifecycleOwner.observe(connectionManager) { isInternetAvailable ->
    // TODO 
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.