ConnectivityManager.CONNECTIVITY_ACTION không được dùng nữa


94

Trong Android N, trên trang web chính thức có đề cập đến "Các ứng dụng nhắm mục tiêu đến Android N không nhận được chương trình phát sóng CONNECTIVITY_ACTION". Và nó cũng được đề cập rằng JobSchedulercó thể được sử dụng như một sự thay thế. Nhưng JobSchedulerkhông cung cấp chính xác hành vi giống nhưCONNECTIVITY_ACTION phát sóng.

Trong ứng dụng Android của mình, tôi đang sử dụng chương trình phát sóng này để biết trạng thái mạng của thiết bị. Tôi muốn biết nếu trạng thái này là CONNECTINGhoặc CONNECTEDvới sự giúp đỡ củaCONNECTIVITY_ACTION phát sóng và nó phù hợp nhất với yêu cầu của tôi.

Bây giờ nó không được dùng nữa, bất kỳ ai có thể gợi ý cho tôi cách tiếp cận thay thế để có được trạng thái mạng hiện tại không?


10
Và điều gì sẽ xảy ra nếu một ngày nào đó OP muốn một số hành vi yêu cầu nâng cấp targetSdkVersionlên N hoặc mới hơn?
Michael

1
Chà, tôi cũng biết rằng nếu tôi không nhắm mục tiêu ứng dụng của mình vào Android NI sẽ nhận được chương trình phát sóng. Nhưng ứng dụng của tôi cần hỗ trợ Android N. Làm cách nào để tôi có thể có cùng hành vi phát sóng trong Android N? Có cách tiếp cận nào khác mà tôi có thể thử không? @DavidWasser
Raghuram db

Đôi khi tôi nghĩ lo lắng về tương lai trong tương lai sẽ có ý nghĩa hơn. Đây hoàn toàn là một cách tiếp cận thực dụng để lập trình. Tất nhiên, bạn luôn có thể cố gắng đảm bảo rằng mã của bạn không sử dụng bất kỳ tính năng không dùng nữa. Mặt khác, các tính năng không dùng nữa thường tồn tại trong một thời gian dài và có thể ứng dụng của bạn sẽ hết tuổi thọ trước khi các tính năng không dùng nữa biến mất. Android N quá mới nên tôi sẽ không mất nhiều thời gian lo lắng về nó. Chưa. Chỉ 2 xu của tôi. Xin lưu ý rằng tôi đã viết bình luận cho câu hỏi và không gợi ý rằng "không làm vậy" là một câu trả lời hợp lệ.
David Wasser

2
@Raghuramdb Ứng dụng của bạn có thể chạy trên Android N ngay cả khi bạn không nhắm mục tiêu ứng dụng của mình đến Android N. Bạn chỉ phải nhắm mục tiêu Android N nếu bạn muốn sử dụng các tính năng chỉ có trong Android N.
David Wasser

2
Bạn vẫn có thể sử dụng BroadcastReceivervới android.net.conn.CONNECTIVITY_CHANGEbộ lọc ý định ngay cả khi nhắm mục tiêu API29, bạn chỉ cần đăng ký nó Application.OnCreate. Bạn sẽ không nhận được bất kỳ bản cập nhật nào khi ứng dụng bị đóng.
Pierre

Câu trả lời:


97

Điều sẽ không được dùng nữa là khả năng ứng dụng chạy nền nhận các thay đổi trạng thái kết nối mạng.

Như David Wasser đã nói, bạn vẫn có thể nhận được thông báo về các thay đổi kết nối nếu thành phần ứng dụng được khởi tạo (không bị phá hủy) và bạn đã đăng ký bộ thu của mình theo chương trình với ngữ cảnh của nó, thay vì thực hiện trong tệp kê khai.

Hoặc bạn có thể sử dụng NetworkCallback để thay thế. Đặc biệt, bạn sẽ cần ghi đè onAvailable để thay đổi trạng thái được kết nối.

