(一百七十五)Android P registerNetworkCallback

2023-12-19 07:32

本文主要是介绍(一百七十五)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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/511369

相关文章

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Android 实现一个隐私弹窗功能

《Android实现一个隐私弹窗功能》:本文主要介绍Android实现一个隐私弹窗功能,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 效果图如下:1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来res/l

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl