主頁 > 移動端開發 > Android筑基——BroadcastReceiver 的動態注冊、發送和接收程序(基于api21)

Android筑基——BroadcastReceiver 的動態注冊、發送和接收程序(基于api21)

2021-12-25 08:54:13 移動端開發

目錄

  • 1. 前言
  • 2. 正文
    • 2.1 廣播接收者的動態注冊
      • 2.1.1 ContextWrapper.registerReceiver() 方法
      • 2.1.2 ContextImpl.registerReceiver() 方法
      • 2.1.3 ContextImpl.registerReceiverInternal() 方法
        • 2.1.3.1 LoadedApk.getReceiverDispatcher() 方法
        • 2.1.3.2 new LoadedApk.ReceiverDispatcher() 方法
        • 2.1.3.3 ActivityManagerNative.getDefault() 方法
      • 2.1.4 ActivityManagerProxy.registerReceiver() 方法
      • 2.1.5 ActivityManagerNative.onTransact() 方法
      • 2.1.6 ActivityManagerService.registerReceiver() 方法
        • 2.1.6.1 IntentResolver.addFilter()方法
    • 2.2 發送普通廣播
      • 2.2.1 ContextWrapper.sendBroadcast() 方法
      • 2.2.2 ContextImpl.sendBroadcast() 方法
      • 2.2.3 ActivityManagerProxy.broadcastIntent() 方法
      • 2.2.4 ActivityManagerNative.onTransact() 方法
      • 2.2.5 ActivityManagerService.broadcastIntent() 方法
      • 2.2.6 ActivityManagerService.broadcastIntentLocked() 方法
        • 2.2.6.1 ActivityManagerService.collectReceiverComponents() 方法
        • 2.2.6.2 IntentResolver.queryIntent() 方法
        • 2.2.6.3 IntentResolver.buildResolveList() 方法
        • 2.2.6.4 new BroadcastRecord() 方法
        • 2.2.6.5 BroadcastQueue.enqueueParallelBroadcastLocked() 方法
    • 2.3 接收普通廣播
      • 2.3.1 BroadcastQueue.scheduleBroadcastsLocked() 方法
      • 2.3.2 BroadcastHandler.handleMessage() 方法
      • 2.3.3 BroadcastQueue.processNextBroadcast() 方法
      • 2.3.4 BroadcastQueue.deliverToRegisteredReceiverLocked() 方法
      • 2.3.5 BroadcastQueue.performReceiveLocked() 方法
      • 2.3.6 ApplicationThreadProxy.scheduleRegisteredReceiver() 方法
      • 2.3.7 ApplicationThreadNative.onTransact() 方法
      • 2.3.8 ApplicationThread.scheduleRegisteredReceiver() 方法
      • 2.3.9 InnerReceiver.performReceive() 方法
      • 2.3.10 ReceiverDispatcher.performReceive() 方法
      • 2.3.11 Args.run() 方法
      • 2.3.12 MyDynamicReceiver.onReceive() 方法
  • 3.最后
  • 4. 參考

1. 前言

BroadcastReceiver 的作業程序主要包括兩個方面的內容:

  1. 廣播接收者的注冊程序(靜態注冊和動態注冊);
  2. 廣播的發送(包括發送普通廣播、有序廣播和粘性廣播)和接收程序,

本文主要分析廣播接收者的動態注冊,普通廣播的發送和接收程序,

2. 正文

首先,看一下我們要分析的代碼:

  1. 定義廣播接收者,繼承 BroadcastReceiver 抽象類并重寫它的 onReceive() 方法:

    public class MyDynamicReceiver extends BroadcastReceiver {
        private static final String TAG = "MyDynamicReceiver";
        public static final String ACTION_LAUNCH_DYNAMIC = "com.wzc.receiver.LAUNCH_DYNAMIC";
    
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.d(TAG, "onReceive: receive action = " + action);
            if (ACTION_LAUNCH_DYNAMIC.equals(action)) {
                Log.d(TAG, "onReceive: do launch work...");
            }
        }
    }
    
  2. 點擊按鈕動態注冊以及動態注銷廣播接收者

    private MyDynamicReceiver receiver;
    // 動態注冊廣播接收者
    public void registerReceiver(View view) {
        if (null == receiver) {
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(ACTION_LAUNCH_DYNAMIC);
            receiver = new MyDynamicReceiver();
            registerReceiver(receiver, intentFilter);
        }
    }
    // 動態注銷廣播接收者
    public void unregisterReceiver(View view) {
        if (null != receiver) {
            unregisterReceiver(receiver);
            receiver = null;
        }
    }
    
  3. 點擊按鈕發送廣播

    public void sendBroadcast(View view) {
        Intent intent = new Intent();
        intent.setAction(ACTION_LAUNCH_DYNAMIC);
        sendBroadcast(intent);
    }
    

先點擊動態注冊廣播接收者按鈕,再點擊發送廣播按鈕,查看日志如下:

D/MyDynamicReceiver: onReceive: receive action = com.wzc.receiver.LAUNCH_DYNAMIC
D/MyDynamicReceiver: onReceive: do launch work...

如果不對廣播接收者進行動態注冊,直接點擊發送廣播按鈕,是看不到 onReceive 方法里的日志的,

現在我們開始查看相關的原始碼吧,

2.1 廣播接收者的動態注冊

2.1.1 ContextWrapper.registerReceiver() 方法

  • BroadcastReceiver receiver, MyDynamicReceiver 物件
  • IntentFilter filter, 添加了 action 的 IntentFilter 物件
@Override
public Intent registerReceiver(
    BroadcastReceiver receiver, IntentFilter filter) {
    return mBase.registerReceiver(receiver, filter);
}

這里的 ContextWrapper其實是裝飾器設計模式的應用了,類結構圖如下所示:
在這里插入圖片描述
呼叫裝飾類 ContextWrapperregisterReceiver 方法,內部真正呼叫的是核心實作類 ContextImplregisterReceiver 方法,所以 mBase 實際上是一個 ContextImpl 型別的物件,

2.1.2 ContextImpl.registerReceiver() 方法

  • BroadcastReceiver receiver, MyDynamicReceiver 物件
  • IntentFilter filter, 添加了 action 的 IntentFilter 物件
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    return registerReceiver(receiver, filter, null, null);
}

內部呼叫多載的 registerReceiver 方法

  • BroadcastReceiver receiver, MyDynamicReceiver 物件
  • IntentFilter filter, 添加了 action 的 IntentFilter 物件
  • String broadcastPermission, null
  • Handler scheduler, null
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
        String broadcastPermission, Handler scheduler) {
    return registerReceiverInternal(receiver, getUserId(),
            filter, broadcastPermission, scheduler, getOuterContext());
}

這里的 getOuterContext() 方法獲取的是 mOuterContext 物件,是通過 setOuterContext 方法賦值的,

我們這里的 ContextImpl 物件是在創建 MainActivity 的時候被初始化的,具體來說是在 ActivityThreadperformLaunchActivity 方法:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    Activity activity = null;
	...
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        // 創建 Activity 物件
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        if (activity != null) {
        	// 創建 ContextImpl 物件
            Context appContext = createBaseContextForActivity(r, activity);
            // 把 ContextImpl 物件和 Activity 物件相關聯
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.voiceInteractor);
        }    
    return activity;
}

createBaseContextForActivity 方法中,呼叫 setOuterContext 方法把 activity 物件賦值給 ContextImpl 物件,

