There is no problem with mobile phones below Android 9. Videos can be played normally, including our SDK for apk connection to advertising channels and advertising. The main reason behind this may be that the advertising address is HTTP rather than HTTPS. Android P requires that the network request must be HTTPS, and the HTTP request will throw an exception.
The following is a reference blog post:
Source code analysis Android 9.0 http request adaptation principle - brief book
Android P requires that the network request must be Https, and the Http request will throw an exception.
Here I visit http://www.hao123.cn This address tests the following three scenarios and gives the corresponding results:
TargetSDK | scene | result |
---|---|---|
26 | Volley visits Http link | Normal access |
26 | OkHttp access Http link | Normal access |
26 | Socket implements Http request | Normal access |
28 | Volley visits Http link | Throw exception: java.io.IOException: Cleartext HTTP traffic to www.hao123.cn not permitted |
28 | OkHttp access Http link | CLEARTEXT communication to www.hao123.cn not permitted by network security policy |
28 | Socket implements Http request | Normal access |
As we know, the underlying layer of Volley is implemented by HttpUrlConnection, and the underlying layer of HttpUrlConnection is essentially OkHttp.
Here, you can only simply deduce:
The limitation of Android 9.0 on Http network requests originates from the application framework layer and must be related to the OkHttp source code.
Source code analysis
Objective: To study the principle of Http network request restriction and how to turn on the restriction switch by analyzing the source code.
Find cleartext communication to www.hao123.com globally Cn not allowed by network security policy. It is found that this exception is thrown when OkHttp executes the connect() method:
RealConnection
public void connect(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled, Call call, EventListener eventListener) { ... if (route.address().sslSocketFactory() == null) { if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication not enabled for client")); } String host = route.address().url().host(); if (!Platform.get().isCleartextTrafficPermitted(host)) { throw new RouteException(new UnknownServiceException( "CLEARTEXT communication to " + host + " not permitted by network security policy")); } } ...
route.address() will return an address object. Here you can see some codes intercepted by the address constructor:
this.url = new HttpUrl.Builder() .scheme(sslSocketFactory != null ? "https" : "http") .host(uriHost) .port(uriPort) .build();
As can be seen from the source code, the following condition judgment will be entered only when the scheme is http.
Android 9.0 does not allow Http requests. See platform get(). Iscleartexttrafficpermitted (host) returns false on the 9.0 System. This method means whether plaintext transmission is possible.
The real implementation class of the Platform is AndroidPlatform, so the actual source code is as follows:
AndroidPlatform
@Override public boolean isCleartextTrafficPermitted(String hostname) { try { Class<?> networkPolicyClass = Class.forName("android.security.NetworkSecurityPolicy"); Method getInstanceMethod = networkPolicyClass.getMethod("getInstance"); Object networkSecurityPolicy = getInstanceMethod.invoke(null); return api24IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy); } catch (ClassNotFoundException | NoSuchMethodException e) { return super.isCleartextTrafficPermitted(hostname); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw assertionError("unable to determine cleartext support", e); } }
Through reflection, get Android security. Networksecuritypolicy object and executed api24IsCleartextTrafficPermitted method:
AndroidPlatform
private boolean api24IsCleartextTrafficPermitted(String hostname, Class<?> networkPolicyClass, Object networkSecurityPolicy) throws InvocationTargetException, IllegalAccessException { try { Method isCleartextTrafficPermittedMethod = networkPolicyClass .getMethod("isCleartextTrafficPermitted", String.class); return (boolean) isCleartextTrafficPermittedMethod.invoke(networkSecurityPolicy, hostname); } catch (NoSuchMethodException e) { return api23IsCleartextTrafficPermitted(hostname, networkPolicyClass, networkSecurityPolicy); } }
Here is still through reflection, and finally executed Android security. isCleartextTrafficPermitted method of networksecuritypolicy:
android.security.NetworkSecurityPolicy
public boolean isCleartextTrafficPermitted(String hostname) { return libcore.net.NetworkSecurityPolicy.getInstance() .isCleartextTrafficPermitted(hostname); }
libcore is the Java core library of Android.
libcore.net.NetworkSecurityPolicy is an abstract class whose implementation class is Android security. net. config. Confignetworksecuritypolicy. Its source code is as follows:
ConfigNetworkSecurityPolicy
public class ConfigNetworkSecurityPolicy extends libcore.net.NetworkSecurityPolicy { private final ApplicationConfig mConfig; public ConfigNetworkSecurityPolicy(ApplicationConfig config) { mConfig = config; } @Override public boolean isCleartextTrafficPermitted() { return mConfig.isCleartextTrafficPermitted(); } @Override public boolean isCleartextTrafficPermitted(String hostname) { return mConfig.isCleartextTrafficPermitted(hostname); } @Override public boolean isCertificateTransparencyVerificationRequired(String hostname) { return false; } }
ConfigNetworkSecurityPolicy returns whether plaintext transmission is possible by calling isCleartextTrafficPermitted of ApplicationConfig, and then look down.
ApplicationConfig
public boolean isCleartextTrafficPermitted(String hostname) { return getConfigForHostname(hostname).isCleartextTrafficPermitted(); }
getConfigForHostname() has a long source code. We don't pay attention to details here. We just need to know that if we don't configure it separately, getConfigForHostname() will return NetworkSecurityConfig by default. It internally maintains a boolean variable, which determines whether to allow Http plaintext transmission.
Now you only need to find out when the Boolean value is assigned, you can understand the conditions of Http plaintext transmission.
Here, start with the initialization of NetworkSecurityConfig:
private void ensureInitialized() { synchronized(mLock) { ... mConfigs = mConfigSource.getPerDomainConfigs(); mDefaultConfig = mConfigSource.getDefaultConfig(); ... } }
It can be seen that NetworkSecurityConfig comes from ConfigSource, which is an interface. The real implementation class is ManifestConfigSource, which provides methods to obtain other ConfigSource implementation classes:
ManifestConfigSource
private ConfigSource getConfigSource() { synchronized (mLock) { if (mConfigSource != null) { return mConfigSource; } int configResource = mApplicationInfo.networkSecurityConfigRes; ConfigSource source; if (configResource != 0) { boolean debugBuild = (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; if (DBG) { Log.d(LOG_TAG, "Using Network Security Config from resource " + mContext.getResources() .getResourceEntryName(configResource) + " debugBuild: " + debugBuild); } source = new XmlConfigSource(mContext, configResource, mApplicationInfo); } else { if (DBG) { Log.d(LOG_TAG, "No Network Security Config specified, using platform default"); } // the legacy FLAG_USES_CLEARTEXT_TRAFFIC is not supported for Ephemeral apps, they // should use the network security config. boolean usesCleartextTraffic = (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0 && mApplicationInfo.targetSandboxVersion < 2; source = new DefaultConfigSource(usesCleartextTraffic, mApplicationInfo); } mConfigSource = source; return mConfigSource; } }
Here, we don't care about the XmlConfigSource ConfigSource for the time being. We only care about the DefaultConfigSource. Its source code is as follows:
DefaultConfigSource
private static final class DefaultConfigSource implements ConfigSource { private final NetworkSecurityConfig mDefaultConfig; DefaultConfigSource(boolean usesCleartextTraffic, ApplicationInfo info) { mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(info) .setCleartextTrafficPermitted(usesCleartextTraffic) .build(); } @Override public NetworkSecurityConfig getDefaultConfig() { return mDefaultConfig; } @Override public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() { return null; } }
Here you can see the key method: setcleartexttrafficpermitted (usesclleartexttraffic). Usesclleartexttraffic in this method is the boolean variable mentioned above, which determines whether to support Http plaintext transmission.
The usesclleartexttraffic comes from the code in getConfigSource in the previous step:
boolean usesCleartextTraffic = (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0 && mApplicationInfo.targetSandboxVersion < 2;
Of which:
public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27;
About applicationinfo Flags you can learn more about Baidu. The meaning of this code here is:
If the API version is > 27, usesclleartexttraffic = false is returned.
The whole system call process is as follows:
flow chart. png
But so far, our problem -- how to break the network limit has not been solved.
In the above code, ManifestConfigSource will call XmlConfigSource first.
So we can draw a preliminary conclusion: by configuring xml files in AndroidManifest, we can break the limit. So how to configure it?
Let's first look at the constructor of ManifestConfigSource:
public ManifestConfigSource(Context context) { mContext = context; ApplicationInfo info = context.getApplicationInfo(); //1 mApplicationInfoFlags = info.flags; mTargetSdkVersion = info.targetSdkVersion; mConfigResourceId = info.networkSecurityConfigRes; //2 mTargetSandboxVesrsion = info.targetSandboxVersion; }
Here, we can know two information:
- Note 2: go to get the networkSecurityConfigRes network security configuration;
- Note 1 shows that the network security configuration information comes from application;
So I tried to enter net in Android manifest application, Sure enough, the code prompt of as shows the configuration I want:
application.png
What networkSecurityConfig needs is an xml file. How can this xml file be configured to bypass network restrictions?
Returning to the XmlConfigSource source code just now, we need to focus on two places:
Source code 1:
private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry( XmlResourceParser parser, Set<String> seenDomains, NetworkSecurityConfig.Builder parentBuilder, int configType) throws IOException, XmlPullParserException, ParserException { ... for (int i = 0; i < parser.getAttributeCount(); i++) { String name = parser.getAttributeName(i); if ("hstsEnforced".equals(name)) { builder.setHstsEnforced( parser.getAttributeBooleanValue(i, NetworkSecurityConfig.DEFAULT_HSTS_ENFORCED)); } else if ("cleartextTrafficPermitted".equals(name)) { builder.setCleartextTrafficPermitted( parser.getAttributeBooleanValue(i, NetworkSecurityConfig.DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED)); } } ...
parseConfigEntry parses and sets whether plaintext transmission is allowed. It seems that it is the key. Here we can see that the attribute we want to configure is called cleartextTrafficPermitted, and its value is of boolean type.
Don't ask me how to find the attribute cleartextTrafficPermitted Plaintext transmission is the key word. If you search globally, you will find that only one place is called.
Source code 2:
private static final String getConfigString(int configType) { switch (configType) { case CONFIG_BASE: return "base-config"; case CONFIG_DOMAIN: return "domain-config"; case CONFIG_DEBUG: return "debug-overrides"; default: throw new IllegalArgumentException("Unknown config type: " + configType); } }
The above source code can be obtained: the Type of config can only be one of the three. Here, we don't need to traverse the source code to analyze. From the field name, we can basically infer that it is the first one.
So the xml is as follows:
<?xml version="1.0" encoding="utf-8"?> <Customize your name> <base-config cleartextTrafficPermitted="true" /> </Customize your name>
Configure the xml in the above networkSecurityConfig.
conclusion
OkHttp restricts Http plaintext requests on 9.0 systems by calling iscleartext traffic allowed (host) in Android API.
Therefore, there are two ways to bypass Http plaintext restrictions:
1. Open the switch limit through the XmlConfigSource in the above source code analysis.
2. Write your own Http request through Socket, or use a third-party network framework that is not OkHttp at the bottom and has not made Http plaintext judgment.
Bypass HTTP restrictions by configuring XML
Here are the complete solution steps for the first scheme in the above conclusion:
First, create an xml directory under the res folder and create a new network_security_config.xml file and generate the following code:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
Then at androidmanifest The following configuration is made in the Application of XML:
android:networkSecurityConfig="@xml/network_security_config"
Through the above analysis, the principle of such configuration should be roughly understood.
In addition, to use Volley, you also need to configure the following codes in the application:
<uses-library android:name="org.apache.http.legacy" android:required="false" />
Finally, I would like to say:
Android9.0 has chosen to disable Http plaintext requests. Whether in terms of security or other considerations, we should try our best to comply with Android regulations and jointly maintain the Android environment, rather than trying to bypass the restrictions.