主頁 > 移動端開發 > Android筑基——ContentProvider 的跨行程啟動程序和資料操作方法呼叫程序(基于api21)

Android筑基——ContentProvider 的跨行程啟動程序和資料操作方法呼叫程序(基于api21)

2022-01-03 20:38:02 移動端開發

目錄

  • 1.前言
  • 2.正文
    • 2.1 小例子
    • 2.2 代碼分析
      • 2.2.1 ContextWrapper.getContentResolver() 方法
      • 2.2.2 ContextImpl.getContentResolver() 方法
      • 2.2.3 ContentResolver.query() 方法
      • 2.2.4 ContentResolver.acquireUnstableProvider() 方法
      • 2.2.5 ApplicationContentResolver.acquireUnstableProvider() 方法
      • 2.2.6 ActivityThread.acquireProvider() 方法
        • 2.2.6.1 ActivityThread.acquireExistingProvider() 方法
        • 2.2.6.2 ActivityManagerNative.getDefault() 方法
      • 2.2.7 ActivityManagerProxy.getContentProvider() 方法
      • 2.2.8 ActivityManagerNative.onTransact() 方法
      • 2.2.9 ActivityManagerService.getContentProvider() 方法
      • 2.2.10 ActivityManagerService.getContentProviderImpl() 方法
        • 2.2.10.1 ProviderMap 類
        • 2.2.10.2 ContentProviderRecord.canRunHere() 方法
      • 2.2.11 ActivityManagerService.startProcessLocked() 方法
        • 2.2.11.1 ActivityManagerService.newProcessRecordLocked() 方法
      • 2.2.12 ActivityManagerService.startProcessLocked() 方法
      • 2.2.13 Process.start() 方法
      • 2.2.14 ActivityThread.main() 方法
      • 2.2.15 ActivityThread.attach() 方法
      • 2.2.16 ActivityManagerService.attachApplication() 方法
      • 2.2.17 ActivityManagerService.attachApplicationLocked() 方法
        • 2.2.17.1 ActivityManagerService.generateApplicationProvidersLocked() 方法
      • 2.2.18 ApplicationThreadProxy.bindApplication() 方法
      • 2.2.19 ApplicationThreadNative.onTransact() 方法
      • 2.2.20 ApplicationThread.bindApplication() 方法
      • 2.2.21 H.handleMessage() 方法
      • 2.2.22 ActivityThread.handleBindApplication() 方法
      • 2.2.23 ActivityThread.installContentProviders() 方法
      • 2.2.24 ActivityThread.installProvider() 方法
        • 2.2.24.1 ContentProvider.attachInfo() 方法
        • 2.2.24.2 ActivityThread.installProviderAuthoritiesLocked() 方法
      • 2.2.25 ActivityManagerService.publishContentProviders() 方法
      • 2.2.26 ActivityManagerService.getContentProviderImpl() 方法跳出回圈等待 provider 發布
      • 2.2.27 ActivityThread.acquireProvider() 方法
        • 2.2.27.1 ActivityThread.installProvider() 方法
        • 2.2.27.2 ActivityThread.installProviderAuthoritiesLocked() 方法
      • 2.2.28 ContentProviderProxy.query() 方法
      • 2.2.29 ContentProviderNative.onTransact() 方法
      • 2.2.30 Transport.query() 方法
      • 2.2.31 BookProvider.query() 方法
  • 3.最后
  • 4.參考

1.前言

ContentProvider (內容提供者)是 Android 的四大組件之一,是一種內容共享型組件,它提供了資料的統一訪問格式,通過單一的 ContentResolver 介面把資料提供給其他應用,只有在多個應用之間分享資料時,ContentProvider 才是必須的,

ContentResolver 介面發起的insert(增加)、delete(洗掉)、update(更新)、query(查詢)資料操作會發給 ContentProvider 對應的 insert(增加)、delete(洗掉)、update(更新)、query(查詢)方法,

我們一般認為,內容提供者的資料來源是資料庫,但實際上,內容提供者對底層的資料存盤方式沒有任何要求,可以使用資料庫,也可以使用普通的檔案,也可以使用記憶體中的一個物件,還可以使用網路資料,

換句話說,ContentProvider 提供了一種抽象,隔離了資料的使用方和資料的提供方,這樣即便資料的提供方修改了資料存盤實作,也不會影響到依賴資料訪問的其他現有應用(即資料的使用方),具體而言,內容提供者使用資料庫作為資料來源,當它把資料來源更改為 SharedPreferences 時,資料的使用方是不會受到影響的,資料的使用方是感知不到的,

在實際開發中,我們使用別人提供的內容提供者的時候比較多,比如使用 Android 系統中自帶的電話簿(ContactsContract)獲取聯系人串列,使用 Android 系統中自帶的 MediaStore(媒體庫)獲取媒體資訊(如 MediaStore.Audio 音頻資料,MediaStore.Video 視頻資料,MediaStore.Files 檔案資料,MediaStore.Images 圖片資料),Android 框架實作的部分內容提供程式位于 android.provider 包下,

而自己開發內容提供者給別人用比較少,一個應用是使用 ContentProvider 在 library 里直接獲取背景關系,不必再由主工程傳遞背景關系給 library 了(不過,谷歌不推薦這樣做),

本文主要包括:

  • 演示一個使用了 ContentProvider 的小例子,這也是我們本次要分析的場景;
  • ContentProvider 的跨行程啟動程序,這樣我們就可以知道為什么 ContentProvideronCreate() 方法要先于 ApplicationonCreate() 方法執行;
  • ContentProvider 的資料操作方法呼叫程序,這里以 query(查詢)方法為例來說明,這樣可以知道由 ContentResolverquery 方法發起的查詢操作如何最終交由 ContentProviderquery 方法來執行,

2.正文

2.1 小例子

例子部分請查看BookProvider,

本文不打算講解這個例子了,因為本文已經很長了,

這里說明一下分析的場景:BookProvider 位于一個單獨的行程里面;在客戶端呼叫 ContentResolverquery 方法時,BookProvider 所處的行程還未啟動,因此需要先開啟目標行程,

2.2 代碼分析

2.2.1 ContextWrapper.getContentResolver() 方法

@Override
public ContentResolver getContentResolver() {
    return mBase.getContentResolver();
}

這里的 ContextWrapper其實是裝飾器設計模式的應用了,類結構圖如下所示:
在這里插入圖片描述

呼叫裝飾類 ContextWrappergetContentResolver()方法,內部真正呼叫的是核心實作類 ContextImplgetContentResolver()方法,所以 mBase 實際上是一個 ContextImpl 型別的物件,

2.2.2 ContextImpl.getContentResolver() 方法

class ContextImpl extends Context {
	private final ApplicationContentResolver mContentResolver;
	
	private ContextImpl(ContextImpl container, ActivityThread mainThread,
			LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
			Display display, Configuration overrideConfiguration) {
		...
		// 初始化 ApplicationContentResolver 物件,
		mContentResolver = new ApplicationContentResolver(this, mainThread, user);
	}
	
	@Override
	public ContentResolver getContentResolver() {
		return mContentResolver;
	}
	private static final class ApplicationContentResolver extends ContentResolver {
		private final ActivityThread mMainThread;
		private final UserHandle mUser;
		public ApplicationContentResolver(
				Context context, ActivityThread mainThread, UserHandle user) {
			super(context);
			mMainThread = Preconditions.checkNotNull(mainThread);
			mUser = Preconditions.checkNotNull(user);
		}
		@Override
		protected IContentProvider acquireProvider(Context context, String auth) {
			return mMainThread.acquireProvider(context,
					ContentProvider.getAuthorityWithoutUserId(auth),
					resolveUserIdFromAuthority(auth), true);
		}
		... // 省略一些重寫的方法
	}
}

可以看到,最侄訓傳的是一個 mContentResolver 物件,它的型別是 ApplicationContentResolver,它是在 ContextImpl 的構造方法中被初始化的,

在初始化ApplicationContentResolver物件時,傳入了 ActivityThread 物件,并且 ApplicationContentResolver 物件內部持有了 ActivityThread 物件,

ApplicationContentResolver是繼承于 ContentResolver 抽象類的具體類,它是 ContextImpl 的私有嵌套內部類,