private Context createBaseContextForActivity(ActivityClientRecord r,
        final Activity activity) {
    ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
    appContext.setOuterContext(activity);
    Context baseContext = appContext;
	...
    return baseContext;
}

所以,getOuterContext() 方法獲取的就是 MainActivity 物件,

2.1.3 ContextImpl.registerReceiverInternal() 方法

  • BroadcastReceiver receiver, MyDynamicReceiver 物件
  • int userId, 通過 UserHandle 物件的 getIdentifier() 方法獲得
  • IntentFilter filter, 添加了 action 的 IntentFilter 物件
  • String broadcastPermission, null
  • Handler scheduler, null
  • Context context, MainActivity 物件
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
        IntentFilter filter, String broadcastPermission,
        Handler scheduler, Context context) {
    IIntentReceiver rd = null;
    if (receiver != null) { // 進入此分支
    	// mPackageInfo 是 LoadedApk 物件,是在創建 ContextImpl 的時候賦值的,不為 null
    	// context 是 MainActivity 物件,不為 null
        if (mPackageInfo != null && context != null) { // 進入此分支
            if (scheduler == null) { // 進入此分支,對 scheduler 進行賦值
            	// mMainThread 是 ActivityThread 物件,通過 getHandler() 方法獲取的是它的 H mH 物件,
                scheduler = mMainThread.getHandler();
            }
            // 獲取 InnerReceiver 物件,見[2.1.3.1]
            rd = mPackageInfo.getReceiverDispatcher(
                receiver, context, scheduler,
                mMainThread.getInstrumentation(), true);
        } else { // 不會進入這里
            ...
        }
    }
    try {
    	// 呼叫到這里,通過 AMP 經過 binder 驅動,向 AMS 發起遠程呼叫,
    	// ActivityManagerNative.getDefault() 見[2.1.3.3]
        return ActivityManagerNative.getDefault().registerReceiver(
                mMainThread.getApplicationThread(), mBasePackageName,
                rd, filter, broadcastPermission, userId);
    } catch (RemoteException e) {
        return null;
    }
}

在這里思考一下,為什么我們不直接把 BroadcastReceiver 物件傳遞給 AMS,而是把通過 getReceiverDispatcher() 方法獲取的 InnerReceiver 物件傳遞給 AMS?

這是因為 BroadcastReceiver 僅僅是一個普通的 java 類而已:

public abstract class BroadcastReceiver {
	public abstract void onReceive(Context context, Intent intent);
}

而廣播接收者的注冊程序是一個行程間通信的程序, BroadcastReceiver 作為一個普通的 java 類,不具備跨行程通信的能力,所以 Android 封裝了具有跨行程傳輸能力的 InnerReciver 來完成 BroadcastReceiver 不能完成的作業,

InnerReciver 接收到廣播時,會再呼叫到 BroadcastReceiveronReceive() 方法,也就是說,InnerReciver 起到了一個中轉作用,

2.1.3.1 LoadedApk.getReceiverDispatcher() 方法

  • BroadcastReceiver r, MyDynamicReceiver 物件
  • Context context, MainActivity 物件
  • Handler handler, ActivityThread 里的 mH 物件
  • Instrumentation instrumentation, Instrumentation 物件,是在 ActivityThread 的 handleBindApplication 方法中創建的
  • boolean registered, true
public final class LoadedApk {
	// mReceivers 是一個 ArrayMap 集合,以 Context 為鍵,以 ArrayMap<BroadcastReceiver, ReceiverDispatcher> 為值
	// 其中值又是一個 ArrayMap 集合,以 BroadcastReceiver 為鍵,以 ReceiverDispatcher 為值
	private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
			= new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
	public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
			Context context, Handler handler,
			Instrumentation instrumentation, boolean registered) {
		synchronized (mReceivers) {
			// 獲取一個 ReceiverDispatcher 物件
			LoadedApk.ReceiverDispatcher rd = null;
			ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
			if (registered) { // registered 為 true,進入此分支
				// 根據 context 物件,從 mReceivers 里面取出 map 物件
				map = mReceivers.get(context);
				if (map != null) {
					// 如果 map 不為 null,再根據 r 物件,取出 ReceiverDispatcher rd 物件
					rd = map.get(r);
				}
			}
			if (rd == null) {
				// 創建 ReceiverDispatcher 物件,見[2.1.3.2]
				rd = new ReceiverDispatcher(r, context, handler,
						instrumentation, registered);
				if (registered) { // 進入此分支
					// 把 rd 存到 mReceivers 這個資料結構中
					if (map == null) {
						map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
						mReceivers.put(context, map);
					}
					map.put(r, rd);
				}
			} else {
				rd.validate(context, handler);
			}
			rd.mForgotten = false;
			// 通過 ReceiverDispatcher 物件,獲取 InnerReceiver 物件,
			return rd.getIIntentReceiver();
		}
	}
}

2.1.3.2 new LoadedApk.ReceiverDispatcher() 方法

  • BroadcastReceiver r, MyDynamicReceiver 物件
  • Context context, MainActivity 物件
  • Handler activityThread, ActivityThread 里的 mH 物件
  • Instrumentation instrumentation, Instrumentation 物件,是在 ActivityThread 的 handleBindApplication 方法中創建的
  • boolean registered, true
public final class LoadedApk {
	static final class ReceiverDispatcher {
		final static class InnerReceiver extends IIntentReceiver.Stub {
			final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
			final LoadedApk.ReceiverDispatcher mStrongRef;
			InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
				mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
				mStrongRef = strong ? rd : null;
			}
			public void performReceive(Intent intent, int resultCode, String data,
					Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
				LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
				if (rd != null) {
					rd.performReceive(intent, resultCode, data, extras,
							ordered, sticky, sendingUser);
				} 
				...
			}
		}
		final IIntentReceiver.Stub mIIntentReceiver;
		final BroadcastReceiver mReceiver;
		final Context mContext;
		final Handler mActivityThread;
		final Instrumentation mInstrumentation;
		final boolean mRegistered;
		boolean mForgotten;
		final class Args extends BroadcastReceiver.PendingResult implements Runnable {
			private Intent mCurIntent;
			private final boolean mOrdered;
			public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
					boolean ordered, boolean sticky, int sendingUser) {
				super(resultCode, resultData, resultExtras,
						mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED,
						ordered, sticky, mIIntentReceiver.asBinder(), sendingUser);
				mCurIntent = intent;
				mOrdered = ordered;
			}
			
			public void run() {
				final BroadcastReceiver receiver = mReceiver;
				final boolean ordered = mOrdered;
				
				final IActivityManager mgr = ActivityManagerNative.getDefault();
				final Intent intent = mCurIntent;
				mCurIntent = null;
				
				if (receiver == null || mForgotten) {
					if (mRegistered && ordered) {
						sendFinished(mgr);
					}
					return;
				}
					ClassLoader cl =  mReceiver.getClass().getClassLoader();
					intent.setExtrasClassLoader(cl);
					setExtrasClassLoader(cl);
					receiver.setPendingResult(this);
					receiver.onReceive(mContext, intent);
				
				if (receiver.getPendingResult() != null) {
					finish();
				}
			}
		}
		ReceiverDispatcher(BroadcastReceiver receiver, Context context,
				Handler activityThread, Instrumentation instrumentation,
				boolean registered) {
			mIIntentReceiver = new InnerReceiver(this, !registered);
			mReceiver = receiver;
			mContext = context;
			mActivityThread = activityThread;
			mInstrumentation = instrumentation;
			mRegistered = registered;
		}
		
		IIntentReceiver getIIntentReceiver() {
			return mIIntentReceiver;
		}
		public void performReceive(Intent intent, int resultCode, String data,
				Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
			Args args = new Args(intent, resultCode, data, extras, ordered,
					sticky, sendingUser);
			if (!mActivityThread.post(args)) {
				...
			}
		}
	}
}

