[android] android 9 or above mobile phones fail to play videos, and an error is reported. GenericSource: initFromDataSource, cannot create extractor reason

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:

TargetSDKsceneresult
26Volley visits Http linkNormal access
26OkHttp access Http linkNormal access
26Socket implements Http requestNormal access
28Volley visits Http linkThrow exception: java.io.IOException: Cleartext HTTP traffic to www.hao123.cn not permitted
28OkHttp access Http linkCLEARTEXT communication to www.hao123.cn not permitted by network security policy
28Socket implements Http requestNormal 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:

  1. Note 2: go to get the networkSecurityConfigRes network security configuration;
  2. 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.

Keywords: Android

Added by mwd2005 on Thu, 27 Jan 2022 21:12:19 +0200