讓我們用 UML 圖來表示它們之間的關系:
在這里插入圖片描述
可以看到,ApplicationContentResolver 實作了基類 ContentResolver 的一系列 acquireXXX 以及 releaseXXX 的方法,而方法內部的實作則是委托ActivityThread 物件的相應方法來完成,

2.2.3 ContentResolver.query() 方法

  • Uri uri, Uri.parse(“content://com.wzc.chapter_9.bookprovider/books”)
  • String[] projection, null
  • String selection, null
  • String[] selectionArgs, null
  • String sortOrder, null
public final Cursor query(Uri uri, String[] projection,
        String selection, String[] selectionArgs, String sortOrder) {
    return query(uri, projection, selection, selectionArgs, sortOrder, null);
}

呼叫多載的 query() 方法:

  • Uri uri, Uri.parse(“content://com.wzc.chapter_9.bookprovider/books”)
  • String[] projection, null
  • String selection, null
  • String[] selectionArgs, null
  • String sortOrder, null
  • CancellationSignal cancellationSignal, null
public final Cursor query(final Uri uri, String[] projection,
        String selection, String[] selectionArgs, String sortOrder,
        CancellationSignal cancellationSignal) {
	// 獲取 IContentProvider 物件,見【2.2.4】,
    IContentProvider unstableProvider = acquireUnstableProvider(uri);
    if (unstableProvider == null) {
        return null;
    }
    Cursor qCursor = null;
    try {
        ICancellationSignal remoteCancellationSignal = null;
        if (cancellationSignal != null) { // cancellationSignal 為 null,所以不會進入此分支
			...
        }
        try {
			// 獲取 Cursor 物件,見【2.2.28】
            qCursor = unstableProvider.query(mPackageName, uri, projection,
                    selection, selectionArgs, sortOrder, remoteCancellationSignal);
        } catch (DeadObjectException e) {
            // 省略例外的代碼部分
        }
        if (qCursor == null) {
            return null;
        }
        qCursor.getCount();
        // 把 Cursor 物件包裝成 CursorWrapperInner 物件回傳.
        CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, acquireProvider(uri));
        qCursor = null;
        return wrapper;
    } catch (RemoteException e) {
        return null;
    } finally {
        if (qCursor != null) {
            qCursor.close();
        }
        if (unstableProvider != null) {
            releaseUnstableProvider(unstableProvider);
        }
    }
}

2.2.4 ContentResolver.acquireUnstableProvider() 方法

  • Uri uri, Uri.parse(“content://com.wzc.chapter_9.bookprovider/books”)
public static final String SCHEME_CONTENT = "content";
public final IContentProvider acquireUnstableProvider(Uri uri) {
    if (!SCHEME_CONTENT.equals(uri.getScheme())) { // uri.getScheme() 是 "content",不會進入此分支
        return null;
    }
    String auth = uri.getAuthority(); // auth 等于 "com.wzc.chapter_9.bookprovider"
    if (auth != null) { // 進入此分支
        return acquireUnstableProvider(mContext, uri.getAuthority());
    }
    return null;
}

呼叫多載的 acquireUnstableProvider() 方法:

protected abstract IContentProvider acquireUnstableProvider(Context c, String name);

這是一個抽象方法,它的實作是在 ContextImpl.ApplicationContentResolver 里面,

這里出現了 IContentProvider 介面,這是一個用于跨行程通信的介面,與之相關的 binder 客戶端與服務端類 UML 圖如下:
在這里插入圖片描述
比較疑惑的是,這里回傳的 IContentProvider 物件到底是個什么物件,是 ContentProviderProxy 物件,還是 ContentProviderNative 物件呢?

我們現在還不得而知,但是我們可以通過Debug:

getContentResolver().acquireUnstableProvider(BookStore.Books.CONTENT_URI);

得到:
在這里插入圖片描述
很明顯,回傳的是一個 ContentProviderProxy 物件,也就是說,是一個 binder 客戶端物件,

那么,客戶端行程就是通過這個 binder 客戶端物件,經過 binder 驅動,發起遠程呼叫,從 binder 服務端那里獲取到資料的,

而由上面的類圖可知,IContentProvider 介面對應的 binder 服務端物件是 ContentProvider 的內部類 Transport

這樣就構成了 binder 跨行程通信的客戶端和服務端了,

2.2.5 ApplicationContentResolver.acquireUnstableProvider() 方法

  • Context c, ContextImpl 物件
  • String auth, “com.wzc.chapter_9.bookprovider”
@Override
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
    return mMainThread.acquireProvider(c,
            ContentProvider.getAuthorityWithoutUserId(auth),
            resolveUserIdFromAuthority(auth), false);
}

本次的 auth 經過 ContentProvider.getAuthorityWithoutUserId(auth) 后,仍為 "com.wzc.chapter_9.bookprovider"
resolveUserIdFromAuthority(auth) 的結果是 0

2.2.6 ActivityThread.acquireProvider() 方法

  • Context c, ContextImpl 物件
  • String auth, “com.wzc.chapter_9.bookprovider”
  • int userId, 0
  • boolean stable, false
public final IContentProvider acquireProvider(
        Context c, String auth, int userId, boolean stable) {
	// 獲取已經存在的 IContentProvider 物件,見【2.2.6.1】
    final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
    if (provider != null) { // 本次分析 provider 為 null,所以不會進入此分支了,
		// 如果可以獲取到已經存在的 IContentProvider 物件,就直接回傳了,
        return provider;
    }
    IActivityManager.ContentProviderHolder holder = null;
    try {
		// 向 AMS 請求獲取 ContentProviderHolder 物件
        // ActivityManagerNative.getDefault() 的回傳 ActivityManagerProxy 物件,分析見 【2.2.6.2】
        // ActivityManagerProxy.getContentProvider() 分析,見【2.2.7】
        holder = ActivityManagerNative.getDefault().getContenProvider(
                getApplicationThread(), auth, userId, stable)
    } catch (RemoteException ex) {
    }
    if (holder == null) {
        // 如果 AMS 回傳的 ContentProviderHolder 物件為 null,則直接回傳 null,
        return null;
    }
	// 安裝 provider 將會增加參考計數,并且打破競爭中的任何聯系,
    holder = installProvider(c, holder, holder.info,
            true /*noisy*/, holder.noReleaseNeeded, stable);
    return holder.provider;
}

該方法的主要作用:

  1. 嘗試獲取已經存在的 IContentProvider 物件;
  2. 如果 1 獲取不到 IContentProvider 物件,再向 AMS 請求獲取 ContentProviderHolder 物件;
  3. 如果 2 獲取的 ContentProviderHolder 物件不為 null,則呼叫安裝 provider 的方法,

2.2.6.1 ActivityThread.acquireExistingProvider() 方法

  • Context c, ContextImpl 物件
  • String auth, “com.wzc.chapter_9.bookprovider”
  • int userId, 0
  • boolean stable, false
// mProviderMap 以 ProviderKey 為鍵,以 ProviderClientRecord 為值
final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
    = new ArrayMap<ProviderKey, ProviderClientRecord>();
	
final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap
    = new ArrayMap<IBinder, ProviderRefCount>();
	
public final IContentProvider acquireExistingProvider(
        Context c, String auth, int userId, boolean stable) {
    synchronized (mProviderMap) {
		// 把 auth 和 userId 封裝為 ProviderKey 物件,
        final ProviderKey key = new ProviderKey(auth, userId);
		// 從 mProviderMap 查找對應 ProviderKey 的 ProviderClientRecord 物件
        final ProviderClientRecord pr = mProviderMap.get(key);
        if (pr == null) { 
			// 本次分析,獲取的 pr 為 null,所以直接在這里回傳了,
            return null;
        }
        // ###下面的代碼,本次分析不會走到了###,
        IContentProvider provider = pr.mProvider;
        IBinder jBinder = provider.asBinder();
        if (!jBinder.isBinderAlive()) {
			// provider 的宿主行程已經死亡,不能使用這個 binder 了,
            handleUnstableProviderDiedLocked(jBinder, true);
            return null;
        }
		// 如果我們有一個 ProviderRefCount 物件,就僅僅增加參考計數;
		// 否則,說明這個 provider 沒有被參考計數,也不需要被釋放,
        ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
        if (prc != null) {
			// 增加參考計數
            incProviderRefLocked(prc, stable);
        }
        return provider;
    }
}

mProviderMap 是一個 ArrayMap 物件,是 ActivityThread 的一個成員變數,表示在該行程記錄的 provider 資訊,不過,目前它還是一個空的 ArrayMap 物件,

mProviderMap 是一個重要的集合,我們繪制下它的結構圖如下:

在這里插入圖片描述

2.2.6.2 ActivityManagerNative.getDefault() 方法

// ActivityManagerNative 類:
static public IActivityManager getDefault() {
    return gDefault.get();
}

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        IBinder b = ServiceManager.getService("activity");
        IActivityManager am = asInterface(b);
        return am;
    }
};
// 因為這里客戶端和服務端不處于同一個行程,所以這個方法回傳的是 ActivityManagerProxy 物件,
static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
        (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }
    return new ActivityManagerProxy(obj);
}
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

這里采用的是區域單例技術,保證獲取到的 IActivityManager 物件總是一個物件,實際上是 ActivityManagerProxy 物件,

2.2.7 ActivityManagerProxy.getContentProvider() 方法

  • IApplicationThread caller, ActivityThread 的 ApplicationThread 物件 mApplicationThread,是 binder 服務端物件

  • String name, “com.wzc.chapter_9.bookprovider”

  • int userId, 0

  • boolean stable, false

class ActivityManagerProxy implements IActivityManager
{
	public ContentProviderHolder getContentProvider(IApplicationThread caller,
			String name, int userId, boolean stable) throws RemoteException {
		Parcel data = Parcel.obtain();
		Parcel reply = Parcel.obtain();
		data.writeInterfaceToken(IActivityManager.descriptor);
		data.writeStrongBinder(caller != null ? caller.asBinder() : null);
		data.writeString(name);
		data.writeInt(userId);
		data.writeInt(stable ? 1 : 0);
        // 見[【2.2.8】
		mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
		reply.readException();
		int res = reply.readInt();
		ContentProviderHolder cph = null;
		if (res != 0) {
			cph = ContentProviderHolder.CREATOR.createFromParcel(reply);
		}
		data.recycle();
		reply.recycle();
		return cph;
	}
}

這個方法仍是在客戶端行程呼叫的,

mRemote.transact() 是客戶端行程發起 binder 通信的方法,經過 binder 驅動,最后會到 binder 服務端 ActivityManagerNativeonTransact() 方法,

這里遠程呼叫回傳的 reply 值,最侄訓轉為一個 ContentProviderHolder 物件,回傳給客戶端呼叫者,從 ContentProviderHolder 的名字來看,它是 ContentProvider 的持有者或者說 ContentProviderHolder 的容器,

ContentProviderHolder 類是 IActivityManager 介面的嵌套內部類,

public static class ContentProviderHolder implements Parcelable {
    public final ProviderInfo info;
    public IContentProvider provider;
    public IBinder connection;
    public boolean noReleaseNeeded;
    public ContentProviderHolder(ProviderInfo _info) {
        info = _info;
    }
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        info.writeToParcel(dest, 0);
        if (provider != null) {
            dest.writeStrongBinder(provider.asBinder());
        } else {
            dest.writeStrongBinder(null);
        }
        dest.writeStrongBinder(connection);
        dest.writeInt(noReleaseNeeded ? 1:0);
    }
    public static final Parcelable.Creator<ContentProviderHolder> CREATOR
            = new Parcelable.Creator<ContentProviderHolder>() {
        @Override
        public ContentProviderHolder createFromParcel(Parcel source) {
            return new ContentProviderHolder(source);
        }
        @Override
        public ContentProviderHolder[] newArray(int size) {
            return new ContentProviderHolder[size];
        }
    };
    private ContentProviderHolder(Parcel source) {
        info = ProviderInfo.CREATOR.createFromParcel(source);
        // 這里拿到把 BinderProxy 物件,轉換成了 ContentProviderProxy 物件,
        provider = ContentProviderNative.asInterface(
            source.readStrongBinder());
        connection = source.readStrongBinder();
        noReleaseNeeded = source.readInt() != 0;
    }
}

可以看到,ContentProviderHolder 是一個實作了 Parcelable 介面的類,所以它可以被序列化和反序列化,它封裝了 provider 在清單中注冊的資訊,ContentProviderProxy 物件等,

2.2.8 ActivityManagerNative.onTransact() 方法

public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
	@Override
	public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
			throws RemoteException {
		switch (code) {
			case GET_CONTENT_PROVIDER_TRANSACTION: {
				data.enforceInterface(IActivityManager.descriptor);
				IBinder b = data.readStrongBinder();
				// 獲取的是 ApplicationThreadProxy 物件
				IApplicationThread app = ApplicationThreadNative.asInterface(b);
				String name = data.readString();
				int userId = data.readInt();
				boolean stable = data.readInt() != 0;
                // 呼叫到這里,進入 AMS,見【2.2.9】
				ContentProviderHolder cph = getContentProvider(app, name, userId, stable);
				reply.writeNoException();
				if (cph != null) {
					reply.writeInt(1);
					cph.writeToParcel(reply, 0);
				} else {
					reply.writeInt(0);
				}
				return true;
			}
		}
	}
}

這個方法是運行在服務端行程了,在這里是 system_server 行程,

ActivityManagerNative 是抽象類,getContentProvider() 是它的一個抽象方法,

ActivityManagerService 繼承了 ActivityManagerNative,實作了 getContentProvider() 這個抽象方法,

2.2.9 ActivityManagerService.getContentProvider() 方法

  • IApplicationThread caller, ApplicationThreadProxy 物件
  • String name, “com.wzc.chapter_9.bookprovider”
  • int userId, 0
  • boolean stable, false
@Override
public final ContentProviderHolder getContentProvider(
        IApplicationThread caller, String name, int userId, boolean stable) {
    enforceNotIsolatedCaller("getContentProvider");
    if (caller == null) {
        throw new SecurityException("");
    }
    // 見【2.2.10】
    return getContentProviderImpl(caller, name, null, stable, userId);
}

該方法的主要作用:檢查 IApplicationThread caller 是不是為 null,通過檢查后,呼叫 getContentProviderImpl() 方法,

2.2.10 ActivityManagerService.getContentProviderImpl() 方法

  • IApplicationThread caller, ApplicationThreadProxy 物件
  • String name, “com.wzc.chapter_9.bookprovider”
  • IBinder token, null
  • boolean stable, false
  • int userId, 0