IIntentReceiver.aidl 檔案如下:

oneway interface IIntentReceiver {
    void performReceive(in Intent intent, int resultCode, String data,
            in Bundle extras, boolean ordered, boolean sticky, int sendingUser);
}

類結構圖如下:
在這里插入圖片描述
從類結構圖可以看到,

  • LoadedApk.ReceiverDispatcher 分別持有 InnerReceiver 物件 和 BroadcastReceiver 物件,這樣 LoadedApk.ReceiverDispatcher 可以獲取到 BroadcastReceiver 物件;
  • InnerReceiver 通過弱參考持有 LoadedApk.ReceiverDispatcher 物件,所以 InnerReceiver 可以找到 LoadedApk.ReceiverDispatcher 物件;
  • InnerReceiver 可以獲取LoadedApk.ReceiverDispatcher 物件,且LoadedApk.ReceiverDispatcher 可以獲取到 BroadcastReceiver 物件,因此 InnerReceiver 可以通過 LoadedApk.ReceiverDispatcher獲取BroadcastReceiver 物件(這樣我們可以說 LoadedApk.ReceiverDispatcher起到了連接 InnerReceiverBroadcastReceiver的作用);
  • InnerReceiver 是一個繼承于 IIntentReceiver.Stub 的類,所以它是一個 binder 通信的服務端類,

2.1.3.3 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.1.4 ActivityManagerProxy.registerReceiver() 方法

  • IApplicationThread caller, ApplicationThread 物件,作為 binder 服務端,用于 AMS 行程向客戶端行程發起通信
  • String packageName, 包名,即 “com.wzc.chapter_9”
  • IIntentReceiver receiver, InnerReceiver 物件
  • IntentFilter filter, 添加了 action 的 IntentFilter 物件
  • String perm, null
  • int userId, 通過 UserHandle 物件的 getIdentifier() 方法獲得
class ActivityManagerProxy implements IActivityManager
{
	public Intent registerReceiver(IApplicationThread caller, String packageName,
			IIntentReceiver receiver,
			IntentFilter filter, String perm, int userId) throws RemoteException
	{
		Parcel data = Parcel.obtain();
		Parcel reply = Parcel.obtain();
		data.writeInterfaceToken(IActivityManager.descriptor);
		data.writeStrongBinder(caller != null ? caller.asBinder() : null);
		data.writeString(packageName);
		data.writeStrongBinder(receiver != null ? receiver.asBinder() : null);
		filter.writeToParcel(data, 0);
		data.writeString(perm);
		data.writeInt(userId);
		mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0);
		reply.readException();
		Intent intent = null;
		int haveIntent = reply.readInt();
		if (haveIntent != 0) {
			intent = Intent.CREATOR.createFromParcel(reply);
		}
		reply.recycle();
		data.recycle();
		return intent;
	}
}

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

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

2.1.5 ActivityManagerNative.onTransact() 方法

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
	switch (code) {
		...
		case REGISTER_RECEIVER_TRANSACTION:
		{
			data.enforceInterface(IActivityManager.descriptor);
			IBinder b = data.readStrongBinder();
			// 這里獲取的是 ApplicationThreadProxy 物件,是 binder 客戶端物件,
			IApplicationThread app =
				b != null ? ApplicationThreadNative.asInterface(b) : null;
			String packageName = data.readString();
			b = data.readStrongBinder();
			// 這里獲取的是 IIntentReceiver.Stub.Proxy 物件,是 binder 客戶端物件,
			IIntentReceiver rec
				= b != null ? IIntentReceiver.Stub.asInterface(b) : null;
			IntentFilter filter = IntentFilter.CREATOR.createFromParcel(data);
			String perm = data.readString();
			int userId = data.readInt();
			// 呼叫到這里,進入 AMS,見[2.1.6]
			Intent intent = registerReceiver(app, packageName, rec, filter, perm, userId);
			reply.writeNoException();
			if (intent != null) {
				reply.writeInt(1);
				intent.writeToParcel(reply, 0);
			} else {
				reply.writeInt(0);
			}
			return true;
		}
	}
}

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

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

2.1.6 ActivityManagerService.registerReceiver() 方法

  • IApplicationThread caller, ApplicationThreadProxy物件,作為 binder 客戶端
  • String packageName, 包名,即 “com.wzc.chapter_9”
  • IIntentReceiver receiver, InnerReceiver.Stub.Proxy物件
  • IntentFilter filter, 添加了 action 的 IntentFilter 物件
  • String perm, null
  • int userId, 通過 UserHandle 物件的 getIdentifier() 方法獲得
// mRegisteredReceivers 用于記錄所有已經被注冊給廣播的 IIntentReceiver
// 鍵為 InnerReceiver.Stub.Proxy 對應的 BinderProxy 物件
// 值為 ReceiverList 物件
final HashMap<IBinder, ReceiverList> mRegisteredReceivers =
        new HashMap<IBinder, ReceiverList>();
final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
        = new IntentResolver<BroadcastFilter, BroadcastFilter>() {}
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
        IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
    int callingUid;
    int callingPid;
    synchronized(this) {
        ProcessRecord callerApp = null;
        if (caller != null) { // 進入此分支
			// 獲取發起方的行程記錄物件
            callerApp = getRecordForAppLocked(caller);
            if (callerApp == null) { // 不為 null
                throw new SecurityException();
            }
			// 本次分析 callerApp.pkgList.containsKey(callerPackage) 為 true,所以不會進入此分支,
            if (callerApp.info.uid != Process.SYSTEM_UID &&
                    !callerApp.pkgList.containsKey(callerPackage) &&
                    !"android".equals(callerPackage)) {
                throw new SecurityException();
            }
            callingUid = callerApp.info.uid;
            callingPid = callerApp.pid;
        } else { // 不會進入此分支
            ...
        }
        userId = this.handleIncomingUser(callingPid, callingUid, userId,
                true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
        List allSticky = null;
        // Look for any matching sticky broadcasts...
        Iterator actions = filter.actionsIterator();
        if (actions != null) { // 進入此分支
            while (actions.hasNext()) {
                String action = (String)actions.next();
				// 獲取 sticky intent 的串列,本次分析沒有 sticky intent,所以 allSticky 為 null,
                allSticky = getStickiesLocked(action, filter, allSticky,
                        UserHandle.USER_ALL);
                allSticky = getStickiesLocked(action, filter, allSticky,
                        UserHandle.getUserId(callingUid));
            }
        } else { 
            ...
        }
        Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null; // 回傳 null
        if (receiver == null) { // 不為 null
            return sticky;
        }
		// 從 mRegisteredReceivers 獲取對應 InnerReceiver.Stub.Proxy 對應的 BinderProxy 物件 的 ReceiverList 集合
		// 因為我們是首次注冊,所以這里獲取到的 rl 是 null
        ReceiverList rl
            = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
        if (rl == null) { // 進入此分支
			// 創建一個 ReceiverList 物件,
            rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                    userId, receiver);
            if (rl.app != null) { // r1.app 就是 callerApp,不為 null,進入此分支
                rl.app.receivers.add(rl); // 把 rl 添加到 ProcessRecord 的 receivers 串列里面保存,
            } else {
                ...
            }
			// 把新建的 rl 添加到 mRegisteredReceivers 集合里面保存,
            mRegisteredReceivers.put(receiver.asBinder(), rl);
        } 
        ...
		// 創建 BroadcastFilter 物件,BroadcastFilter 是 IntentFilter 的子類
        BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                permission, callingUid, userId);
		// 保存在 rl 里面
        rl.add(bf);
        // BroadcastFilter 物件添加到 mReceiverResolver 里面,
        mReceiverResolver.addFilter(bf);
        if (allSticky != null) { // allSticky 為 null,不會進入此分支
            ...
        }
        return sticky;
    }
}

