- getType(uri)调用过程
- 其它
getType(uri)调用过程
Base on:Android 13
getType()会根据传进来的URI,调用到Provider端,由Provider端自定义的ContentProvider返回一个表示MimeType的字符串。
常见问题
ContentResolver.getType()中的getTypeAsync()是一个异步请求,调用后即返回,然后Resolver端线程进入wait状态。
从getTypeAsync()调用到AMS端之后,如果涉及到Provider端进程启动,且进程启动耗时,通常在CPH.getContentProviderImpl()处陷入wait(),于是无法调用notify()唤醒ContentResolver端,最终ContentResolver端就会发生ANR。
ContentResolver端堆栈状态示例:
"main" prio=5 tid=1 TimedWaiting
| group="main" sCount=1 ucsCount=0 flags=1 obj=0x71a7f398 self=0xb400007d16242c00
| sysTid=11509 nice=-10 cgrp=top-app sched=0/0 handle=0x7dc858e4f8
| state=S schedstat=( 7539789172 6591919896 9380 ) utm=603 stm=150 core=6 HZ=100
| stack=0x7fc5ab0000-0x7fc5ab2000 stackSize=8188KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x04738fd1> (a android.content.ContentResolver$StringResultListener)
at java.lang.Object.wait(Object.java:442)
at android.content.ContentResolver$ResultListener.waitForResult(ContentResolver.java:1005)
- locked <0x04738fd1> (a android.content.ContentResolver$StringResultListener)
at android.content.ContentResolver.getType(ContentResolver.java:956)
at android.content.Intent.resolveType(Intent.java:8569)
at android.content.Intent.resolveTypeIfNeeded(Intent.java:8594)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1860)
at android.app.Activity.startActivityForResult(Activity.java:5623)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:2)
at android.app.Activity.startActivityForResult(Activity.java:5576)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:1)
at android.app.Activity.startActivity(Activity.java:6094)
at android.app.Activity.startActivity(Activity.java:6061)
...
AMS端等待进程启动堆栈:
"pool-2-thread-1" prio=5 tid=245 TimedWaiting
| group="main" sCount=1 ucsCount=0 flags=1 obj=0x14bded50 self=0xb400007ba7604800
| sysTid=4758 nice=0 cgrp=foreground sched=0/0 handle=0x7b8a5c8cb0
| state=S schedstat=( 6722807920 17774798580 15733 ) utm=362 stm=310 core=5 HZ=100
| stack=0x7b8a4c5000-0x7b8a4c7000 stackSize=1039KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x00f440b6> (a com.android.server.am.ContentProviderRecord)
at java.lang.Object.wait(Object.java:442)
at com.android.server.am.ContentProviderHelper.getContentProviderImpl(ContentProviderHelper.java:662)
- locked <0x00f440b6> (a com.android.server.am.ContentProviderRecord)
at com.android.server.am.ContentProviderHelper.getContentProviderExternalUnchecked(ContentProviderHelper.java:158)
at com.android.server.am.ContentProviderHelper.doGetProviderMimeTypeAsync(ContentProviderHelper.java:1139)
at com.android.server.am.ContentProviderHelper.lambda$getProviderMimeTypeAsync$0$com-android-server-am-ContentProviderHelper(ContentProviderHelper.java:1122)
at com.android.server.am.ContentProviderHelper$$ExternalSyntheticLambda0.run(unavailable:8)
at com.android.server.am.ContentProviderHelper$1WithCallingIdentityRequest.run(ContentProviderHelper.java:1115)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:463)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
at java.lang.Thread.run(Thread.java:1012)
getContentResolver().getType(uri)
用户APP端调用接口查询:
Uri uri = Uri.parse(uriString);
String mime=getContentResolver().getType(uri);
ContentResolver.getType(uri)
这里分为两种情况。
情况1:Resolver端进程已有IContentProvider连接(之前访问过),那么就直接通过provider.getTypeAsync()异步调用到Provider端,返回MIME类型后被唤醒。
情况2:Resolver端进程本地没有IContentProvider连接,则走到下面,通过AMS.getProviderMimeTypeAsync()异步访问,然后等待唤醒。
public final @Nullable String getType(@NonNull Uri url) {
Objects.requireNonNull(url, "url");
//...
IContentProvider provider = acquireExistingProvider(url);
if (provider != null) {
try {
final StringResultListener resultListener = new StringResultListener();
provider.getTypeAsync(url, new RemoteCallback(resultListener));
resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
if (resultListener.exception != null) {
throw resultListener.exception;
}
return resultListener.result;
} catch (RemoteException e) {
// ...
}
}
try {
final StringResultListener resultListener = new StringResultListener();
ActivityManager.getService().getProviderMimeTypeAsync(
ContentProvider.getUriWithoutUserId(url),
resolveUserId(url),
new RemoteCallback(resultListener));
resultListener.waitForResult(REMOTE_CONTENT_PROVIDER_TIMEOUT_MILLIS);
if (resultListener.exception != null) {
throw resultListener.exception;
}
return resultListener.result;
} catch (RemoteException e) {
// ...
return null;
}
}
AMS.getProviderMimeTypeAsync()
/**
* Allows apps to retrieve the MIME type of a URI.
* If an app is in the same user as the ContentProvider, or if it is allowed to interact across
* users, then it does not need permission to access the ContentProvider.
* Either way, it needs cross-user uri grants.
*/
@Override
public void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) {
mCpHelper.getProviderMimeTypeAsync(uri, userId, resultCallback);
}
CPH.getProviderMimeTypeAsync()
从这里获取到Provider引用之后,同ContentResolver.getType(uri)中的情况1一样,通过该引用异步调用provider.getTypeAsync(),将MIME结果放入到RemoteCallback对象中。
void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) {
mService.enforceNotIsolatedCaller("getProviderMimeTypeAsync");
final String name = uri.getAuthority();
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
final long ident = canClearIdentity(callingPid, callingUid, userId)
? Binder.clearCallingIdentity() : 0;
try {
final ContentProviderHolder holder = getContentProviderExternalUnchecked(name, null,
callingUid, "*getmimetype*", safeUserId);
if (holder != null) {
holder.provider.getTypeAsync(uri, new RemoteCallback(result -> {
final long identity = Binder.clearCallingIdentity();
try {
removeContentProviderExternalUnchecked(name, null, safeUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
resultCallback.sendResult(result);
}));
} else {
resultCallback.sendResult(Bundle.EMPTY);
}
} catch (RemoteException e) {
Log.w(TAG, "Content provider dead retrieving " + uri, e);
resultCallback.sendResult(Bundle.EMPTY);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
CPH.getContentProviderExternalUnchecked()
ContentProviderHolder getContentProviderExternalUnchecked(String name,
IBinder token, int callingUid, String callingTag, int userId) {
return getContentProviderImpl(null, name, token, callingUid, null, callingTag,
true, userId);
}
CPH.getContentProviderImpl()
而在getContentProviderImpl()中,由于是从getContentProviderExternalUnchecked()中调用而来,所以caller == null,如果Provider端进程未启动,则会在以下处陷入wait,此处wait=20s。
synchronized (cpr) {
while (cpr.provider == null) {
if (cpr.launchingApp == null) {
//...
return null;
}
try {
final long wait = Math.max(0L, timeout - SystemClock.uptimeMillis());
if (DEBUG_MU) {
Slog.v(TAG_MU, "Waiting to start provider " + cpr
+ " launchingApp=" + cpr.launchingApp + " for " + wait + " ms");
}
if (conn != null) {
conn.waiting = true;
}
cpr.wait(wait);
if (cpr.provider == null) {
timedOut = true;
break;
}
} catch (InterruptedException ex) {
} finally {
if (conn != null) {
conn.waiting = false;
}
}
}
}
堆栈如下:
"pool-2-thread-1" prio=5 tid=245 TimedWaiting
| group="main" sCount=1 ucsCount=0 flags=1 obj=0x14bded50 self=0xb400007ba7604800
| sysTid=4758 nice=0 cgrp=foreground sched=0/0 handle=0x7b8a5c8cb0
| state=S schedstat=( 6722807920 17774798580 15733 ) utm=362 stm=310 core=5 HZ=100
| stack=0x7b8a4c5000-0x7b8a4c7000 stackSize=1039KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x00f440b6> (a com.android.server.am.ContentProviderRecord)
at java.lang.Object.wait(Object.java:442)
at com.android.server.am.ContentProviderHelper.getContentProviderImpl(ContentProviderHelper.java:662)
- locked <0x00f440b6> (a com.android.server.am.ContentProviderRecord)
at com.android.server.am.ContentProviderHelper.getContentProviderExternalUnchecked(ContentProviderHelper.java:158)
at com.android.server.am.ContentProviderHelper.doGetProviderMimeTypeAsync(ContentProviderHelper.java:1139)
at com.android.server.am.ContentProviderHelper.lambda$getProviderMimeTypeAsync$0$com-android-server-am-ContentProviderHelper(ContentProviderHelper.java:1122)
at com.android.server.am.ContentProviderHelper$$ExternalSyntheticLambda0.run(unavailable:8)
at com.android.server.am.ContentProviderHelper$1WithCallingIdentityRequest.run(ContentProviderHelper.java:1115)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:463)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1137)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:637)
at java.lang.Thread.run(Thread.java:1012)
ResultListener.waitForResult()
在此处等待AMS端调用notify()
详解-IContentProvider.getTypeAsync()
有两处调用到这里:
- ContentResolver.getType(uri)
Resolver端进程已有IContentProvider连接(之前访问过),那么就直接通过provider.getTypeAsync()异步调用到Provider端,返回MIME类型后被唤醒。
注:StringResultListener父类的父类ResultListener
try {
final StringResultListener resultListener = new StringResultListener();
provider.getTypeAsync(url, new RemoteCallback(resultListener));
resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
if (resultListener.exception != null) {
throw resultListener.exception;
}
return resultListener.result;
}
- CPH.getProviderMimeTypeAsync()
获取到Provider引用之后,同ContentResolver.getType(uri)中的情况1一样,通过该引用异步调用provider.getTypeAsync(),将MIME结果放入到RemoteCallback对象中。
holder.provider.getTypeAsync(uri, new RemoteCallback(result -> {
final long identity = Binder.clearCallingIdentity();
try {
removeContentProviderExternalUnchecked(name, null, safeUserId);
} finally {
Binder.restoreCallingIdentity(identity);
}
resultCallback.sendResult(result);
}));
RemoteCallback说明
RemoteCallback是一个Android的回调类。
因为Resolver端调用getType()是一个异步调用,调用后即陷入wait状态,当AMS从Provider获取到结果后,需要通过RemoteCallback将Resolver端线程唤醒。
在Resolver端调用getType()过程中,在获取到Provider的Binder引用后,都会将MIME结果放到RemoteCallback对象中(参数OnResultListener对象),然后唤醒wait状态中的Resolver端线程。
RemoteCallback对象在构造时传入一个OnResultListener接口对象,它就是回调时的执行代码。
IContentProvider.getTypeAsync()
IContentProvider的实现在ContentProvider.Transport类中:
@Override
public void getTypeAsync(Uri uri, RemoteCallback callback) {
final Bundle result = new Bundle();
try {
result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
} catch (Exception e) {
result.putParcelable(ContentResolver.REMOTE_CALLBACK_ERROR,
new ParcelableException(e));
}
callback.sendResult(result);
}
在这里通过Binder获取到结果后,调用到RemoteCallback.sendResult(result)中。
RemoteCallback.sendResult()
在这里的条件为mListener != null,mHandler == null,所以直接执行mListener.onResult(result);
mListener是构造RemoteCallback是传入的参数。
public void sendResult(@Nullable final Bundle result) {
// Do local dispatch
if (mListener != null) {
if (mHandler != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
mListener.onResult(result);
}
});
} else {
mListener.onResult(result);
}
// Do remote dispatch
} else {
try {
mCallback.sendResult(result);
} catch (RemoteException e) {
/* ignore */
}
}
}
ResultListener.onResult()
代码位于:ContentResolver.ResultListener.onResult()
在这里唤醒本线程,于是ContentResolver.getType(uri)和CPH.getProviderMimeTypeAsync()中调用getTypeAsync()的线程被唤醒,并在ContentResolver.getType(uri)中将结果返回。
@Override
public void onResult(Bundle result) {
synchronized (this) {
ParcelableException e = result.getParcelable(REMOTE_CALLBACK_ERROR);
if (e != null) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
this.exception = (RuntimeException) t;
} else {
this.exception = new RuntimeException(t);
}
} else {
this.result = getResultFromBundle(result);
}
done = true;
notifyAll();
}
}
其它
getContentProviderExternalUnchecked()
ContentProviderHolder getContentProviderExternalUnchecked(String name,
IBinder token, int callingUid, String callingTag, int userId) {
return getContentProviderImpl(null, name, token, callingUid, null, callingTag,
true, userId);
}
getContentProviderImpl()是system_process进程中访问Provider的主要方法,有两处可以调用而来。
- 来自CPH.getContentProvider():大多数APP访问Provider要走的流程
- 来自CPH.getContentProviderExternalUnchecked():在部分情况下非用户APP的一些调用流程,如shell、native、getType()调用而来。
(1) 情况1:通过shell访问Provider
命令:adb shell content query –uri content://com.demoapp.contentproviderdemo.provider/person
(2) 情况2:来自native的调用
如media进程(UID=1013)的调用:
Native层:
MediaPlayerService::setDataSource() ->
ActivityManager::openContentProviderFile()
int openContentProviderFile(const String16& uri){
int fd = -1;
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16("activity"));
sp<IActivityManager> am = interface_cast<IActivityManager>(binder);
if (am != NULL) {
fd = am->openContentUri(uri);
}
return fd;
}
Framework层:
AMS.openContentUri(String uriString) ->
CPH.getContentProviderExternalUnchecked() ->
CPH.getContentProviderImpl()
(3) getContentProviderImpl()调用方式不同的区别
ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage,
String name, int userId, boolean stable) {
//...
return getContentProviderImpl(caller, name, null, callingUid, callingPackage,
null, stable, userId);
}
ContentProviderHolder getContentProviderExternalUnchecked(String name,
IBinder token, int callingUid, String callingTag, int userId) {
return getContentProviderImpl(null, name, token, callingUid, null, callingTag,
true, userId);
}
在传入的参数上,最大的区别是IApplicationThread caller,ExternalUnchecked()调用而来的caller == null,这回导致后续在等待Provider端进程启动时有一些区别。
还有其它的参数如token、callingPackage、callingTag等,对getContentProviderImpl()中的流程造成的影响比较小。
如果Provider端进程未启动,会走到启动进程的流程中:
proc = mService.startProcessLocked(
cpi.processName, cpr.appInfo, false, 0,
new HostingRecord(HostingRecord.HOSTING_TYPE_CONTENT_PROVIDER,
new ComponentName(
cpi.applicationInfo.packageName, cpi.name)),
// MIUI MOD:
// Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false);
Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false, callerPackage);
对常规APP调用进来的而言,由于caller != null,所以会在这里发送超时消息,并返回ContentProviderHolder对象,之后是在AT.acquireProvider()中等待进程启动,否则执行超时流程:
if (caller != null) {
// The client will be waiting, and we'll notify it when the provider is ready.
synchronized (cpr) {
if (cpr.provider == null) {
if (cpr.launchingApp == null) {
//...
return null;
}
if (conn != null) {
conn.waiting = true;
}
Message msg = mService.mHandler.obtainMessage(
ActivityManagerService.WAIT_FOR_CONTENT_PROVIDER_TIMEOUT_MSG);
msg.obj = cpr;
mService.mHandler.sendMessageDelayed(msg,
ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
}
}
//...
return cpr.newHolder(conn, false);
}
对于通过getContentProviderExternalUnchecked()调用进来的流程,由于caller == null,所以在getContentProviderImpl()中,可能会走到以下流程中。
注:正常的APP调用不会走到从这里开始的流程后面。
// Because of the provider's external client (i.e., SHELL), we'll have to wait right here.
// Wait for the provider to be published...
final long timeout =
SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS;
boolean timedOut = false;
synchronized (cpr) {
while (cpr.provider == null) {
if (cpr.launchingApp == null) {
//...
return null;
}
try {
final long wait = Math.max(0L, timeout - SystemClock.uptimeMillis());
if (conn != null) {
conn.waiting = true;
}
cpr.wait(wait);
if (cpr.provider == null) {
timedOut = true;
break;
}
} catch (InterruptedException ex) {
} finally {
if (conn != null) {
conn.waiting = false;
}
}
}
}
从代码中可以看出,线程可能会在这里陷入等待,而不像APP调用一样在Resolver端进程等待。