public final class ActivityManagerService extends ActivityManagerNative {
	// ProviderMap 結構,見【2.2.10.1】
	final ProviderMap mProviderMap;
	// mLaunchingProviders 記錄客戶端正在等待publish的ContentProviderRecord物件
	// 應用當前正在被啟動,provider 在被發布后就會從這個串列里面移除掉,
	final ArrayList<ContentProviderRecord> mLaunchingProviders
			= new ArrayList<ContentProviderRecord>();
	public ActivityManagerService(Context systemContext) {
		mProviderMap = new ProviderMap(this);
	}
	private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
			String name, IBinder token, boolean stable, int userId) {
		ContentProviderRecord cpr;
		ContentProviderConnection conn = null;
		ProviderInfo cpi = null;
		synchronized(this) {
			long startTime = SystemClock.elapsedRealtime();
			ProcessRecord r = null;
			if (caller != null) { // call 不為 null,進入此分支
				// 獲取發起方的行程記錄 ProcessRecord 物件
				r = getRecordForAppLocked(caller);
			}
			boolean checkCrossUser = true;
			// 首先,檢查這個 provider 是否已經被發布了
			// 本次分析,我們的 provider 還未發布,因此這里會回傳 null,cpr 為 null,
			cpr = mProviderMap.getProviderByName(name, userId);
			// userId 是 0,UserHandle.USER_OWNER 也是 0,所以不會進入此分支
			if (cpr == null && userId != UserHandle.USER_OWNER) { 
				...
			}
			boolean providerRunning = cpr != null; // false,表示目標 provider 不存在,
			if (providerRunning) { // providerRunning 為 false,不會進入此分支
				...// 省略掉很多代碼,它們與本次分析無關,
			}
			boolean singleton;
			if (!providerRunning) { // 進入此分支
				try {
					// 通過 PKMS 來決議清單檔案中的 provider 資訊,ProviderInfo 物件
					cpi = AppGlobals.getPackageManager().
						resolveContentProvider(name,
							STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
				} catch (RemoteException ex) {
				}
				if (cpi == null) {
					return null;
				}
				// 本次我們分析的是單實體 provider,因此 singleton 為 true,
				singleton = isSingleton(cpi.processName, cpi.applicationInfo,
						cpi.name, cpi.flags)
						&& isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
				if (singleton) {
					userId = UserHandle.USER_OWNER;
				}
				// getAppInfoForUser 獲取包含用戶資訊的 ApplicationInfo 物件, 
				cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
				String msg;
				// 檢查發起方行程是否有權限獲取此 provider
				if ((msg = checkContentProviderPermissionLocked(cpi, r, userId, !singleton))
						!= null) {
					throw new SecurityException(msg);
				}
				...
				ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
				// 根據 class,從 mProviderMap 里面獲取 ContentProviderRecord
				// 本次分析,provider 還未發布,所以回傳 null,所以 cpr 為 null,
				cpr = mProviderMap.getProviderByClass(comp, userId);
				final boolean firstClass = cpr == null; // firstClass 為 true
				if (firstClass) {
					final long ident = Binder.clearCallingIdentity();
					try {
						ApplicationInfo ai =
							AppGlobals.getPackageManager().
								getApplicationInfo(
										cpi.applicationInfo.packageName,
										STOCK_PM_FLAGS, userId);
						if (ai == null) {
							Slog.w(TAG, "No package info for content provider "
									+ cpi.name);
							return null;
						}
						ai = getAppInfoForUser(ai, userId);
						// 創建 ContentProviderRecord 物件
						cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
					} catch (RemoteException ex) {
					} finally {
						Binder.restoreCallingIdentity(ident);
					}
				}
				// 到這里,ContentProviderRecord cpr 物件一定不會為 null 了,
				// canRunHere 判斷 ContentProvider 是否能運行在發起方所在行程
				// 本次分析,canRunHere() 回傳 false,見【2.2.10.2】分析
				if (r != null && cpr.canRunHere(r)) { // 不會進入此分支
					return cpr.newHolder(null);
				}
				// 遍歷 mLaunchingProviders,如果有元素等于 cpr,就跳出回圈,
				final int N = mLaunchingProviders.size();
				int i;
				for (i=0; i<N; i++) {
					if (mLaunchingProviders.get(i) == cpr) {
						break;
					}
				}
				if (i >= N) { // i >= N,說明上面的回圈走完了,沒有中途跳出,也就是說,沒有發現有元素等于 cpr,
					final long origId = Binder.clearCallingIdentity();
					try {
						try {
							AppGlobals.getPackageManager().setPackageStoppedState(
									cpr.appInfo.packageName, false, userId);
						} catch (RemoteException e) {
						} catch (IllegalArgumentException e) {
						}
						// 獲取 provider 所在行程(行程名字:"com.wzc.chapter_9.provider")的行程記錄物件
						ProcessRecord proc = getProcessRecordLocked(
								cpi.processName, cpr.appInfo.uid, false);
						// 行程記錄存在且其 IApplicationThread 成員存在,則計劃安裝此provider,	
						// 本次分析,目標行程還沒有啟動,所以不會進入此分支
						if (proc != null && proc.thread != null) {
							proc.pubProviders.put(cpi.name, cpr);
							try {
								proc.thread.scheduleInstallProvider(cpi);
							} catch (RemoteException e) {
							}
						} else {
							// 否則,開啟對應的行程,見【2.2.11】
							proc = startProcessLocked(cpi.processName,
									cpr.appInfo, false, 0, "content provider",
									new ComponentName(cpi.applicationInfo.packageName,
											cpi.name), false, false, false);
							if (proc == null) {
								return null;
							}
						}
                        // 把目標行程物件賦值給 cpr 的 launchingApp 成員變數
						cpr.launchingApp = proc;
						mLaunchingProviders.add(cpr);
					} finally {
						Binder.restoreCallingIdentity(origId);
					}
				}
				if (firstClass) {
					mProviderMap.putProviderByClass(comp, cpr);
				}
				mProviderMap.putProviderByName(name, cpr);
				conn = incProviderCountLocked(r, cpr, token, stable);
				if (conn != null) {
					conn.waiting = true;
				}
			}
		}
        // 回圈等待 provider 發布
        // 本次分析,cpr.provider 為 null,因此會進入 while 回圈,
		synchronized (cpr) {
			while (cpr.provider == null) {
				if (cpr.launchingApp == null) {
					return null;
				}
				try {
					if (conn != null) {
						conn.waiting = true;
					}
					cpr.wait();
				} catch (InterruptedException ex) {
				} finally {
					if (conn != null) {
						conn.waiting = false;
					}
				}
			}
		}
		return cpr != null ? cpr.newHolder(conn) : null;
	}
}

該方法的主要作用:

  1. 嘗試從 AMS 持有的資料結構 mProviderMap 中獲取到 ContentProviderRecord 物件;
  2. 如果 1 無法拿到 ContentProviderRecord 物件,說明 provider 還未發布,對發起方行程進行權限檢查等操作后,創建一個 ContentProviderRecord 物件;
  3. 檢查 provider 是否可以運行在發起方行程里面,本次不可以;
  4. 判斷 ContentProviderRecord 是否是客戶端在等待發布的,不是,則進入 5,是,則進入 6;
  5. 判斷 provider 的目標行程是否存在,不存在,則需要去開啟對應的目標行程;
  6. ContentProviderRecord 存到 mProviderMap 資料結構中,增加 provider 參考計數;
  7. 回圈等待 provider 發布,

2.2.10.1 ProviderMap 類

public final class ProviderMap {

    private static final String TAG = "ProviderMap";

    private final ActivityManagerService mAm;

	// 全域集合:存放的是單實體的 provider 記錄
	// 以 authority 為 key,以 CPR 為 value
    private final HashMap<String, ContentProviderRecord> mSingletonByName
            = new HashMap<String, ContentProviderRecord>();
	// 以 class 為 key,以 CPR 為 value
    private final HashMap<ComponentName, ContentProviderRecord> mSingletonByClass
            = new HashMap<ComponentName, ContentProviderRecord>();

	// 按用戶的userId,來管理對應的集合:存放的是多實體的 provider 記錄,
    private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser
            = new SparseArray<HashMap<String, ContentProviderRecord>>();
    private final SparseArray<HashMap<ComponentName, ContentProviderRecord>> mProvidersByClassPerUser
            = new SparseArray<HashMap<ComponentName, ContentProviderRecord>>();

    ProviderMap(ActivityManagerService am) {
        mAm = am;
    }

    ContentProviderRecord getProviderByName(String name) {
        return getProviderByName(name, -1);
    }

    ContentProviderRecord getProviderByName(String name, int userId) {
        // 先去全域集合里面查找
        ContentProviderRecord record = mSingletonByName.get(name);
        if (record != null) {
            return record;
        }

        // 再去當前用戶的集合里面查找
        return getProvidersByName(userId).get(name);
    }

    ContentProviderRecord getProviderByClass(ComponentName name) {
        return getProviderByClass(name, -1);
    }

    ContentProviderRecord getProviderByClass(ComponentName name, int userId) {
        // 先去全域集合里面查找
        ContentProviderRecord record = mSingletonByClass.get(name);
        if (record != null) {
            return record;
        }

        // 再去當前用戶的集合里面查找
        return getProvidersByClass(userId).get(name);
    }

    void putProviderByName(String name, ContentProviderRecord record) {
        if (record.singleton) {
            mSingletonByName.put(name, record);
        } else {
            final int userId = UserHandle.getUserId(record.appInfo.uid);
            getProvidersByName(userId).put(name, record);
        }
    }

    void putProviderByClass(ComponentName name, ContentProviderRecord record) {
        if (record.singleton) {
            mSingletonByClass.put(name, record);
        } else {
            final int userId = UserHandle.getUserId(record.appInfo.uid);
            getProvidersByClass(userId).put(name, record);
        }
    }

    void removeProviderByName(String name, int userId) {
        if (mSingletonByName.containsKey(name)) {
            mSingletonByName.remove(name);
        } else {
            if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
            HashMap<String, ContentProviderRecord> map = getProvidersByName(userId);
            map.remove(name);
            if (map.size() == 0) {
                mProvidersByNamePerUser.remove(userId);
            }
        }
    }