該方法的作用:

  1. 創建 ReceiverList 物件,它封裝了 InnerReceiver.Stub.Proxy物件,并保存在 mRegisteredReceivers 集合里面;
  2. 創建 BroadcastFilter 物件,封裝了 IntentFilter 物件, ReceiverList 物件,并保存在 mReceiverResolverReceiverList 集合物件里面,

類結構圖如下:

在這里插入圖片描述

這樣,廣播接收者的注冊就完成了,

2.1.6.1 IntentResolver.addFilter()方法

  • F f, BroadcastFilter 物件
public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
	// 以 BroadcastFilter 為元素的串列
	private final HashSet<F> mFilters = new HashSet<F>();
	// 以 action 為鍵,以 BroadcastFilter[] 為值的集合
	private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();
	
	public void addFilter(F f) {
		mFilters.add(f);
		int numS = register_intent_filter(f, f.schemesIterator(),
				mSchemeToFilter, "      Scheme: "); // 沒有設定 schema,所以回傳 0
		int numT = register_mime_types(f, "      Type: "); // 沒有設定 type,所以回傳 0
		if (numS == 0 && numT == 0) { // 進入此分支,把 F f 存放到 mActionToFilter 集合里面,
			register_intent_filter(f, f.actionsIterator(),
					mActionToFilter, "      Action: ");
		}
		...
	}

2.2 發送普通廣播

2.2.1 ContextWrapper.sendBroadcast() 方法

  • Intent intent, 添加了 action 的 Intent 物件
@Override
public void sendBroadcast(Intent intent) {
    mBase.sendBroadcast(intent);
}

同 2.1.1, mBase 實際上是一個 ContextImpl 型別的物件,

2.2.2 ContextImpl.sendBroadcast() 方法

  • Intent intent, 添加了 action 的 Intent 物件
@Override
public void sendBroadcast(Intent intent) {
    String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
    try {
        intent.prepareToLeaveProcess();
		// 呼叫到這里,見[2.2.3]
        ActivityManagerNative.getDefault().broadcastIntent(
            mMainThread.getApplicationThread(), intent, resolvedType, null,
            Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false,
            getUserId());
    } catch (RemoteException e) {
    }
}

由 2.1.3.3,ActivityManagerNative.getDefault() 獲取的是一個ActivityManagerProxy 物件,

2.2.3 ActivityManagerProxy.broadcastIntent() 方法

  • IApplicationThread caller, ApplicationThread 物件,作為 binder 服務端,用于 AMS 行程向客戶端行程發起通信
  • Intent intent, 添加了 action 的 Intent 物件
  • String resolvedType, 由 intent.resolveTypeIfNeeded 得到
  • IIntentReceiver resultTo, null
  • int resultCode, Activity.RESULT_OK,值為 -1
  • String resultData, null
  • Bundle map, null
  • String requiredPermission, null
  • int appOp, AppOpsManager.OP_NONE,值為 -1
  • boolean serialized, false,表示不是有序廣播
  • boolean sticky, false,表示不是粘性廣播
  • int userId, mUser.getIdentifier() 得到
class ActivityManagerProxy implements IActivityManager
{
	public int broadcastIntent(IApplicationThread caller,
			Intent intent, String resolvedType,  IIntentReceiver resultTo,
			int resultCode, String resultData, Bundle map,
			String requiredPermission, int appOp, boolean serialized,
			boolean sticky, int userId) throws RemoteException
	{
		Parcel data = Parcel.obtain();
		Parcel reply = Parcel.obtain();
		data.writeInterfaceToken(IActivityManager.descriptor);
		data.writeStrongBinder(caller != null ? caller.asBinder() : null);
		intent.writeToParcel(data, 0);
		data.writeString(resolvedType);
		data.writeStrongBinder(resultTo != null ? resultTo.asBinder() : null);
		data.writeInt(resultCode);
		data.writeString(resultData);
		data.writeBundle(map);
		data.writeString(requiredPermission);
		data.writeInt(appOp);
		data.writeInt(serialized ? 1 : 0);
		data.writeInt(sticky ? 1 : 0);
		data.writeInt(userId);
		// 呼叫到這里,見[2.2.4]
		mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0);
		reply.readException();
		int res = reply.readInt();
		reply.recycle();
		data.recycle();
		return res;
	}
}

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

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

2.2.4 ActivityManagerNative.onTransact() 方法

@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
	switch (code) {
		...
		case BROADCAST_INTENT_TRANSACTION:
		{
			data.enforceInterface(IActivityManager.descriptor);
			IBinder b = data.readStrongBinder();
			// 這里獲取的是 ApplicationThreadProxy 物件,是 binder 客戶端物件,
			IApplicationThread app =
				b != null ? ApplicationThreadNative.asInterface(b) : null;
			Intent intent = Intent.CREATOR.createFromParcel(data);
			String resolvedType = data.readString();
			b = data.readStrongBinder();
			// 這里 resultTo 為 null,因為 b 為 null,
			IIntentReceiver resultTo =
				b != null ? IIntentReceiver.Stub.asInterface(b) : null;
			int resultCode = data.readInt();
			String resultData = data.readString();
			Bundle resultExtras = data.readBundle();
			String perm = data.readString();
			int appOp = data.readInt();
			boolean serialized = data.readInt() != 0;
			boolean sticky = data.readInt() != 0;
			int userId = data.readInt();
			// 呼叫到這里,見[2.2.5]
			int res = broadcastIntent(app, intent, resolvedType, resultTo,
					resultCode, resultData, resultExtras, perm, appOp,
					serialized, sticky, userId);
			reply.writeNoException();
			reply.writeInt(res);
			return true;
		}
	}
}

這里已經在 system_server 行程了,

2.2.5 ActivityManagerService.broadcastIntent() 方法

  • IApplicationThread caller, ApplicationThreadProxy 物件,作為 binder 客戶端
  • Intent intent, 添加了 action 的 Intent 物件
  • String resolvedType, 由 intent.resolveTypeIfNeeded 得到
  • IIntentReceiver resultTo, null
  • int resultCode, Activity.RESULT_OK,值為 -1
  • String resultData, null
  • Bundle map, null
  • String requiredPermission, null
  • int appOp, AppOpsManager.OP_NONE,值為 -1
  • boolean serialized, false,表示不是有序廣播
  • boolean sticky, false,表示不是粘性廣播
  • int userId, mUser.getIdentifier() 得到
public final int broadcastIntent(IApplicationThread caller,
        Intent intent, String resolvedType, IIntentReceiver resultTo,
        int resultCode, String resultData, Bundle map,
        String requiredPermission, int appOp, boolean serialized, boolean sticky, int userId) {
    synchronized(this) {
        intent = verifyBroadcastLocked(intent);
        // 獲取發起方的行程記錄物件
        final ProcessRecord callerApp = getRecordForAppLocked(caller);
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        int res = broadcastIntentLocked(callerApp,
                callerApp != null ? callerApp.info.packageName : null,
                intent, resolvedType, resultTo,
                resultCode, resultData, map, requiredPermission, appOp, serialized, sticky,
                callingPid, callingUid, userId);
        Binder.restoreCallingIdentity(origId);
        return res;
    }
}

