本文主要是介绍(一百七十五)Android P registerNetworkCallback,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1. API
ConnectivityManager的api,用来接收满足NetworkRequest的所有网络通知,除非应用退出或者调用了
/*** Registers to receive notifications about all networks which satisfy the given* {@link NetworkRequest}. The callbacks will continue to be called until* either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.** @param request {@link NetworkRequest} describing this request.* @param networkCallback The {@link NetworkCallback} that the system will call as suitable* networks change state.* The callback is invoked on the default internal Handler.*/@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback) {registerNetworkCallback(request, networkCallback, getDefaultHandler());}/*** Registers to receive notifications about all networks which satisfy the given* {@link NetworkRequest}. The callbacks will continue to be called until* either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.** @param request {@link NetworkRequest} describing this request.* @param networkCallback The {@link NetworkCallback} that the system will call as suitable* networks change state.* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.*/@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback, Handler handler) {CallbackHandler cbHandler = new CallbackHandler(handler);NetworkCapabilities nc = request.networkCapabilities;sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler);}
NetworkCallback就是个回调,应用可以复写用来实现自己的逻辑
/*** Base class for {@code NetworkRequest} callbacks. Used for notifications about network* changes. Should be extended by applications wanting notifications.** A {@code NetworkCallback} is registered by calling* {@link #requestNetwork(NetworkRequest, NetworkCallback)},* {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)},* or {@link #registerDefaultNetworkCallback(NetworkCallback)}. A {@code NetworkCallback} is* unregistered by calling {@link #unregisterNetworkCallback(NetworkCallback)}.* A {@code NetworkCallback} should be registered at most once at any time.* A {@code NetworkCallback} that has been unregistered can be registered again.*/public static class NetworkCallback {
2. 梳理流程
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,int timeoutMs, int action, int legacyType, CallbackHandler handler) {checkCallbackNotNull(callback);Preconditions.checkArgument(action == REQUEST || need != null, "null NetworkCapabilities");final NetworkRequest request;try {synchronized(sCallbacks) {if (callback.networkRequest != null&& callback.networkRequest != ALREADY_UNREGISTERED) {// TODO: throw exception instead and enforce 1:1 mapping of callbacks// and requests (http://b/20701525).Log.e(TAG, "NetworkCallback was already registered");}Messenger messenger = new Messenger(handler);Binder binder = new Binder();if (action == LISTEN) {request = mService.listenForNetwork(need, messenger, binder);} else {request = mService.requestNetwork(need, messenger, timeoutMs, binder, legacyType);}if (request != null) {sCallbacks.put(request, callback);}callback.networkRequest = request;}} catch (RemoteException e) {throw e.rethrowFromSystemServer();} catch (ServiceSpecificException e) {throw convertServiceException(e);}return request;}
继而调用到了ConnectivityService
@Overridepublic NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,Messenger messenger, IBinder binder) {if (!hasWifiNetworkListenPermission(networkCapabilities)) {enforceAccessPermission();}NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);ensureSufficientPermissionsForRequest(networkCapabilities,Binder.getCallingPid(), Binder.getCallingUid());restrictRequestUidsForCaller(nc);// Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so// make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get// onLost and onAvailable callbacks when networks move in and out of the background.// There is no need to do this for requests because an app without CHANGE_NETWORK_STATE// can't request networks.restrictBackgroundRequestForCaller(nc);ensureValidNetworkSpecifier(nc);NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),NetworkRequest.Type.LISTEN);NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder);if (VDBG) log("listenForNetwork for " + nri);mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));return networkRequest;}
继而发出一个消息并处理
private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {mNetworkRequests.put(nri.request, nri);mNetworkRequestInfoLogs.log("REGISTER " + nri);if (nri.request.isListen()) {for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {if (nri.request.networkCapabilities.hasSignalStrength() &&network.satisfiesImmutableCapabilitiesOf(nri.request)) {updateSignalStrengthThresholds(network, "REGISTER", nri.request);}}}rematchAllNetworksAndRequests(null, 0);if (nri.request.isRequest() && getNetworkForRequest(nri.request.requestId) == null) {sendUpdatedScoreToFactories(nri.request, 0);}}
特殊判断的先不看,看下rematchAllNetworksAndRequests
/*** Attempt to rematch all Networks with NetworkRequests. This may result in Networks* being disconnected.* @param changed If only one Network's score or capabilities have been modified since the last* time this function was called, pass this Network in this argument, otherwise pass* null.* @param oldScore If only one Network has been changed but its NetworkCapabilities have not* changed, pass in the Network's score (from getCurrentScore()) prior to the change via* this argument, otherwise pass {@code changed.getCurrentScore()} or 0 if* {@code changed} is {@code null}. This is because NetworkCapabilities influence a* network's score.*/private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore) {// TODO: This may get slow. The "changed" parameter is provided for future optimization// to avoid the slowness. It is not simply enough to process just "changed", for// example in the case where "changed"'s score decreases and another network should begin// satifying a NetworkRequest that "changed" currently satisfies.// Optimization: Only reprocess "changed" if its score improved. This is safe because it// can only add more NetworkRequests satisfied by "changed", and this is exactly what// rematchNetworkAndRequests() handles.final long now = SystemClock.elapsedRealtime();if (changed != null && oldScore < changed.getCurrentScore()) {rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP, now);} else {final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(new NetworkAgentInfo[mNetworkAgentInfos.size()]);// Rematch higher scoring networks first to prevent requests first matching a lower// scoring network and then a higher scoring network, which could produce multiple// callbacks and inadvertently unlinger networks.Arrays.sort(nais);for (NetworkAgentInfo nai : nais) {rematchNetworkAndRequests(nai,// Only reap the last time through the loop. Reaping before all rematching// is complete could incorrectly teardown a network that hasn't yet been// rematched.(nai != nais[nais.length-1]) ? ReapUnvalidatedNetworks.DONT_REAP: ReapUnvalidatedNetworks.REAP,now);}}}
传来的changed为null,所以走下面的判断,其实都是走一个方法rematchNetworkAndRequests
// Handles a network appearing or improving its score.//// - Evaluates all current NetworkRequests that can be// satisfied by newNetwork, and reassigns to newNetwork// any such requests for which newNetwork is the best.//// - Lingers any validated Networks that as a result are no longer// needed. A network is needed if it is the best network for// one or more NetworkRequests, or if it is a VPN.//// - Tears down newNetwork if it just became validated// but turns out to be unneeded.//// - If reapUnvalidatedNetworks==REAP, tears down unvalidated// networks that have no chance (i.e. even if validated)// of becoming the highest scoring network.//// NOTE: This function only adds NetworkRequests that "newNetwork" could satisfy,// it does not remove NetworkRequests that other Networks could better satisfy.// If you need to handle decreases in score, use {@link rematchAllNetworksAndRequests}.// This function should be used when possible instead of {@code rematchAllNetworksAndRequests}// as it performs better by a factor of the number of Networks.//// @param newNetwork is the network to be matched against NetworkRequests.// @param reapUnvalidatedNetworks indicates if an additional pass over all networks should be// performed to tear down unvalidated networks that have no chance (i.e. even if// validated) of becoming the highest scoring network.private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork,ReapUnvalidatedNetworks reapUnvalidatedNetworks, long now) {if (!newNetwork.everConnected) return;boolean keep = newNetwork.isVPN();boolean isNewDefault = false;NetworkAgentInfo oldDefaultNetwork = null;final boolean wasBackgroundNetwork = newNetwork.isBackgroundNetwork();final int score = newNetwork.getCurrentScore();if (VDBG) log("rematching " + newNetwork.name());// Find and migrate to this Network any NetworkRequests for// which this network is now the best.ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<NetworkRequestInfo>();NetworkCapabilities nc = newNetwork.networkCapabilities;if (VDBG) log(" network has: " + nc);for (NetworkRequestInfo nri : mNetworkRequests.values()) {// Process requests in the first pass and listens in the second pass. This allows us to// change a network's capabilities depending on which requests it has. This is only// correct if the change in capabilities doesn't affect whether the network satisfies// requests or not, and doesn't affect the network's score.if (nri.request.isListen()) continue;final NetworkAgentInfo currentNetwork = getNetworkForRequest(nri.request.requestId);final boolean satisfies = newNetwork.satisfies(nri.request);if (newNetwork == currentNetwork && satisfies) {if (VDBG) {log("Network " + newNetwork.name() + " was already satisfying" +" request " + nri.request.requestId + ". No change.");}keep = true;continue;}// check if it satisfies the NetworkCapabilitiesif (VDBG) log(" checking if request is satisfied: " + nri.request);if (satisfies) {// next check if it's better than any current network we're using for// this requestif (VDBG) {log("currentScore = " +(currentNetwork != null ? currentNetwork.getCurrentScore() : 0) +", newScore = " + score);}if (currentNetwork == null || currentNetwork.getCurrentScore() < score) {if (VDBG) log("rematch for " + newNetwork.name());if (currentNetwork != null) {if (VDBG) log(" accepting network in place of " + currentNetwork.name());currentNetwork.removeRequest(nri.request.requestId);currentNetwork.lingerRequest(nri.request, now, mLingerDelayMs);affectedNetworks.add(currentNetwork);} else {if (VDBG) log(" accepting network in place of null");}newNetwork.unlingerRequest(nri.request);setNetworkForRequest(nri.request.requestId, newNetwork);if (!newNetwork.addRequest(nri.request)) {Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);}addedRequests.add(nri);keep = true;// Tell NetworkFactories about the new score, so they can stop// trying to connect if they know they cannot match it.// TODO - this could get expensive if we have alot of requests for this// network. Think about if there is a way to reduce this. Push// netid->request mapping to each factory?sendUpdatedScoreToFactories(nri.request, score);if (isDefaultRequest(nri)) {isNewDefault = true;oldDefaultNetwork = currentNetwork;if (currentNetwork != null) {mLingerMonitor.noteLingerDefaultNetwork(currentNetwork, newNetwork);}}}} else if (newNetwork.isSatisfyingRequest(nri.request.requestId)) {// If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri",// mark it as no longer satisfying "nri". Because networks are processed by// rematchAllNetworksAndRequests() in descending score order, "currentNetwork" will// match "newNetwork" before this loop will encounter a "currentNetwork" with higher// score than "newNetwork" and where "currentNetwork" no longer satisfies "nri".// This means this code doesn't have to handle the case where "currentNetwork" no// longer satisfies "nri" when "currentNetwork" does not equal "newNetwork".if (DBG) {log("Network " + newNetwork.name() + " stopped satisfying" +" request " + nri.request.requestId);}newNetwork.removeRequest(nri.request.requestId);if (currentNetwork == newNetwork) {clearNetworkForRequest(nri.request.requestId);sendUpdatedScoreToFactories(nri.request, 0);} else {Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +newNetwork.name() +" without updating mNetworkForRequestId or factories!");}// TODO: Technically, sending CALLBACK_LOST here is// incorrect if there is a replacement network currently// connected that can satisfy nri, which is a request// (not a listen). However, the only capability that can both// a) be requested and b) change is NET_CAPABILITY_TRUSTED,// so this code is only incorrect for a network that loses// the TRUSTED capability, which is a rare case.callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST, 0);}}if (isNewDefault) {// Notify system services that this network is up.makeDefault(newNetwork);// Log 0 -> X and Y -> X default network transitions, where X is the new default.metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, newNetwork, oldDefaultNetwork);// Have a new default network, release the transition wakelock inscheduleReleaseNetworkTransitionWakelock();}if (!newNetwork.networkCapabilities.equalRequestableCapabilities(nc)) {Slog.wtf(TAG, String.format("BUG: %s changed requestable capabilities during rematch: %s -> %s",newNetwork.name(), nc, newNetwork.networkCapabilities));}if (newNetwork.getCurrentScore() != score) {Slog.wtf(TAG, String.format("BUG: %s changed score during rematch: %d -> %d",newNetwork.name(), score, newNetwork.getCurrentScore()));}// Second pass: process all listens.if (wasBackgroundNetwork != newNetwork.isBackgroundNetwork()) {// If the network went from background to foreground or vice versa, we need to update// its foreground state. It is safe to do this after rematching the requests because// NET_CAPABILITY_FOREGROUND does not affect requests, as is not a requestable// capability and does not affect the network's score (see the Slog.wtf call above).updateCapabilities(score, newNetwork, newNetwork.networkCapabilities);} else {processListenRequests(newNetwork, false);}// do this after the default net is switched, but// before LegacyTypeTracker sends legacy broadcastsfor (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);// Linger any networks that are no longer needed. This should be done after sending the// available callback for newNetwork.for (NetworkAgentInfo nai : affectedNetworks) {updateLingerState(nai, now);}// Possibly unlinger newNetwork. Unlingering a network does not send any callbacks so it// does not need to be done in any particular order.updateLingerState(newNetwork, now);if (isNewDefault) {// Maintain the illusion: since the legacy API only// understands one network at a time, we must pretend// that the current default network disconnected before// the new one connected.if (oldDefaultNetwork != null) {mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),oldDefaultNetwork, true);}mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0;mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);notifyLockdownVpn(newNetwork);}if (keep) {// Notify battery stats service about this network, both the normal// interface and any stacked links.// TODO: Avoid redoing this; this must only be done once when a network comes online.try {final IBatteryStats bs = BatteryStatsService.getService();final int type = newNetwork.networkInfo.getType();final String baseIface = newNetwork.linkProperties.getInterfaceName();bs.noteNetworkInterfaceType(baseIface, type);for (LinkProperties stacked : newNetwork.linkProperties.getStackedLinks()) {final String stackedIface = stacked.getInterfaceName();bs.noteNetworkInterfaceType(stackedIface, type);}} catch (RemoteException ignored) {}// This has to happen after the notifyNetworkCallbacks as that tickles each// ConnectivityManager instance so that legacy requests correctly bind dns// requests to this network. The legacy users are listening for this bcast// and will generally do a dns request so they can ensureRouteToHost and if// they do that before the callbacks happen they'll use the default network.//// TODO: Is there still a race here? We send the broadcast// after sending the callback, but if the app can receive the// broadcast before the callback, it might still break.//// This *does* introduce a race where if the user uses the new api// (notification callbacks) and then uses the old api (getNetworkInfo(type))// they may get old info. Reverse this after the old startUsing api is removed.// This is on top of the multiple intent sequencing referenced in the todo above.for (int i = 0; i < newNetwork.numNetworkRequests(); i++) {NetworkRequest nr = newNetwork.requestAt(i);if (nr.legacyType != TYPE_NONE && nr.isRequest()) {// legacy type tracker filters out repeat addsmLegacyTypeTracker.add(nr.legacyType, newNetwork);}}// A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above,// because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest// wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the// newNetwork to the tracker explicitly (it's a no-op if it has already been added).if (newNetwork.isVPN()) {mLegacyTypeTracker.add(TYPE_VPN, newNetwork);}}if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {if (unneeded(nai, UnneededFor.TEARDOWN)) {if (nai.getLingerExpiry() > 0) {// This network has active linger timers and no requests, but is not// lingering. Linger it.//// One way (the only way?) this can happen if this network is unvalidated// and became unneeded due to another network improving its score to the// point where this network will no longer be able to satisfy any requests// even if it validates.updateLingerState(nai, now);} else {if (DBG) log("Reaping " + nai.name());teardownUnneededNetwork(nai);}}}}}
一大摞的代码看到不是很懂,大概意思是
check if it satisfies the NetworkCapabilities,大概就是比较下现有网络和新网络哪个分高,新网络分高的话更新一下
Second pass: process all listens.
其实这是比较想关注的,大概就是网络更新了通知一下
// Second pass: process all listens.if (wasBackgroundNetwork != newNetwork.isBackgroundNetwork()) {// If the network went from background to foreground or vice versa, we need to update// its foreground state. It is safe to do this after rematching the requests because// NET_CAPABILITY_FOREGROUND does not affect requests, as is not a requestable// capability and does not affect the network's score (see the Slog.wtf call above).updateCapabilities(score, newNetwork, newNetwork.networkCapabilities);} else {processListenRequests(newNetwork, false);}// do this after the default net is switched, but// before LegacyTypeTracker sends legacy broadcastsfor (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
看下processListenRequests(这里比较牵强,debug 新网络连接会走这里)
private void processListenRequests(NetworkAgentInfo nai, boolean capabilitiesChanged) {// For consistency with previous behaviour, send onLost callbacks before onAvailable.for (NetworkRequestInfo nri : mNetworkRequests.values()) {NetworkRequest nr = nri.request;if (!nr.isListen()) continue;if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {nai.removeRequest(nri.request.requestId);callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);}}if (capabilitiesChanged) {notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);}for (NetworkRequestInfo nri : mNetworkRequests.values()) {NetworkRequest nr = nri.request;if (!nr.isListen()) continue;if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {nai.addRequest(nr);notifyNetworkAvailable(nai, nri);}}}
这个和我们的registerNetworkCallback有些相关,都是listen状态,大概就是网络变了调用更新一下监听器
// Notify only this one new request of the current state. Transfer all the// current state by calling NetworkCapabilities and LinkProperties callbacks// so that callers can be guaranteed to have as close to atomicity in state// transfer as can be supported by this current API.protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);if (nri.mPendingIntent != null) {sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);// Attempt no subsequent state pushes where intents are involved.return;}callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0);}private void callCallbackForRequest(NetworkRequestInfo nri,NetworkAgentInfo networkAgent, int notificationType, int arg1) {if (nri.messenger == null) {return; // Default request has no msgr}Bundle bundle = new Bundle();// TODO: check if defensive copies of data is needed.putParcelable(bundle, new NetworkRequest(nri.request));Message msg = Message.obtain();if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {putParcelable(bundle, networkAgent.network);}switch (notificationType) {case ConnectivityManager.CALLBACK_AVAILABLE: {putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities));putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));break;}case ConnectivityManager.CALLBACK_LOSING: {msg.arg1 = arg1;break;}case ConnectivityManager.CALLBACK_CAP_CHANGED: {// networkAgent can't be null as it has been accessed a few lines above.final NetworkCapabilities nc = networkCapabilitiesRestrictedForCallerPermissions(networkAgent.networkCapabilities, nri.mPid, nri.mUid);putParcelable(bundle, nc);break;}case ConnectivityManager.CALLBACK_IP_CHANGED: {putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));break;}}msg.what = notificationType;msg.setData(bundle);try {if (VDBG) {String notification = ConnectivityManager.getCallbackName(notificationType);log("sending notification " + notification + " for " + nri.request);}nri.messenger.send(msg);} catch (RemoteException e) {// may occur naturally in the race of binder death.loge("RemoteException caught trying to send a callback msg for " + nri.request);}}
看下ConnectivityManager对CALLBACK_AVAILABLE的处理,其实就是回调了下callback的onAvailable
case CALLBACK_AVAILABLE: {NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);LinkProperties lp = getObject(message, LinkProperties.class);callback.onAvailable(network, cap, lp);break;}
2.总结
调试发现新网络的建立和断开也会走下面红框框出来的流程,即网络状态发生变化了会走如下流程,结合之前WiFi连接上的经验猜是updateNetworkInfo调用到的,因为WiFi连接上了之后会把networkInfo更新到CS这边,后续debug看下
这篇关于(一百七十五)Android P registerNetworkCallback的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!