    void removeProviderByClass(ComponentName name, int userId) {
        if (mSingletonByClass.containsKey(name)) {
            mSingletonByClass.remove(name);
        } else {
            if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
            HashMap<ComponentName, ContentProviderRecord> map = getProvidersByClass(userId);
            map.remove(name);
            if (map.size() == 0) {
                mProvidersByClassPerUser.remove(userId);
            }
        }
    }

    private HashMap<String, ContentProviderRecord> getProvidersByName(int userId) {
        if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
        final HashMap<String, ContentProviderRecord> map = mProvidersByNamePerUser.get(userId);
        if (map == null) {
            HashMap<String, ContentProviderRecord> newMap = new HashMap<String, ContentProviderRecord>();
            mProvidersByNamePerUser.put(userId, newMap);
            return newMap;
        } else {
            return map;
        }
    }

    HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int userId) {
        if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
        final HashMap<ComponentName, ContentProviderRecord> map
                = mProvidersByClassPerUser.get(userId);
        if (map == null) {
            HashMap<ComponentName, ContentProviderRecord> newMap
                    = new HashMap<ComponentName, ContentProviderRecord>();
            mProvidersByClassPerUser.put(userId, newMap);
            return newMap;
        } else {
            return map;
        }
    }
}

雖然這個類的名字叫 ProviderMap,但是它并不是一個 Map 的子類,這個類采用組合的方式,內部管理了 mSingletonByNamemSingletonByClassmProvidersByNamePerUsermProvidersByClassPerUser四個集合物件,并定義了方法來實作元素的添加,獲取,洗掉操作,

思考一下:為什么要使用組合的方式而不是繼承的方式來實作 ProviderMap

因為采用繼承的方式,就要受限于父類的介面;而這里要實作對單實體集合和多實體集合的分別管理,采用組合可以達到這種目的,

這個類在后面用的比較多,所以這里重點說明了一下它的結構,

2.2.10.2 ContentProviderRecord.canRunHere() 方法

  • ProcessRecord app, 呼叫方行程記錄物件
final class ContentProviderRecord {
	public final ProviderInfo info;
	final int uid;
	
	public boolean canRunHere(ProcessRecord app) {
		return (info.multiprocess || info.processName.equals(app.processName))
				&& uid == app.info.uid;
	}
}

該方法的主要作用:判斷 provider 是否可以運行在呼叫方行程,需要滿足兩個條件:

  1. 條件一:provider 在清單檔案中設定了 android:multiprocess="true",或者 provider 的行程名字和呼叫方行程名字一致;
  2. 條件二:provider 的 uid 和呼叫方行程的 uid 一致,

本次分析,不滿足條件一:provider 在清單檔案中沒有設定 android:multiprocess="true",且provider 的行程名字("com.wzc.chapter_9.provider")和呼叫方行程名字("com.wzc.chapter_9")不一致,所以這個方法回傳 false,即 provider 不可以運行在呼叫方行程,

2.2.11 ActivityManagerService.startProcessLocked() 方法

  • String processName, “com.wzc.chapter_9.provider”
  • ApplicationInfo info, 清單檔案中的 application 節點資訊
  • boolean knownToBeDead, false
  • int intentFlags, 為 0,
  • String hostingType, “content provider”
  • ComponentName hostingName, ContentProvider 的組件名物件,即包名 + 類名的組合
  • boolean allowWhileBooting, false
  • boolean isolated, false
  • boolean keepIfLarge false
final ProcessRecord startProcessLocked(String processName,
        ApplicationInfo info, boolean knownToBeDead, int intentFlags,
        String hostingType, ComponentName hostingName, boolean allowWhileBooting,
        boolean isolated, boolean keepIfLarge) {
    return startProcessLocked(processName, info, knownToBeDead, intentFlags, hostingType,
            hostingName, allowWhileBooting, isolated, 0 /* isolatedUid */, keepIfLarge,
            null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */,
            null /* crashHandler */);
}

呼叫 startProcessLocked 的多載方法:

  • String processName, “com.wzc.chapter_9.provider”
  • ApplicationInfo info, 清單檔案中的 application 節點資訊
  • boolean knownToBeDead, false
  • int intentFlags, 為 0,
  • String hostingType, “content provider”
  • ComponentName hostingName, ContentProvider 的組件名物件,即包名 + 類名的組合
  • boolean allowWhileBooting, false
  • boolean isolated, false
  • int isolatedUid, 0
  • boolean keepIfLarge false
  • String abiOverride, null
  • String entryPoint, null
  • String[] entryPointArgs, null
  • Runnable crashHandler, null
final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
        boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName,
        boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
        String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
    long startTime = SystemClock.elapsedRealtime();
    ProcessRecord app;
    if (!isolated) { // isolated 為 false,進入此分支
		// 獲取行程名為 "com.wzc.chapter_9.provider" 的行程記錄物件,這里還沒有,所以回傳 null,
        app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
    } else {
        ...
    }
	
    if (app != null && app.pid > 0) { // app 為 null,不會進入此分支
        ...
    }
    String hostingNameStr = hostingName != null
            ? hostingName.flattenToShortString() : null;
    if (!isolated) { // 進入此分支
        if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) { // intentFlags 為 0,不會進入此分支
            ...
        } else { // 進入此分支
            mProcessCrashTimes.remove(info.processName, info.uid);
            if (mBadProcesses.get(info.processName, info.uid) != null) {
                mBadProcesses.remove(info.processName, info.uid);
                if (app != null) {
                    app.bad = false;
                }
            }
        }
    }
    if (app == null) { // 進入此分支
		// 創建新的 ProcessRecord 物件,見【2.2.11.1】,
        app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
        app.crashHandler = crashHandler;
        if (app == null) {
            return null;
        }
        mProcessNames.put(processName, app.uid, app);
        ...
    } else {
        ...
    }
	// 如果系統還沒有就緒,那么就推遲啟動行程直到系統就緒,
    if (!mProcessesReady
            && !isAllowedWhileBooting(info)
            && !allowWhileBooting) {
        if (!mProcessesOnHold.contains(app)) {
            mProcessesOnHold.add(app);
        }
        return app;
    }
	// 啟動新行程,見【2.2.12】
    startProcessLocked(
            app, hostingType, hostingNameStr, abiOverride, entryPoint, entryPointArgs);
    return (app.pid != 0) ? app : null;
}

2.2.11.1 ActivityManagerService.newProcessRecordLocked() 方法

  • ApplicationInfo info, 清單檔案中的 application 節點資訊
  • String customProcess, “com.wzc.chapter_9.provider”
  • boolean isolated, false
  • int isolatedUid, 0
final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
        boolean isolated, int isolatedUid) {
    String proc = customProcess != null ? customProcess : info.processName;
    BatteryStatsImpl.Uid.Proc ps = null;
    BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
    int uid = info.uid;
    if (isolated) { // isolated 為 false,所以不會進入此分支
        ...
    }
    return new ProcessRecord(stats, info, proc, uid);
}

該方法的作用:創建了一個 ProcessRecord 物件而已,

2.2.12 ActivityManagerService.startProcessLocked() 方法

  • ProcessRecord app, 新創建的 ProcessRecord 物件
  • String hostingType, “service”
  • String hostingNameStr, Service 的組件名物件轉換成的字串,即"com.wzc.chapter_9/.provider.BookProvider"
  • String abiOverride, null
  • String entryPoint, null
  • String[] entryPointArgs, null
private final void startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
    long startTime = SystemClock.elapsedRealtime();
    if (app.pid > 0 && app.pid != MY_PID) { // app.pid 為 0,不會進入此分支
        ...
    }
    mProcessesOnHold.remove(app);
    updateCpuStats();
    try {
        int uid = app.uid;
        int[] gids = null;
        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
        if (!app.isolated) { // isolated 為 false,會進入此分支
            ...
        }
        ...
        boolean isActivityProcess = (entryPoint == null); // true
		// entryPoint 是 null,所以 entryPoint 會被賦值為 "android.app.ActivityThread"
        if (entryPoint == null) entryPoint = "android.app.ActivityThread";
		// 開啟行程:請求 zygote 去開啟行程,見[2.2.13]
        Process.ProcessStartResult startResult = Process.start(entryPoint,
                app.processName, uid, uid, gids, debugFlags, mountExternal,
                app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                app.info.dataDir, entryPointArgs);
        ...
		// 設定 ProcessRecord app 的成員變數
        app.setPid(startResult.pid);
        app.usingWrapper = startResult.usingWrapper;
        app.removed = false;
        app.killed = false;
        app.killedByAm = false;
        synchronized (mPidsSelfLocked) {
            // 存盤新行程的 pid 和 ProcessRecord 物件鍵值對到 mPidsSelfLocked,
            this.mPidsSelfLocked.put(startResult.pid, app);
            if (isActivityProcess) {
                Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
                msg.obj = app;
                mHandler.sendMessageDelayed(msg, startResult.usingWrapper
                        ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
            }
        }
    } catch (RuntimeException e) {
		...
    }
}