2.2.6 ActivityManagerService.broadcastIntentLocked() 方法

  • ProcessRecord callerApp, 發起方的行程記錄物件
  • String callerPackage, 發起方的包名
  • Intent intent, 添加了 action 的 Intent 物件
  • String resolvedType, 由 intent.resolveTypeIfNeeded 得到,為 null
  • IIntentReceiver resultTo, null
  • int resultCode, Activity.RESULT_OK,值為 -1
  • String resultData, null
  • Bundle map, null
  • String requiredPermission, null
  • int appOp, AppOpsManager.OP_NONE,值為 -1
  • boolean ordered, false,表示不是有序廣播
  • boolean sticky, false,表示不是粘性廣播
  • int callingPid,
  • int callingUid,
  • int userId, mUser.getIdentifier() 得到,值為 0
// 記錄處于開啟狀態的 user
final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>();

final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
        = new IntentResolver<BroadcastFilter, BroadcastFilter>() {...};
private final int broadcastIntentLocked(ProcessRecord callerApp,
        String callerPackage, Intent intent, String resolvedType,
        IIntentReceiver resultTo, int resultCode, String resultData,
        Bundle map, String requiredPermission, int appOp,
        boolean ordered, boolean sticky, int callingPid, int callingUid,
        int userId) {
	// ====> 1,設定廣播 flag
    intent = new Intent(intent);
	// Intent.FLAG_EXCLUDE_STOPPED_PACKAGES 用來控制廣播不對停止狀態的應用起作用
    intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
    userId = handleIncomingUser(callingPid, callingUid, userId,
            true, ALLOW_NON_FULL, "broadcast", callerPackage);
	// 確保正在接收廣播的用戶已經開啟了;否則,跳過本次廣播發送,
	// 本次分析不會進入此分支,
    if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) {
        if (callingUid != Process.SYSTEM_UID || (intent.getFlags()
                & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
            return ActivityManager.BROADCAST_SUCCESS;
        }
    }
	// ====> 2,廣播權限的驗證
	// 阻止非系統代碼去發送受保護的廣播,
    int callingAppId = UserHandle.getAppId(callingUid);
    if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID
        || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID
        || callingAppId == Process.NFC_UID || callingUid == 0) {
        // Always okay.
    } else if (callerApp == null || !callerApp.persistent) { // callerApp.persistent 為 false,進入此分支
        try {
            if (AppGlobals.getPackageManager().isProtectedBroadcast(
                    intent.getAction())) { // 判斷 action 是不是受保護的,本次不會進入此分支
                throw new SecurityException(msg);
            } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) { // 不會進入此分支
				...
            }
        } catch (RemoteException e) {
            return ActivityManager.BROADCAST_SUCCESS;
        }
    }
	// ====> 3,處理系統相關廣播
	// 處理特殊廣播,如包移除廣播
    final boolean uidRemoved = Intent.ACTION_UID_REMOVED.equals(
            intent.getAction());
    if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
            || Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
            || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())
            || Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())
            || uidRemoved) { // 本次分析沒有移除包,不會進入此分支
        ...
	// 本次分析沒有安裝包,不會進入此分支
    } else if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { 
        ...
    }
    // 本次分析沒有時區改變,不會進入此分支
    if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
        mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
    }
	// 如果是時間改變廣播,通知所有運行的行程,本次分析不會進入此分支,
    if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
        ...
    }
    if (Intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) {
        mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
    }
    if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) {
        ProxyInfo proxy = intent.getParcelableExtra(Proxy.EXTRA_PROXY_INFO);
        mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
    }
	// ====> 4,是 sticky 廣播,就添加 sticky 廣播
    if (sticky) { // sticky 為 false,不會進入此分支
        ...
    }
    int[] users;
    if (userId == UserHandle.USER_ALL) {
        users = mStartedUserArray;
    } else { // 我們不考慮多用戶,進入此分支
        users = new int[] {userId};
    }
	// ====> 5,查詢 receivers 和 registeredReceivers
    List receivers = null;
    List<BroadcastFilter> registeredReceivers = null;
	// Intent.FLAG_RECEIVER_REGISTERED_ONLY 表示僅僅動態注冊的廣播接收者才會被呼叫
    if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
             == 0) { // 成立,進入此分支
        // 收集靜態注冊的廣播接收者,回傳的是 List<ResolveInfo>,見[2.2.6.1]
        receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
    }
    if (intent.getComponent() == null) { // 進入此分支
        if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
            ...
        } else { // 進入此分支
			// 收集動態注冊的廣播接收者,見[2.2.6.2]
            registeredReceivers = mReceiverResolver.queryIntent(intent,
                    resolvedType, false, userId);
        }
    }
    final boolean replacePending = // 沒有設定此 flag,所以回傳 false,
            (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0; 
    
    int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
  	// ====> 6. 處理并行廣播
    if (!ordered && NR > 0) { // 進入此分支
		// 獲取到的是 BroadcastQueue mBgBroadcastQueue,它是在 AMS 構造方法中初始化的,
        final BroadcastQueue queue = broadcastQueueForIntent(intent);
        // 創建 BroadcastRecord 物件,見[2.2.6.4]
        BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                callerPackage, callingPid, callingUid, resolvedType, requiredPermission,
                appOp, registeredReceivers, resultTo, resultCode, resultData, map,
                ordered, sticky, false, userId);
        final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); // false
        if (!replaced) { // 進入此分支
        	// 將 BroadcastRecord 物件添加到并行廣播佇列,見[2.2.6.5 ]
            queue.enqueueParallelBroadcastLocked(r);
            // 處理廣播,見[2.3.1]
            queue.scheduleBroadcastsLocked();
        }
        registeredReceivers = null;
        NR = 0;
    }
    // Merge into one list.
    int ir = 0;
    if (receivers != null) { // receivers 為 null,不會進入此分支
        ...
    }
    while (ir < NR) { // ir = 0,NR = 0,不會進入此回圈
        ...
    }
    if ((receivers != null && receivers.size() > 0)
            || resultTo != null) { // receivers 為 null,且 resultTo 為 null,不會進入此分支
        ...
    }
    return ActivityManager.BROADCAST_SUCCESS;
}

該方法的作用:

  1. 設定廣播 flag;
  2. 廣播權限的驗證;
  3. 處理系統相關廣播;
  4. 是 sticky 廣播,就添加 sticky 廣播;
  5. 查詢 receivers 和 registeredReceivers;
    在 2.1.6 中,通過 mReceiverResolver.addFilter(bf);添加了 BroadcastFiilter 物件,這里通過 mReceiverResolver.queryIntent 獲取之前添加的 BroadcastFiilter 物件串列,賦值給 registeredReceivers
  6. 處理并行廣播,

需要再次說明的是,我們本次分析的場景是發送普通廣播,所以不用關心有序廣播和粘性廣播的情況,我們有意限定了分析的范圍,這樣才不會迷失在原始碼里面,

2.2.6.1 ActivityManagerService.collectReceiverComponents() 方法

  • Intent intent, 添加了 action 的 Intent 物件
  • String resolvedType, 由 intent.resolveTypeIfNeeded 得到,為 null
  • int callingUid,
  • int[] users, 只包含了當前的用戶id的陣列,這里的用戶id的值是0