Hãy để tôi viết nhanh một đoạn mã:

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
Vì kỹ thuật này sẽ chỉ có thể hoạt động, nếu ứng dụng đang chạy ở chế độ nền. Điều đó có nghĩa là chúng ta không còn khả năng lắng nghe sự kiện kết nối khi ứng dụng không chạy ở chế độ nền? Có <action android: name = "android.net.conn.CONNECTIVITY_CHANGE" /> trong manifest.xml không còn có bất kỳ tác dụng trong Android N.
Cheok Yan Cheng

2
@CheokYanCheng AFAIK đó là chính xác. Bạn cần có một quy trình chạy ở phía trước để lắng nghe các sự kiện kết nối. Có vẻ như giả định được đưa ra bởi các kỹ sư khung Android là việc lắng nghe các sự kiện kết nối chủ yếu được thực hiện để biết khi nào bắt đầu đồng bộ hóa dữ liệu giữa máy khách và máy chủ. Do đó, JobScheduler là cách được khuyến nghị cho trường hợp sử dụng đó.
Amokrane Chentir

25
lol cái quái gì vậy, 10 bản cập nhật Android khác và tất cả những gì chúng tôi có thể viết là một ứng dụng hello world
DennisVA.

1
Tôi có cần hủy đăng ký NetworkCallback (ví dụ: trong phương thức onDestroy của hoạt động) không?
Ruslan Berozov

2
@Ruslan tất nhiên là có, hoặc bạn sẽ bị rò rỉ bất cứ thứ gì đã đăng ký
DennisVA 13/02/19

34

Tôi sẽ cập nhật Sayem'scâu trả lời để khắc phục sự cố lint hiển thị cho tôi.

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)
    }
}

Và cách sử dụng tương tự:

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

Btw cảm ơn sayem cho giải pháp của bạn.


2
Giải pháp tuyệt vời!
hộp

2
Giải pháp rất tốt để sử dụng dữ liệu trực tiếp và hỗ trợ phiên bản cũ hơn
Prakash Shukla

Đây là giải pháp tốt nhất hiện có trên internet.
Karan Sharma

Giải pháp rất tốt! NHƯNG có một "không phải" - đó là cách sử dụng phương thức onAvailable (mạng: Mạng?) Sai, vì nó gọi ngay cả Internet cũng không khả dụng. Tốt hơn là sử dụng onCapabilitiesChanged (network: Network, networkCapabilities: NetworkCapabilities) và kiểm tra networkCapabilities.hasCapability (NET_CAPABILITY_INTERNET) và networkCapabilities.hasCapability (NET_CAPABILITY_VALIDATED).
DmitryKanunnikoff

Làm thế nào để lấy ip và loại mạng trong cơ sở mã này?
A_rmas

28

Tài liệu dành cho Android N cho biết:

Các ứng dụng nhắm mục tiêu đến Android N không nhận được chương trình phát sóng CONNECTIVITY_ACTION, ngay cả khi chúng có mục nhập tệp kê khai để yêu cầu thông báo về những sự kiện này. Các ứng dụng đang chạy ở nền trước vẫn có thể lắng nghe CONNECTIVITY_CHANGE trên chuỗi chính của chúng nếu chúng yêu cầu thông báo bằng BroadcastReceiver.

Điều này có nghĩa là bạn vẫn có thể đăng ký BroadcastReceivernếu ứng dụng của bạn đang chạy ở nền trước, để phát hiện những thay đổi trong kết nối mạng.


Đẹp bắt tinh tế :)
Amokrane Chentir

Điều này có nghĩa là ứng dụng sẽ ngừng nhận các chương trình phát sóng khi nó không ở phía trước? (vì vậy tôi không thể nghe nó trong một dịch vụ chẳng hạn?)
sundie

1
Tôi không biết chắc chắn, tôi sẽ cần phải kiểm tra nó để chắc chắn. Tuy nhiên, đọc tài liệu sẽ thấy rằng nếu ứng dụng của bạn không có ở nền trước, thì bạn sẽ không nhận được chương trình phát sóng Intent.
David Wasser