2.2.13 Process.start() 方法

  • final String processClass, “android.app.ActivityThread”
  • final String niceName, “com.wzc.chapter_9.provider”
  • int uid,
  • int gid,
  • int[] gids,
  • int debugFlags,
  • int mountExternal,
  • int targetSdkVersion, 目標 sdk 版本
  • String seInfo,
  • String abi, 架構
  • String instructionSet, 指令集
  • String appDataDir,
  • String[] zygoteArgs, null
public static final ProcessStartResult start(final String processClass,
                              final String niceName,
                              int uid, int gid, int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] zygoteArgs) {
    try {
		// 通過 zygote 開啟行程
        return startViaZygote(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, zygoteArgs);
    } catch (ZygoteStartFailedEx ex) {
        throw new RuntimeException(
                "Starting VM process through Zygote failed", ex);
    }
}

system_server 行程里,呼叫 Process.start() 方法,通過 socket 向 zygote 行程發送創建新行程的請求;

在 zygote 行程里面,在執行ZygoteInit.main()后便進入runSelectLoop()回圈體內,當有客戶端連接時便會執行ZygoteConnection.runOnce()方法,再經過層層呼叫后 fork 出新的應用行程;

新的行程創建后,執行handleChildProc方法,最后呼叫ActivityThread.main()方法,

這部分詳細請參考理解Android行程創建流程-袁輝輝,

本文會直接跳到 ActivityThread.main() 方法繼續分析,

2.2.14 ActivityThread.main() 方法

public static void main(String[] args) {
    ...
    // 準備主執行緒的 Looper 物件
    Looper.prepareMainLooper();
	// 創建 ActivityThread 物件
    ActivityThread thread = new ActivityThread();
	// 呼叫 ActivityThread 物件的 attach 方法
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    // 運行訊息佇列
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

2.2.15 ActivityThread.attach() 方法

  • boolean system, false,表示不是系統行程,而是普通應用行程
private void attach(boolean system) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    if (!system) { // system 為 false,進入此分支
        ...
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        // mgr 實際上是 ActivityManagerProxy 物件
        final IActivityManager mgr = ActivityManagerNative.getDefault();
        try {
            mgr.attachApplication(mAppThread);
        } catch (RemoteException ex) {
            // Ignore
        }
        ...
    } else {
        ...
    }
    ...
}

2.2.16 ActivityManagerService.attachApplication() 方法

  • IApplicationThread thread, ApplicationThreadProxy 物件
@Override
public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid);
        Binder.restoreCallingIdentity(origId);
    }
}

2.2.17 ActivityManagerService.attachApplicationLocked() 方法

  • IApplicationThread thread, ApplicationThreadProxy 物件
  • int pid, provider 行程的 pid
// 存盤了所有運行的行程,以pid為鍵,以ProcessRecord為值
final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();
private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid) {
    ProcessRecord app;
    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
			// 根據 pid,獲取到之前存盤的 ProcessRecord 物件,不為 null,
            app = mPidsSelfLocked.get(pid);
        }
    } else {
        app = null;
    }
    if (app == null) { // 不會進入此分支
       ...
    }
    if (app.thread != null) { // app.thread 目前為 null,不會進入此分支
        handleAppDiedLocked(app, true, true);
    }
    ...
	// mProcessesReady 為 true,所以這里 normalMode 為 true
    boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); 
	// 獲取清單檔案中注冊的 provider 資訊
    List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
    try {
        ...
        ApplicationInfo appInfo = app.instrumentationInfo != null
                ? app.instrumentationInfo : app.info;
        app.compat = compatibilityInfoForPackageLocked(appInfo);
        if (profileFd != null) {
            profileFd = profileFd.dup();
        }
        ProfilerInfo profilerInfo = profileFile == null ? null
                : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
		// 呼叫到這里,thread 是 ApplicationThreadProxy 物件,見【2.2.18】
        thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
                isRestrictedBackupMode || !normalMode, app.persistent,
                new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
                mCoreSettingsObserver.getCoreSettingsLocked());
        updateLruProcessLocked(app, false, null);
        app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
    } catch (Exception e) {
        ...
    }
    ...// 省略與本次分析無關的代碼
    return true;
}

2.2.17.1 ActivityManagerService.generateApplicationProvidersLocked() 方法

private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
    List<ProviderInfo> providers = null;
    try {
		// 獲取清單檔案中注冊的 provider 資訊串列
        providers = AppGlobals.getPackageManager().
            queryContentProviders(app.processName, app.uid,
                    STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS);
    } catch (RemoteException ex) {
    }
    int userId = app.userId;
    if (providers != null) {
        int N = providers.size();
        app.pubProviders.ensureCapacity(N + app.pubProviders.size());
        for (int i=0; i<N; i++) {
            ProviderInfo cpi =
                (ProviderInfo)providers.get(i);            
            ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
            ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId);
            if (cpr == null) {
                cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton);
                mProviderMap.putProviderByClass(comp, cpr);
            }
			// 把 provider 資訊添加到 ProcessRecord 的 pubProviders ArrayMap 里面,
            app.pubProviders.put(cpi.name, cpr);
            if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
                app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,
                        mProcessStats);
            }
        }
    }
    return providers;
}

2.2.18 ApplicationThreadProxy.bindApplication() 方法

public final void bindApplication(...) throws RemoteException {
    ...
    mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
            IBinder.FLAG_ONEWAY);
    data.recycle();
}

這個方法仍是在 system_server 行程(是 binder 客戶端行程)呼叫的,

mRemote.transact() 是 system_server 行程發起 binder 通信的方法,經過 binder 驅動,最后會到 provider 的目標行程(是 binder 服務端)ApplicationThreadNativeonTransact() 方法,

2.2.19 ApplicationThreadNative.onTransact() 方法

public abstract class ApplicationThreadNative extends Binder
        implements IApplicationThread {
	@Override
	public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
			throws RemoteException {
		case BIND_APPLICATION_TRANSACTION:
		{
			...
            // 呼叫到這里,見【2.2.20】
			bindApplication(packageName, info, providers, testName, profilerInfo, testArgs,
					testWatcher, uiAutomationConnection, testMode, openGlTrace,
					restrictedBackupMode, persistent, config, compatInfo, services, coreSettings);
			return true;
		}				
	}			
}

這個方法是運行在 provider 的目標行程里的,

ApplicationThreadNative 是一個抽象類,而 bindApplication()是它的一個抽象方法,

ApplicationThread 是繼承于 ApplicationThreadNative 的具體類,它實作了 bindApplication() 方法,

ApplicationThread 類是 ActivityThread 的私有內部類,

2.2.20 ApplicationThread.bindApplication() 方法

public final void bindApplication(...) {
    ...
    // 把資訊封裝在 AppBindData 里,通過 handler 發送到主執行緒,
    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providers;
	...
    sendMessage(H.BIND_APPLICATION, data);
}

這個方法目前是運行在 provider 的目標行程的 binder 執行緒池里面的,

2.2.21 H.handleMessage() 方法

public final class ActivityThread {
	final H mH = new H();
	private class H extends Handler {
		public void handleMessage(Message msg) {
			case BIND_APPLICATION:
				AppBindData data = (AppBindData)msg.obj;
				handleBindApplication(data);
				break;
		}
	}
}

這個方法目前是運行在 provider 的目標行程的主執行緒里面的,

2.2.22 ActivityThread.handleBindApplication() 方法

