Fork me on GitHub

[Android][Framework]记一个Uri相关的奇葩bug

安装爱奇艺之后,搜索一个外链视频,然后点击播放,就会出现crash。

这个Bug只在我们的系统里出现,在手机上安装不存在这个现象。

那就很奇怪了,我们的系统又做了什么错事?作为负责系统的,我慌的一逼,赶紧抓一份log分析。

crash log:

--------- beginning of crash
01-04 15:54:29.614 3743 3743 E AndroidRuntime: FATAL EXCEPTION: main
01-04 15:54:29.614 3743 3743 E AndroidRuntime: Process: com.qiyi.video.pad, PID: 3743
01-04 15:54:29.614 3743 3743 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.qiyi.video.pad/org.qiyi.android.video.activitys.CommonWebViewNewActivity}: java.lang.UnsupportedOperationException: This isn't a hierarchical URI.
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.ActivityThread.-wrap12(ActivityThread.java)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:102)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.os.Looper.loop(Looper.java:154)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6119)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:900)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:790)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: Caused by: java.lang.UnsupportedOperationException: This isn't a hierarchical URI.
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.net.Uri.getQueryParameter(Uri.java:1685)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at com.qiyi.h.com1.aX(Unknown Source)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at org.qiyi.android.video.activitys.CommonWebViewNewActivity.aCW(Unknown Source)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at org.qiyi.android.video.activitys.CommonWebViewNewActivity.aiS(Unknown Source)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at org.qiyi.android.video.activitys.CommonWebViewNewActivity.v(Unknown Source)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at org.qiyi.android.video.activitys.CommonWebViewBaseActivity.onCreate(Unknown Source)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:6720)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
01-04 15:54:29.614 3743 3743 E AndroidRuntime: ... 9 more
01-04 15:54:29.621 1228 2156 W ActivityManager: Force finishing activity com.qiyi.video.pad/org.qiyi.android.video.activitys.CommonWebViewNewActivity

根据

UnsupportedOperationException: This isn't a hierarchical URI.

所以简单跟踪一下Uri代码:

报错的地方在:

/**
* Returns a set of the unique names of all query parameters. Iterating
* over the set will return the names in order of their first occurrence.
*
* @throws UnsupportedOperationException if this isn't a hierarchical URI
*
* @return a set of decoded names
*/
public Set<String> getQueryParameterNames() {
if (isOpaque()) {
throw new UnsupportedOperationException(NOT_HIERARCHICAL);
}

这里就是抛出this isn't a hierarchical URI异常的地方,所以查看isOpaque什么情况下返回true

/**
* Returns true if this URI is opaque like "mailto:nobody@google.com". The
* scheme-specific part of an opaque URI cannot start with a '/'.
*/
public boolean isOpaque() {
return !isHierarchical();
}

额,也就是说要调查isHierarchical在什么时候返回false

/**
* Returns true if this URI is hierarchical like "http://google.com".
* Absolute URIs are hierarchical if the scheme-specific part starts with
* a '/'. Relative URIs are always hierarchical.
*/
public abstract boolean isHierarchical();

emmmm,它是个抽象方法,那就看一下他的实现。因为实在Uri.java定义的,查找继承Uri的类们,看其实现方法。从代码里搜到所有的继承都在Uri.java的内部类,所以直接搜,最后定位到是在StringUri里面实现的isHierarchical()方法处理后返回的false

public boolean isHierarchical() {
int ssi = findSchemeSeparator();

if (ssi == NOT_FOUND) {
// All relative URIs are hierarchical.
return true;
}

if (uriString.length() == ssi + 1) {
// No ssp.
return false;
}

// If the ssp starts with a '/', this is hierarchical.
return uriString.charAt(ssi + 1) == '/';
}

/** Finds the first ':'. Returns -1 if none found. */
private int findSchemeSeparator() {
return cachedSsi == NOT_CALCULATED
? cachedSsi = uriString.indexOf(':')
: cachedSsi;
}

这个方法逻辑是:

  • 先从uriString里面查找:并记录其位置
  • 如果没找到,直接返回true,相当于不做处理
  • 再判断uriString的长度是否到:为止,如果是返回false
  • 最后判断:后面的字符是否是/

所以知道最后处理的uriString是什么就很关键了,通过添加log,编译系统,找到最后出问题的uriString是:

uristring is deviceId=22b0e7ce4a35edca010和谐&platform=GPad&network=1&ov=7.1.1:1.和谐&location=121.372360,31.176649

行吧。我就用你爱奇艺播放个视频而已,你看看你干了些什么?又读我设备ID,又读当前位置,还给我搞出来一个Bug。我TM一刀…算了40米的长刀拔出来太麻烦,还是看问题吧。

这个Uri本来应该在第一处判断就返回true的,但是因为版本号有一个7.1.1:1.xxxx,结果匹配上了冒号,最终因为:后面没有/返回一个false状态。

好嘛,半天是因为我们定义release版本号导致的这个问题。

最后也算长了点经验,以后在Android里字串连接的时候尽量不要用:了,否则不知道什么时候在哪里会挖出来一个坑。

坚持原创技术分享,您的支持将鼓励我继续创作!