2
Nhưng để phát hiện thay đổi kết nối trong nền là điều bắt buộc đối với bất kỳ ứng dụng nhâm nhi (VoIP) nào ... những ứng dụng đó thường chạy ở chế độ nền trong nhiều ngày và chỉ chuyển lên nền trước khi có cuộc gọi đến (giống như trình quay số trên điện thoại của bạn) .. . Các ứng dụng đó cần tự động kết nối lại trong nền. Điều này sẽ giết tất cả các ứng dụng đó (không có máy chủ đẩy riêng) khỏi nền tảng Android vì chúng sẽ ngoại tuyến. luôn luôn.
Grisgram

chỉ cần sử dụng dịch vụ đẩy firebase.
Pierre

21

Vui lòng kiểm tra câu trả lời @Amokrane Chentir trước để được hỗ trợ android N.

Đối với những người muốn hỗ trợ ở tất cả cấp độ api và quan sát nó trong ui, vui lòng kiểm tra mã bên dưới.

LiveData của 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)
        }
    }
}

quan sát trong giao diện người dùng (Hoạt động / Phân mảnh):

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

btw, bạn không cần phải xác định IntentFilterrõ ràng. Như vậy:var intentFilter = IntentFilter(CONNECTIVITY_ACTION)
Ryan Amaral

cảm ơn cho đề nghị của bạn. Tôi không muốn tạo đối tượng mọi lúc trong onActive.
Sayem

Ý tôi là 2 biến / thuộc tính toàn cục ( intentFilterconnectivityManager) bạn không cần xác định rõ ràng kiểu của chúng ( IntentFilterConnectivityManagertương ứng).
Ryan Amaral

7

Tôi gặp phải vấn đề tương tự vài ngày trước và tôi quyết định sử dụng thư viện này Android-Job

Đây thư viện sử dụng JobSchedular, GcmNetworkManagerBroadcastReceivertùy thuộc vào phiên bản Android ứng dụng đang chạy trên.

Bắt đầu một công việc khá dễ dàng

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

1
Tôi đã thử cùng một trình lập lịch và nhận được ngoại lệ như thế này Bạn đang cố gắng xây dựng một công việc không có ràng buộc, điều này không được phép. bạn có thể vui lòng giúp chúng tôi để giải quyết điều này ??
Sanket Kachhela

Sử dụng Android-Job cho mục đích này thực sự không phải là một giải pháp tốt. Nó có nghĩa là để chạy mọi thứ vào một thời điểm xác định, một lần hoặc định kỳ. Nó có nghĩa là mang lại hỗ trợ tương thích ngược cho Báo thức và những thứ tương tự. Điều này đi ngược lại toàn bộ ý tưởng về lý do tại sao API thay đổi và đọc: developer.android.com/training/monitoring-device-state/… Bạn có thể nhanh chóng hiểu được lý do tại sao.
pedronveloso,

chỉ có một vấn đề là trong Android N, nó chỉ có thể được lên lịch trong tối thiểu 15 phút trong tương lai
Fire Crow

4

Tôi đã viết một triển khai Kotlin dựa trên câu trả lời của Sayam nhưng không có LiveData. Tôi quyết định gọi (tại thời điểm này) phương thức API mới nhất ( ConnectivityManager#registerDefaultNetworkCallback) nhắm mục tiêu đến 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)
        }
    }

}

Sử dụng:

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

hoặc là:

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

Đừng quên thêm ACCESS_NETWORK_STATEquyền vào AndroidManifest.xml của bạn :

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

Tôi rất mong được đọc những nhận xét và cải tiến hữu ích từ bạn.


1
Tôi đã phải thay đổi một số thứ cho các lệnh gọi lại để có thể "chạm vào chế độ xem" trong Hoạt động (ngữ cảnh) trên chuỗi chính: (context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } })thay vì onConnectionAvailable.invoke(). Tương tự cho onConnectionLost.invoke().
Андрей Воробьев

Có, tùy thuộc vào trường hợp sử dụng của bạn, bạn có thể cần chuyển đổi chuỗi. Tôi sẽ không biến nó thành một phần của lớp mà thay vào đó để người tiêu dùng của lớp lo việc này. Nhưng cảm ơn bạn đã gợi ý.
JJD

2