private void handleBindApplication(AppBindData data) {
	...
    // 獲取 LoadedApk 物件,賦值給 data.info
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
	// 來自 ProcessRecord 的 instrumentationClass,這個值只有在 AMS 的 startInstrumentation() 方法里賦值了
	// 所以,本次分析 data.instrumentationName 為 null,不會進入 if 分支
    if (data.instrumentationName != null) { 
		...
    } else {
		// 創建 Instrumentation 物件
        mInstrumentation = new Instrumentation();
    }
    try {
		// 創建 Application 物件
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
        // don't bring up providers in restricted mode; they may depend on the
        // app's custom Application class
        if (!data.restrictedBackupMode) { // 非限制模式,進入 if 分支
			// 獲取清單中注冊的 provider 資訊串列
            List<ProviderInfo> providers = data.providers;
            if (providers != null) {
				// 安裝 content providers,見【2.2.23】
                installContentProviders(app, providers);
            }
        }
        try {
			// 內部會呼叫 app.onCreate() 方法
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {}
    } finally {}
}

該方法的主要作用:

  1. 創建 Instrumentation 物件;
  2. 創建 Application 物件;
  3. 安裝 content providers:創建 provider 物件,回呼了 provider 的 onCreate() 方法,并存盤了 provider 資訊到對應的資料結構中;
  4. 回呼 Application 物件的 onCreate() 方法,

從這里可以知道,ContentProvideronCreate() 方法在 ApplicationonCreate() 方法之前回呼,

ContentProvideronCreate() 方法是在主執行緒呼叫的,那么如果在 ContentProvideronCreate() 方法里執行了耗時操作,勢必會推遲ApplicationonCreate() 方法的回呼,也就是說,推遲了應用的初始化操作了,對于用戶來說,就是應用啟動緩慢了,這多么不好啊,

所以,一定不要在 ContentProvideronCreate() 方法里面執行耗時操作,

2.2.23 ActivityThread.installContentProviders() 方法

  • Context context, provider 行程的 Application 物件
  • List providers, 清單檔案中的 provider 資訊串列
private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<IActivityManager.ContentProviderHolder> results =
        new ArrayList<IActivityManager.ContentProviderHolder>();
	// 遍歷清單檔案中的 provider 資訊串列
    for (ProviderInfo cpi : providers) {
		// 安裝每一個 provider,本次分析我們只有一個 provider 待安裝,見【2.2.24】
        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }
    try {
        // 見【2.2.25】
        // ActivityManagerNative.getDefault() 是一個 ActivityManagerProxy 物件,通過它,經過 binder 驅動,向 binder 服務端的 AMS 發起遠程呼叫,
        ActivityManagerNative.getDefault().publishContentProviders(
            getApplicationThread(), results);
    } catch (RemoteException ex) {
    }
}

該方法的主要作用:

  1. 安裝 provider;
  2. 發布安裝結果:由 provider 目標行程向 system_server 行程的 AMS 發起遠程請求,

2.2.24 ActivityThread.installProvider() 方法

  • Context context, provider 行程的 Application 物件
  • IActivityManager.ContentProviderHolder holder, null
  • ProviderInfo info, 清單檔案中的 provider 資訊
  • boolean noisy, false
  • boolean noReleaseNeeded, true
  • boolean stable, true
// 在 2.2.6.2 中,我們繪制了這個集合的結構圖
final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
    = new ArrayMap<ProviderKey, ProviderClientRecord>();
final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName
        = new ArrayMap<ComponentName, ProviderClientRecord>();	
private IActivityManager.ContentProviderHolder installProvider(Context context,
        IActivityManager.ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) { // 進入此 if 分支
		...
        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
			// 反射創建 ContentProvider 物件,本次分析創建的是 BookProvider 物件,
            localProvider = (ContentProvider)cl.
                loadClass(info.name).newInstance();
			// 這里獲取的是 Transport 物件,是 IContentProvider 介面的 binder 服務端物件,
            provider = localProvider.getIContentProvider();
			// 把 Context 物件和 ContentProvider 相關聯,并回呼 BookProvider 物件的 onCreate() 方法,見【2.2.24.1】
            localProvider.attachInfo(c, info);
        } catch (java.lang.Exception e) {}
    } else {
        ...
    }
    IActivityManager.ContentProviderHolder retHolder;
    synchronized (mProviderMap) {
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) { // 進入此 if 分支
            ComponentName cname = new ComponentName(info.packageName, info.name);
            ProviderClientRecord pr = mLocalProvidersByName.get(cname);
            if (pr != null) {
                provider = pr.mProvider;
            } else { // 進入 else 分支
                holder = new IActivityManager.ContentProviderHolder(info);
                holder.provider = provider;
                holder.noReleaseNeeded = true;
				// 把 provider 存盤在 mProviderMap 里面,見【2.2.24.2】
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                mLocalProviders.put(jBinder, pr);
                mLocalProvidersByName.put(cname, pr);
            }
            retHolder = pr.mHolder;
        } else {
            ...
        }
    }
    return retHolder;
}

該方法的主要作用:

  1. 通過反射創建 ContentProvider 物件;
  2. 通過 ContentProvider.attachInfo() 方法把 Context 物件和 ContentProvider 相關聯,并回呼 BookProvider 物件的 onCreate() 方法;
  3. 存盤 provider 資訊到相關的資料結構中,

2.2.24.1 ContentProvider.attachInfo() 方法

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    // 保證 mContext 物件只被設定一次,這樣 onCreate() 方法也是只會呼叫一次了,
    if (mContext == null) {
        mContext = context;
        ...
        // 回呼 BookProvider 的 onCreate() 方法
        ContentProvider.this.onCreate();
    }
}

該方法的主要作用:把 Context 物件和 ContentProvider 相關聯,并回呼 BookProvider 物件的 onCreate() 方法,

2.2.24.2 ActivityThread.installProviderAuthoritiesLocked() 方法

  • IContentProvider provider, ContentProvider 的 Transport 物件
  • ContentProvider localProvider, BookProvider 物件
  • IActivityManager.ContentProviderHolder holder,
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
        ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
    final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority);
    final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
    // 把 holder 封裝在 ProviderClientRecord 物件里面,
    final ProviderClientRecord pcr = new ProviderClientRecord(
            auths, provider, localProvider, holder);
    for (String auth : auths) {
        final ProviderKey key = new ProviderKey(auth, userId);
        final ProviderClientRecord existing = mProviderMap.get(key);
        if (existing != null) {
            Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
                    + " already published as " + auth);
        } else {
			// 把對應的 ProviderKey 和 ProviderClientRecord 存盤在 mProviderMap 集合里面,
            mProviderMap.put(key, pcr);
        }
    }
    return pcr;
}

2.2.25 ActivityManagerService.publishContentProviders() 方法

  • IApplicationThread caller, provider 目標行程的 ApplicationThread 物件
  • List providers, ContentProviderHolder 串列
public final void publishContentProviders(IApplicationThread caller,
        List<ContentProviderHolder> providers) {
    if (providers == null) {
        return;
    }
    synchronized (this) {
		// 獲取 provider 目標行程的行程記錄物件,已存在,所以 r 不為 null,
        final ProcessRecord r = getRecordForAppLocked(caller);
        final int N = providers.size();
        for (int i=0; i<N; i++) {
            ContentProviderHolder src = providers.get(i);
            if (src == null || src.info == null || src.provider == null) {
                continue;
            }
			// r.pubProviders 是 
			// final ArrayMap<String, ContentProviderRecord> pubProviders
			// 			= new ArrayMap<String, ContentProviderRecord>();
			// src.info.name 是 provider 的類名稱
			// 此處 dst 不為 null,因為在 2.2.17.1 中已經添加了 provider 資訊到這個集合里面了,
            ContentProviderRecord dst = r.pubProviders.get(src.info.name);
            if (dst != null) {
                ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                mProviderMap.putProviderByClass(comp, dst);
                String names[] = dst.info.authority.split(";");
                for (int j = 0; j < names.length; j++) {
                    mProviderMap.putProviderByName(names[j], dst);
                }
                ...
                synchronized (dst) {
					// 這行代碼非常關鍵,把 ContentProviderHolder src 的 provider 物件賦值給
					// ContentProviderRecord dst 的 provider 變數,
                    dst.provider = src.provider;
					// 把 provider 的目標行程記錄物件賦值給 ContentProviderRecord dst 的 proc 變數,
                    dst.proc = r;
                    dst.notifyAll();
                }
            }
        }
    }
}