private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
        int callingUid, int[] users) {
    List<ResolveInfo> receivers = null;
    try {
        HashSet<ComponentName> singleUserReceivers = null;
        boolean scannedFirstReceivers = false;
        for (int user : users) { // users 里只有一個元素,所以只會回圈一次,
            ...
			// 決議清單檔案中靜態注冊的 receiver,回傳可能為 null,
            List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
                    .queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);
            if (user != 0 && newReceivers != null) { // user 為 0,不會進入此分支
                ...
            }
            if (newReceivers != null && newReceivers.size() == 0) { // 不會進入此分支
                newReceivers = null;
            }
            if (receivers == null) { // 進入此分支
                receivers = newReceivers;
            } else if (newReceivers != null) { // 走第一個 if 了,不會再進入 else if
                ...
            }
        }
    } catch (RemoteException ex) {
    }
    return receivers;
}

該方法的作用:收集靜態注冊的廣播接收者,回傳的是 List<ResolveInfo>

本次分析沒有靜態注冊廣播接收者,所以這個方法會回傳 null

2.2.6.2 IntentResolver.queryIntent() 方法

  • Intent intent, 添加了 action 的 Intent 物件
  • String resolvedType, null
  • boolean defaultOnly, false
  • int userId, 0
public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
	// 以 action 為鍵,以 BroadcastFilter[] 為值的集合
	private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();
	
	public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
			int userId) {
		String scheme = intent.getScheme(); // 回傳 null
		ArrayList<R> finalList = new ArrayList<R>();
		F[] firstTypeCut = null;
		F[] secondTypeCut = null;
		F[] thirdTypeCut = null;
		F[] schemeCut = null;
		if (resolvedType != null) { // resolvedType 為 null,不會進入此分支
			...
		}
		if (scheme != null) { // scheme 為 null,不會進入此分支
			...
		}
		if (resolvedType == null && scheme == null && intent.getAction() != null) {
			firstTypeCut = mActionToFilter.get(intent.getAction());
		}
		FastImmutableArraySet<String> categories = getFastIntentCategories(intent); // 回傳 null
		if (firstTypeCut != null) {
		    // 見[2.2.6.3]
			buildResolveList(intent, categories, debug, defaultOnly,
					resolvedType, scheme, firstTypeCut, finalList, userId);
		}
		if (secondTypeCut != null) { // 進不去
			... 
		}
		if (thirdTypeCut != null) { // 進不去
			...
		}
		if (schemeCut != null) { // 進不去
			...
		}
		sortResults(finalList);
		return finalList;
	}
}

該方法的作用:從 mActionToFilter 獲取 BroadcastFiilter 物件串列,

2.2.6.3 IntentResolver.buildResolveList() 方法

  • Intent intent, 添加了 action 的 Intent 物件
  • FastImmutableArraySet categories, null
  • boolean debug, false
  • boolean defaultOnly, false
  • String resolvedType, null
  • String scheme, null
  • F[] src, BroadcastFilter[] 物件
  • List dest, 容器引數 List 物件
  • int userId, 0
public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
	// 以 action 為鍵,以 BroadcastFilter[] 為值的集合
	private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();
	
	private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
			boolean debug, boolean defaultOnly,
			String resolvedType, String scheme, F[] src, List<R> dest, int userId) {
		final String action = intent.getAction(); // "com.wzc.receiver.LAUNCH_DYNAMIC"
		final Uri data = intent.getData(); // null
		final String packageName = intent.getPackage(); // null
		final boolean excludingStopped = intent.isExcludingStopped(); // true
		final Printer logPrinter;
		final PrintWriter logPrintWriter;
		final int N = src != null ? src.length : 0;
		boolean hasNonDefaults = false;
		int i;
		F filter;
		for (i=0; i<N && (filter=src[i]) != null; i++) {
			int match;
			if (excludingStopped && isFilterStopped(filter, userId)) {
				continue;
			}
			if (packageName != null && !isPackageForFilter(packageName, filter)) {
				continue;
			}
			
			match = filter.match(action, resolvedType, scheme, data, categories, TAG);
			if (match >= 0) {
				if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
					final R oneResult = newResult(filter, match, userId);
					if (oneResult != null) {
						dest.add(oneResult);
					}
				} else {
					hasNonDefaults = true;
				}
			} else {
				...
			}
		}
	}
}

該方法的作用是:對 BroadcastFilter 物件陣列進行一些過濾,通過容器引數回傳符合條件的 BroadcastFilter 物件,

2.2.6.4 new BroadcastRecord() 方法

final class BroadcastRecord extends Binder {
    final Intent intent;    // 產生廣播的原始 Intent
    final ComponentName targetComp; // 設定在 Intent 上的原始的組件名物件
    final ProcessRecord callerApp; // 發送廣播的行程
    final String callerPackage; // 發送方的包名
    final int callingPid;   // 發起方的pid
    final int callingUid;   // 發起方的uid
    final boolean ordered;  // 是否是有序廣播
    final boolean sticky;   // 是否是粘性廣播
    final boolean initialSticky; 
    final int userId;       
    final String resolvedType; // mime type
    final String requiredPermission; // 廣播權限
    final int appOp;        
    final List receivers;   // 廣播接收器,包括動態注冊(BroadcastFilter)和靜態注冊(ResolveInfo)
    IIntentReceiver resultTo; // null
    long dispatchTime;      // 廣播分發時間點
    long dispatchClockTime; // the clock time the dispatch started
    long receiverTime;      // 當前 receiver 開始處理時間點
    long finishTime;        // 廣播處理完成時間點.
    int resultCode;         // current result code value.
    String resultData;      // current result data value.
    Bundle resultExtras;    // current result extra data values.
    boolean resultAbort;    // current result abortBroadcast value.
    int nextReceiver;       // next receiver to be executed.
    IBinder receiver;       // who is currently running, null if none.
    int state;
    int anrCount;           // has this broadcast record hit any ANRs?
    BroadcastQueue queue;   // 處理廣播的佇列

    static final int IDLE = 0;
    static final int APP_RECEIVE = 1;
    static final int CALL_IN_RECEIVE = 2;
    static final int CALL_DONE_RECEIVE = 3;
    static final int WAITING_SERVICES = 4;
    BroadcastFilter curFilter;
    ProcessRecord curApp;       // hosting application of current receiver.
    ComponentName curComponent; // the receiver class that is currently running.
    ActivityInfo curReceiver;   // info about the receiver that is currently running.

    BroadcastRecord(BroadcastQueue _queue,
            Intent _intent, ProcessRecord _callerApp, String _callerPackage,
            int _callingPid, int _callingUid, String _resolvedType, String _requiredPermission,
            int _appOp, List _receivers, IIntentReceiver _resultTo, int _resultCode,
            String _resultData, Bundle _resultExtras, boolean _serialized,
            boolean _sticky, boolean _initialSticky,
            int _userId) {
        queue = _queue;
        intent = _intent;
        targetComp = _intent.getComponent();
        callerApp = _callerApp;
        callerPackage = _callerPackage;
        callingPid = _callingPid;
        callingUid = _callingUid;
        resolvedType = _resolvedType;
        requiredPermission = _requiredPermission;
        appOp = _appOp;
        receivers = _receivers;
        resultTo = _resultTo;
        resultCode = _resultCode;
        resultData = _resultData;
        resultExtras = _resultExtras;
        ordered = _serialized;
        sticky = _sticky;
        initialSticky = _initialSticky;
        userId = _userId;
        nextReceiver = 0;
        state = IDLE;
    }
}