Các ứng dụng nhắm mục tiêu Android N (Nougat) không nhận được các CONNECTIVITY_ACTIONchương trình phát sóng được xác định trong tệp kê khai (xem Svelte ).

Phương pháp khả thi:

Xem thêm Android O - Phát hiện sự thay đổi kết nối trong nền


1

Tôi đồng ý với câu trả lời do @rds gợi ý.

Xin lưu ý rằng CONNECTIVITY_ACTION không được dùng nữa trong API cấp 28.

Nếu bạn có yêu cầu rằng trạng thái Wifi (kết nối / ngắt kết nối) phải được phát hiện mặc dù ứng dụng đã bị hủy và bạn muốn nhắm mục tiêu phiên bản mới nhất thì bạn không có nhiều lựa chọn.

Bạn cần sử dụng connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

Câu hỏi là bạn không thể sử dụng BroadcastReceiver, vậy làm thế nào sau đó?

Bạn có thể sử dụng JobScheduler hoặc tốt hơn nếu WorkManager (Yêu cầu định kỳ). Tại sao Định kỳ vì nếu đó là OneTimeRequest thì nó sẽ chỉ có thể chạy một lần và tiếp tục lắng nghe trong khi ứng dụng của bạn ở nền trước.

Tài liệu cho biết:

Các lệnh gọi lại sẽ tiếp tục được gọi cho đến khi ứng dụng thoát hoặc liên kết #unregisterNetworkCallback (NetworkCallback)} được gọi.

Sau khi ứng dụng bị giết hoặc bị xóa khỏi danh sách ứng dụng gần đây, networkCallback sẽ không thể nghe.

Vì vậy, bạn cần những công việc định kỳ như vậy để ứng dụng liên tục lắng nghe. Khoảng thời gian nên là bao nhiêu? Đó là tùy thuộc vào bạn và tùy thuộc vào từng trường hợp.

Tôi biết nó là một cách hơi xấu xí nhưng đây là cách nó được. Một thách thức có thể là nếu thiết bị của người dùng ở chế độ Doze hoặc ứng dụng ở Trạng thái chờ, công việc của bạn có thể bị trì hoãn.


Cũng nên nhớ rằng trên một số EMUI được tùy chỉnh nhiều, MIUI Android OS workManager (tác vụ định kỳ) không nhất thiết phải luôn hoạt động chính xác.
Kebab Krabby

1

Khi chúng tôi đăng ký một cuộc gọi lại mạng bằng registerNetworkCallbackphương pháp này, đôi khi nó không kích hoạt và đôi khi nó kích hoạt dương tính giả:

  1. Nếu chúng tôi khởi động ứng dụng bằng kết nối internet, onAvailablephương thức sẽ kích hoạt.
  2. Nhưng nếu không có kết nối internet trên thiết bị khi chúng tôi khởi động một ứng dụng thì sẽ không có gì NetworkCallbackđược gọi là (rất lạ vì trang 1)
  3. Nếu chúng ta có kết nối wifi nhưng không có onAvailablekích hoạt phương thức kết nối internet . Và tôi nghĩ đó là hành vi tích cực giả bởi vì chúng tôi mong đợi sự quan sát của kết nối internet.

Như bạn thấy trong đoạn mã dưới đây theo mặc định kết nối internet khả dụng và nó chỉ kích hoạt nếu nó thay đổi. Không có kích hoạt dương tính giả.

Chỉ tóm tắt điều nàynày câu trả lời (nhưng chỉ cho 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)
    }
}

Cách cung cấp các phụ thuộc

@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)

Và cách sử dụng:

@Inject
lateinit var connectionManager: ConnectionManager

//....

viewLifecycleOwner.observe(connectionManager) { isInternetAvailable ->
    // TODO 
}

1

Dựa trên câu trả lời của @ 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)
        }
    }
}

Và cách sử dụng nó gần giống như trong câu trả lời ban đầu (ví dụ: nếu quan sát từ một Hoạt động):

ConnectivityWatcher(this).observe(this, Observer {
    Log.i("*-*-*", "is internet available? - ${if (it) "Yes" else "No"}")
})
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.