2.2.26 ActivityManagerService.getContentProviderImpl() 方法跳出回圈等待 provider 發布

回到 2.2.10 的分析部分:

public final class ActivityManagerService extends ActivityManagerNative {
	final ProviderMap mProviderMap;
	final ArrayList<ContentProviderRecord> mLaunchingProviders
			= new ArrayList<ContentProviderRecord>();
	public ActivityManagerService(Context systemContext) {
		mProviderMap = new ProviderMap(this);
	}
	private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
			String name, IBinder token, boolean stable, int userId) {
		ContentProviderRecord cpr;
		ContentProviderConnection conn = null;
		ProviderInfo cpi = null;
		...
        // 回圈等待 provider 發布
        // 到這里,cpr.provider 不為 null,因此會結束 while 回圈,
		synchronized (cpr) {
			while (cpr.provider == null) {
				if (cpr.launchingApp == null) {
					return null;
				}
				try {
					if (conn != null) {
						conn.waiting = true;
					}
					cpr.wait();
				} catch (InterruptedException ex) {
				} finally {
					if (conn != null) {
						conn.waiting = false;
					}
				}
			}
		}
        // 把 cpr 封裝成 ContentProviderHolder 物件回傳
		return cpr != null ? cpr.newHolder(conn) : null;
	}
}

2.2.27 ActivityThread.acquireProvider() 方法

回到 2.2.6 的部分分析:

public final IContentProvider acquireProvider(
        Context c, String auth, int userId, boolean stable) {
	...
    IActivityManager.ContentProviderHolder holder = null;
    try {
		// 這個方法會獲取到結果 ContentProviderHolder 物件
        holder = ActivityManagerNative.getDefault().getContenProvider(
                getApplicationThread(), auth, userId, stable)
    } catch (RemoteException ex) {
    }
    if (holder == null) { // 不會進入此分支
        // 如果 AMS 回傳的 ContentProviderHolder 物件為 null,則直接回傳 null,
        return null;
    }
	// 安裝 provider 將會增加參考計數,并且打破競爭中的任何聯系,見【2.2.27.1】
    holder = installProvider(c, holder, holder.info,
            true /*noisy*/, holder.noReleaseNeeded, stable);
    return holder.provider;
}

這個方法執行完畢,就獲得了 ContentProviderProxy 物件了,

2.2.27.1 ActivityThread.installProvider() 方法

雖然我們在 2.2.24 中也分析了這個方法,但是和這里是有區別的:

2.2.24 中的 installProvider方法是發生在 provider 目標行程的主執行緒;

這里的 installProvider 方法是發生在客戶端行程的主執行緒,

  • Context context, ContextImpl 物件
  • IActivityManager.ContentProviderHolder holder, 不為 null
  • ProviderInfo info, 清單檔案中的 provider 資訊
  • boolean noisy, true
  • boolean noReleaseNeeded, true
  • boolean stable, false
private IActivityManager.ContentProviderHolder installProvider(Context context,
        IActivityManager.ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) { // 不會進入 if 分支
        ...
    } else { // 命中 else 分支
        provider = holder.provider;
    }
    IActivityManager.ContentProviderHolder retHolder;
    synchronized (mProviderMap) {
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) { // localProvider 為 null,不會進入 if 分支
            ...
        } else { // 進入 else 分支
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) { // prc 為 null,不會進入 if 分支
                ...
            } else { // 進入 else 分支
                ProviderClientRecord client = installProviderAuthoritiesLocked(
                        provider, localProvider, holder);
                if (noReleaseNeeded) { // 進入 if 分支
                    prc = new ProviderRefCount(holder, client, 1000, 1000);
                } else {
                    ...
                }
                mProviderRefCountMap.put(jBinder, prc);
            }
            retHolder = prc.holder;
        }
    }
    return retHolder;
}

該方法的作用:把 provider 資訊存入 mProviderMap 集合和 mProviderRefCountMap 集合中,

2.2.27.2 ActivityThread.installProviderAuthoritiesLocked() 方法

private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
        ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
    final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority);
    final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
    final ProviderClientRecord pcr = new ProviderClientRecord(
            auths, provider, localProvider, holder);
    for (String auth : auths) {
        final ProviderKey key = new ProviderKey(auth, userId);
        final ProviderClientRecord existing = mProviderMap.get(key);
        if (existing != null) {
            Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
                    + " already published as " + auth);
        } else {
            mProviderMap.put(key, pcr);
        }
    }
    return pcr;
}

該方法的作用:把 provider 資訊存入 mProviderMap 集合中,

2.2.28 ContentProviderProxy.query() 方法

final class ContentProviderProxy implements IContentProvider
{
	public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
			String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
					throws RemoteException {
		// 實體化 BulkCursorToCursorAdaptor 物件
		BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
		Parcel data = Parcel.obtain();
		Parcel reply = Parcel.obtain();
		try {
			...
			data.writeStrongBinder(adaptor.getObserver().asBinder());
			data.writeStrongBinder(cancellationSignal != null ? cancellationSignal.asBinder() : null);
			mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
			DatabaseUtils.readExceptionFromParcel(reply);
			if (reply.readInt() != 0) {
				BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply);
				adaptor.initialize(d);
			} else {
				adaptor.close();
				adaptor = null;
			}
			return adaptor;
		} 
	}	
}

這個方法是運行在客戶端行程的,

mRemote.transact() 是 客戶端行程發起 binder 通信的方法,經過 binder 驅動,最后會到 provider 的目標行程(是 binder 服務端)ContentProviderNativeonTransact() 方法,

2.2.29 ContentProviderNative.onTransact() 方法

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
	case QUERY_TRANSACTION:
	{
		...
		Cursor cursor = query(callingPkg, url, projection, selection, selectionArgs,
				sortOrder, cancellationSignal);
		if (cursor != null) {
			CursorToBulkCursorAdaptor adaptor = null;
			try {
				adaptor = new CursorToBulkCursorAdaptor(cursor, observer,
						getProviderName());
				cursor = null;
				BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
				adaptor = null;
				reply.writeNoException();
				reply.writeInt(1);
				d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
			} finally {}
		} else {
			reply.writeNoException();
			reply.writeInt(0);
		}
		return true;
	}
}

ContentProviderNative 是一個抽象類,query方法是它的一個抽象方法,

ContentProvider.TransportContentProviderNative 的具體實作類,

2.2.30 Transport.query() 方法

@Override
public Cursor query(String callingPkg, Uri uri, String[] projection,
        String selection, String[] selectionArgs, String sortOrder,
        ICancellationSignal cancellationSignal) {
    validateIncomingUri(uri);
    uri = getUriWithoutUserId(uri);
    if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
        return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
                CancellationSignal.fromTransport(cancellationSignal));
    }
    final String original = setCallingPackage(callingPkg);
    try {
        // 這里呼叫的就是 BookProvider 的 query 方法了,
        return ContentProvider.this.query(
                uri, projection, selection, selectionArgs, sortOrder,
                CancellationSignal.fromTransport(cancellationSignal));
    } finally {
        setCallingPackage(original);
    }
}

2.2.31 BookProvider.query() 方法

@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
    final int code = MATCHER.match(uri);
    if (code == CODE_BOOK_DIR || code == CODE_BOOK_ITEM) {
        Context context = getContext();
        if (context == null) {
            return null;
        }
        BookDao bookDao = AppDatabase.getInstance(context).bookDao();
        final Cursor cursor;
        if (code == CODE_BOOK_DIR) {
            cursor = bookDao.queryAll();
        } else {
            cursor = bookDao.queryById(ContentUris.parseId(uri));
        }
        cursor.setNotificationUri(context.getContentResolver(), uri);
        return cursor;
    } else {
        throw new IllegalArgumentException("Unknown URI: " + uri);
    }
}

3.最后

本文到這里就結束了,希望能夠幫助到大家,

關于本文需要討論的地方,可以在評論區留言討論,

4.參考

  1. 理解ContentProvider原理-袁輝輝
  2. ContentProvider參考計數-袁輝輝
  3. Android Cursor淺析

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/402093.html

標籤:其他

上一篇:Lua回到for回圈

下一篇:使用for回圈替換DNA序列中的壞核苷酸

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more