可以看到,BroadcastRecord 是繼承于 Binder 類的,

2.2.6.5 BroadcastQueue.enqueueParallelBroadcastLocked() 方法

public final class BroadcastQueue {
	final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<BroadcastRecord>();
	public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
		mParallelBroadcasts.add(r);
	}
}

該方法的作用:把 BroadcastRecord r 物件添加到 mParallelBroadcasts 串列而已,

2.3 接收普通廣播

2.3.1 BroadcastQueue.scheduleBroadcastsLocked() 方法

public final class BroadcastQueue {
	boolean mBroadcastsScheduled = false;
	final BroadcastHandler mHandler;
	
	BroadcastQueue(ActivityManagerService service, Handler handler,
			String name, long timeoutPeriod, boolean allowDelayBehindServices) {
		mService = service;
		mHandler = new BroadcastHandler(handler.getLooper());
		...
	}
	
	public void scheduleBroadcastsLocked() {
		if (mBroadcastsScheduled) {
			return;
		}
		mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
		mBroadcastsScheduled = true;
	}
}

該方法的作用是:通過 mHandler 發送一個 whatBROADCAST_INTENT_MSGobjectBroadcastQueue 物件的訊息,

mHandler 是在 BroadcastQueue 的構造方法中初始化的:mHandler = new BroadcastHandler(handler.getLooper()),需要特別說明的是,這里的 hanlder.getLooper() 是一個名字 "ActivityManager" 為子執行緒的 Looper 物件,因為這個 Looper 物件來自于 ActivityManagerServicemHandlerThread.getLooper()

public final class ActivityManagerService extends ActivityManagerNative {
	static final String TAG = "ActivityManager";
	final ServiceThread mHandlerThread;
	final MainHandler mHandler;
	BroadcastQueue mFgBroadcastQueue;
	BroadcastQueue mBgBroadcastQueue;
	public ActivityManagerService(Context systemContext) {
		mContext = systemContext;
		mSystemThread = ActivityThread.currentActivityThread();
		mHandlerThread = new ServiceThread(TAG,
				android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
		mHandlerThread.start();
		mHandler = new MainHandler(mHandlerThread.getLooper());
		mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
				"foreground", BROADCAST_FG_TIMEOUT, false);
		mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
				"background", BROADCAST_BG_TIMEOUT, true);
	}
}

所以,發送訊息是在 system_server 行程的主執行緒,接收訊息的地方是在 system_server 行程的子執行緒里面了,

2.3.2 BroadcastHandler.handleMessage() 方法

private final class BroadcastHandler extends Handler {
    public BroadcastHandler(Looper looper) {
        super(looper, null, true);
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case BROADCAST_INTENT_MSG: {
                // 見[2.3.3]
                processNextBroadcast(true);
            } break;
            case BROADCAST_TIMEOUT_MSG: {
                synchronized (mService) {
                    broadcastTimeoutLocked(true);
                }
            } break;
        }
    }
};

2.3.3 BroadcastQueue.processNextBroadcast() 方法

  • boolean fromMsg, true
final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<BroadcastRecord>();
final void processNextBroadcast(boolean fromMsg) {
    synchronized(mService) {
        BroadcastRecord r;
        if (fromMsg) {
			// 重置 mBroadcastsScheduled 標記,
            mBroadcastsScheduled = false;
        }
		// 首先,馬上處理普通廣播
        while (mParallelBroadcasts.size() > 0) { // 存在并行廣播
            r = mParallelBroadcasts.remove(0); 
            r.dispatchTime = SystemClock.uptimeMillis();
            r.dispatchClockTime = System.currentTimeMillis();
            final int N = r.receivers.size();
            for (int i=0; i<N; i++) {
                Object target = r.receivers.get(i);
				// 呼叫到這里,見[2.3.4]
                deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
            }
            addBroadcastToHistoryLocked(r);
        }
		// 當正在等待一個行程被拉起來處理下一個廣播,mPendingBroadcast 就不為 null,
		// 本次分析不涉及跨行程,所以 mPendingBroadcast 為 null,
        if (mPendingBroadcast != null) { // 本次分析不進入此分支
            ...
        }
        boolean looped = false;
        
        do {
            if (mOrderedBroadcasts.size() == 0) { // 本次分析沒有有序廣播,所以進入此分支,
                mService.scheduleAppGcsLocked();
                if (looped) {
                    mService.updateOomAdjLocked();
                }
                return; // 結束本方法了,
            }
			...
        } while (r == null);
		// ... 省略與本次分析無關的代碼,
    }
}

該方法的作用:取出廣播佇列中的元素,傳遞給注冊的廣播接收者,

2.3.4 BroadcastQueue.deliverToRegisteredReceiverLocked() 方法

  • BroadcastRecord r, BroadcastRecord 物件
  • BroadcastFilter filter, BroadcastFilter 物件,存盤了廣播接收者的資訊
  • boolean ordered, false,表示不是有序廣播
private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,
        BroadcastFilter filter, boolean ordered) {
    boolean skip = false;
    if (filter.requiredPermission != null) { // 不進入此分支
        ...
    }
    if (!skip && r.requiredPermission != null) { // 不進入此分支
        ...
    }
    if (r.appOp != AppOpsManager.OP_NONE) { // 不進入此分支
        ...
    }
    if (!skip) {
        skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                r.callingPid, r.resolvedType, filter.receiverList.uid);
    }
    if (filter.receiverList.app == null || filter.receiverList.app.crashing) {
        skip = true;
    }
    if (!skip) {
        ...
        try {
			// 呼叫到這里,見[2.3.5] 
            performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                new Intent(r.intent), r.resultCode, r.resultData,
                r.resultExtras, r.ordered, r.initialSticky, r.userId);
            
        } catch (RemoteException e) {
            
        }
    }
}

該方法的作用:進行必要的權限驗證,然后執行廣播接收程序,

這里為了弄清楚 performReceiveLocked 方法的引數,我們再次查看一下類結構圖:

在這里插入圖片描述

2.3.5 BroadcastQueue.performReceiveLocked() 方法

  • ProcessRecord app, filter.receiverList.app = 發起方的行程記錄物件
  • IIntentReceiver receiver, filter.receiverList.receiver = InnerReceiver.Stub.Proxy物件
  • Intent intent, new Intent(r.intent) = 添加了 action 的 Intent 物件
  • int resultCode, r.resultCode = Activity.RESULT_OK,值為 -1
  • String data, r.resultData = null
  • Bundle extras, r.resultExtras = null
  • boolean ordered, r.ordered = false
  • boolean sticky, r.initialSticky = false
  • int sendingUser, r.userId = 0
private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
        Intent intent, int resultCode, String data, Bundle extras,
        boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
    if (app != null) {
		// app.thread 是 IApplicationThread 物件,實際上是 ApplicationThreadProxy 物件,是 binder 客戶端物件,
        if (app.thread != null) {
            // 開始向 IApplicationThread binder 服務端物件發起請求服務,見[2.3.6]
            app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                    data, extras, ordered, sticky, sendingUser, app.repProcState);
        } else {
            ...
        }
    } else {
        ...
    }
}

2.3.6 ApplicationThreadProxy.scheduleRegisteredReceiver() 方法

class ApplicationThreadProxy implements IApplicationThread {
	public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
			int resultCode, String dataStr, Bundle extras, boolean ordered,
			boolean sticky, int sendingUser, int processState) throws RemoteException {
		Parcel data = Parcel.obtain();
		data.writeInterfaceToken(IApplicationThread.descriptor);
		data.writeStrongBinder(receiver.asBinder());
		intent.writeToParcel(data, 0);
		data.writeInt(resultCode);
		data.writeString(dataStr);
		data.writeBundle(extras);
		data.writeInt(ordered ? 1 : 0);
		data.writeInt(sticky ? 1 : 0);
		data.writeInt(sendingUser);
		data.writeInt(processState);
		mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null,
				IBinder.FLAG_ONEWAY);
		data.recycle();
	}
}

這個方法是運行在 system_server 行程的,

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

2.3.7 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 SCHEDULE_REGISTERED_RECEIVER_TRANSACTION: {
			data.enforceInterface(IApplicationThread.descriptor);
            // 這里獲取的是 IIntentReceiver.Stub.Proxy 物件,是 binder 客戶端物件
			IIntentReceiver receiver = IIntentReceiver.Stub.asInterface(
					data.readStrongBinder());
			Intent intent = Intent.CREATOR.createFromParcel(data);
			int resultCode = data.readInt();
			String dataStr = data.readString();
			Bundle extras = data.readBundle();
			boolean ordered = data.readInt() != 0;
			boolean sticky = data.readInt() != 0;
			int sendingUser = data.readInt();
			int processState = data.readInt();
			scheduleRegisteredReceiver(receiver, intent,
					resultCode, dataStr, extras, ordered, sticky, sendingUser, processState);
			return true;
		}	
	}				
}

ApplicationThreadNative 是一個抽象類,scheduleRegisteredReceiver 是它的一個抽象方法,所以要尋找 scheduleRegisteredReceiver 的實作,實作實在 ApplicationThread 類里面,

ApplicationThreadActivityThread 類的私有內部類,

2.3.8 ApplicationThread.scheduleRegisteredReceiver() 方法

  • IIntentReceiver receiver, IIntentReceiver.Stub.Proxy 物件,是 binder 客戶端物件
  • Intent intent, 添加了 action 的 Intent 物件
  • int resultCode, Activity.RESULT_OK,值為 -1
  • String dataStr, null
  • Bundle extras, null
  • boolean ordered, false
  • boolean sticky, false
  • int sendingUser, 0
  • int processState
public final class ActivityThread {
	private class ApplicationThread extends ApplicationThreadNative {
		public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
				int resultCode, String dataStr, Bundle extras, boolean ordered,
				boolean sticky, int sendingUser, int processState) throws RemoteException {
			receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
					sticky, sendingUser);
		}		
	}
}

這個方法是運行在客戶端行程的 binder 執行緒池里面,

receiverIIntentReceiver.Stub.Proxy 物件,是 binder 客戶端物件,

呼叫 receiver.performReceive 方法,會經過 binder 驅動,呼叫到發起方行程的 IIntentReceiver.Stub 物件,即 InnerReceiver物件的 performReceive 方法,可以查看注冊程序的 2.1.3 部分,并且在 2.1.3.2 部分繪制了 InnerReceiverBroadcastReceiver以及ReceiverDispatcher的類結構圖,

2.3.9 InnerReceiver.performReceive() 方法

  • Intent intent, 添加了 action 的 Intent 物件
  • int resultCode, Activity.RESULT_OK,值為 -1
  • String data, null
  • Bundle extras, null
  • boolean ordered, false
  • boolean sticky, false
  • int sendingUser, 0
final static class InnerReceiver extends IIntentReceiver.Stub {
    final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
    final LoadedApk.ReceiverDispatcher mStrongRef;
    InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
        mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
        mStrongRef = strong ? rd : null;
    }
    public void performReceive(Intent intent, int resultCode, String data,
            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
        LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
        if (rd != null) { 
            // 呼叫到這里,見[2.3.10]
            rd.performReceive(intent, resultCode, data, extras,
                    ordered, sticky, sendingUser);
        } else {
           ...
        }
    }
}

2.3.10 ReceiverDispatcher.performReceive() 方法

  • Intent intent, 添加了 action 的 Intent 物件
  • int resultCode, Activity.RESULT_OK,值為 -1
  • String data, null
  • Bundle extras, null
  • boolean ordered, false
  • boolean sticky, false
  • int sendingUser, 0
public final class LoadedApk {
	static final class ReceiverDispatcher {
		public void performReceive(Intent intent, int resultCode, String data,
				Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
            // 把一系列引數封裝在 Args 物件里面,是一個 Runnable 物件,
			Args args = new Args(intent, resultCode, data, extras, ordered,
					sticky, sendingUser);
            // mActivityThread 就是 ActivityThread 類的 mH 物件,
			if (!mActivityThread.post(args)) {
				...
			}
		}
	}
}

mActivityThread.post 方法把訊息在主執行緒執行,

2.3.11 Args.run() 方法

ArgsReceiverDispatcher 的內部類,因此它可以獲取到 ReceiverDispatcher 的成員,

static final class ReceiverDispatcher {
	final BroadcastReceiver mReceiver;

	ReceiverDispatcher(BroadcastReceiver receiver, Context context,
			Handler activityThread, Instrumentation instrumentation,
			boolean registered) {
		mReceiver = receiver;
		mContext = context;
		mActivityThread = activityThread;
	}
	
	final class Args extends BroadcastReceiver.PendingResult implements Runnable {
		private Intent mCurIntent;
		private final boolean mOrdered;
		public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
				boolean ordered, boolean sticky, int sendingUser) {
			super(resultCode, resultData, resultExtras,
					mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED,
					ordered, sticky, mIIntentReceiver.asBinder(), sendingUser);
			mCurIntent = intent;
			mOrdered = ordered;
		}
		
		public void run() {
			// mReceiver 就是 MyDynamicReceiver 物件
			final BroadcastReceiver receiver = mReceiver;
			final boolean ordered = mOrdered;
			
			final Intent intent = mCurIntent;
			mCurIntent = null;
			
			try {
				ClassLoader cl =  mReceiver.getClass().getClassLoader();
				intent.setExtrasClassLoader(cl);
				setExtrasClassLoader(cl);
				receiver.setPendingResult(this);
                // 呼叫 MyDynamicReceiver 物件的 onReceive 方法
				receiver.onReceive(mContext, intent);
			} catch (Exception e) {
				...
			}
			...
		}
	}
}

2.3.12 MyDynamicReceiver.onReceive() 方法

public class MyDynamicReceiver extends BroadcastReceiver {
    private static final String TAG = "MyDynamicReceiver";
    public static final String ACTION_LAUNCH_DYNAMIC = "com.wzc.receiver.LAUNCH_DYNAMIC";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "onRecei
        ve: receive action = " + action);
        if (ACTION_LAUNCH_DYNAMIC.equals(action)) {
            Log.d(TAG, "onReceive: do launch work...");
        }
    }
}

到這里,就收到了廣播了,

3.最后

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

4. 參考

  1. Android Broadcast廣播機制分析-袁輝輝

  2. 觀察者模式深入探討

    Android 里的廣播和廣播接收者對應于被觀察者和觀察者角色,是觀察者模式的典型應用,

  3. Android:多用戶的UserHandle的東東記錄
    這篇文章解決了對 UerHandle 類中的 uid,userid,appid 不清楚的問題,

  4. 四大組件之BroadcastRecord-袁輝輝

  5. 品茗論道說廣播(Broadcast內部機制講解)(上)

  6. 品茗論道說廣播(Broadcast內部機制講解)(下)

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

標籤:其他

上一篇:SQLServer:替換為NULL

下一篇:2、Flutter Widgets 之 Row和Column

標籤雲
其他(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