码迷,mamicode.com
首页 > 移动开发 > 详细

Android Intent 源码学习

时间:2015-01-06 20:09:57      阅读:222      评论:0      收藏:0      [点我收藏+]

标签:

前言

    这篇文章主要是介绍一下Android Intent,并且从Android源码的角度对Intent查询匹配过程进行分析。

Intent介绍

    Intent的中文是“意图”的意思,而意图是一个非常抽象的概念,那么在Android的编码设计中,如何实例化意图呢?因此Android系统明确指定一个Intent可由两方面属性来衡量。
  • 主要属性:包括Action和Data。其中Action用于表示该Intent所表达的动作意图,Data用于表示该Action所操作的数据。
  • 次要属性:包括Category、Type、Component和Extras。其中Category表示类别,Type表示数据的MIME类型,Component可用于指定特定的Intent的响应者(例如指定intent为某个包下的某个class类),Extras用于承载其他的信息。
    Android系统中主要有两种类型的Intent,显示Intent(Explicit Intent)和隐式Intent(Implicit Intent)。
  • Explicit Intent:这类Intent明确指明了要找哪个Component。在代码中可以通过setClassName或者setComponent来锁定目标对象。
  • Implicit Intent:这类Intent不明确指明要启动哪个Component,而是设置Action、Data、Category让系统来筛选出合适的Component。
    接下来,写两个代码示例,来介绍一下Explicit Intent和Implict Inent。首先是Explicit Intent:
	private void startExplicitIntentWithComponent() {
		Intent intent = new Intent();
		ComponentName component = new ComponentName("com.example.photocrop", "com.example.photocrop.MainActivity");
		intent.setComponent(component);
		startActivity(intent);
	}
	
	private void startExplicitIntentWithClassName() {
		Intent intent = new Intent();
		intent.setClassName("com.example.photocrop", "com.example.photocrop.MainActivity");
		startActivity(intent);
	}
    但是,从源码里面去看,发现setClassName也是借助了ComponentName实现了Explicit Intent。源码如下:
    public Intent setClassName(String packageName, String className) {
        mComponent = new ComponentName(packageName, className);
        return this;
    }
    然后,在给出一个Implict Intent的代码示例。我这里用一个Activity标注一些Intent Filter为例,然后在写一个Intent用于启动它。
        <activity 
            android:name=".SendIntentType">
            <intent-filter >
                <action android:name="justtest"/>
                <category android:name="justcategory"/>
            </intent-filter>
        </activity>
    在当前应用的AndroidManifest.xml中,给SendIntentType类增加了intent-filter,action的名字为“justtest”,category的名字为“justcategory”。启动该Activity的代码如下:
	private void startImplictIntent() {
		Intent intent = new Intent();
		intent.setAction("justaction");
		intent.addCategory("justcategory");
		startActivity(intent);
	}
    系统在匹配Implict Intent的过程中,将以Intent Filter列出的3项内容为参考标准,具体步骤如下:
  1. 首先匹配IntentFilter的Action,如果Intent设置的action不满足IntentFilter的Action,则匹配失败。如果IntentFilter未设定Action或者设定的Action相同,则匹配成功。
  2. 然后检查IntentFilter的Category,匹配方法同Action的匹配相同,唯一例外的是当Category为CATEGORY_DEFAULT的情况。
  3. 最后检查Data。

Activityi信息的管理

    从上面的分析可以看出,系统的匹配Intent的过程中,首先需要管理当前系统中所有Activity信息。Activity的信息是PackageManagerService在扫描APK的时候进行收集和管理的。相关源码如下:
			// 处理该package的activity信息
			N = pkg.activities.size();
			r = null;
			for (i = 0; i < N; i++) {
				PackageParser.Activity a = pkg.activities.get(i);
				a.info.processName = fixProcessName(pkg.applicationInfo.processName, a.info.processName,
						pkg.applicationInfo.uid);
				mActivities.addActivity(a, "activity");
			}
    上面代码中,有两个比较重要的数据结构,如下图所示。
技术分享
    结合代码和上图的数据结构,可知:
  • mAcitivitys为ActivityIntentResolver类型,是PKMS的成员变量,用于保存系统中所有与Activity相关的信息。此数据结构内部也有一个mActivities变量,它以ComponentName为key,保存PackageParser.Activity对象。
  • 从APK中解析得到的所有和Acitivity相关的信息(包括XML中声明的IntentFilter标签)都由PackageParser.Activity来保存。
    前面代码中调用addActivity函数完成了私有信息的公有化。addActivity函数的代码如下:
		public final void addActivity(PackageParser.Activity a, String type) {
			final boolean systemApp = isSystemApp(a.info.applicationInfo);
			mActivities.put(a.getComponentName(), a);
			final int NI = a.intents.size();
			for (int j = 0; j < NI; j++) {
				PackageParser.ActivityIntentInfo intent = a.intents.get(j);
				if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) {
					// 非系统APK的priority必须为0
					intent.setPriority(0);
				}
				addFilter(intent);
			}
		}
    接下来看一下addFilter函数。函数源码如下:
    public void addFilter(F f) {
    	// mFilters保存所有IntentFilter信息
        mFilters.add(f);
        int numS = register_intent_filter(f, f.schemesIterator(),
                mSchemeToFilter, "      Scheme: ");
        int numT = register_mime_types(f, "      Type: ");
        if (numS == 0 && numT == 0) {
            register_intent_filter(f, f.actionsIterator(),
                    mActionToFilter, "      Action: ");
        }
        if (numT != 0) {
            register_intent_filter(f, f.actionsIterator(),
                    mTypedActionToFilter, "      TypedAction: ");
        }
    }
    这里又出现了几种数据结构,它们的类似都是ArrayMap<String, F[ ]>,其中F为模板参数。
  • mSchemeToFilter:用于保存uri中与scheme相关的IntentFilter信息。
  • mActionToFilter:用于保存仅设置Action条件的IntentFilter信息。
  • mTypedActionToFilter:用于保存既设置了Action又设置了Data的MIME类型的IntentFilter信息。
    了解了大概的数据结构之后,我们来看一下register_intent_filter的函数实现:
    private final int register_intent_filter(F filter, Iterator<String> i,
            ArrayMap<String, F[]> dest, String prefix) {
        if (i == null) {
            return 0;
        }

        int num = 0;
        while (i.hasNext()) {
            String name = i.next();
            num++;
            addFilter(dest, name, filter);
        }
        return num;
    }
    然后又是一个addFilter函数,明显是一个函数重载,我们来看一下这个addFilter的实现:
    private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) {
        F[] array = map.get(name);
        if (array == null) {
            array = newArray(2);
            map.put(name,  array);
            array[0] = filter;
        } else {
            final int N = array.length;
            int i = N;
            while (i > 0 && array[i-1] == null) {
                i--;
            }
            if (i < N) {
                array[i] = filter;
            } else {
                F[] newa = newArray((N*3)/2);
                System.arraycopy(array, 0, newa, 0, N);
                newa[N] = filter;
                map.put(name, newa);
            }
        }
    }
    其实代码还是很简单的,如果F数组存在,则判断容量,不够则扩容,够的话就找到位置插入。如果F数组不存在,则创建一个容量为2的数组,将0号元素赋值为该filter。

Intent匹配查询分析

    客户端通过ApplicationPackageManager输出的queryIntentActivities函数向PackageManagerService发起一次查询请求,代码如下:
    @Override
    public List<ResolveInfo> queryIntentActivities(Intent intent,
                                                   int flags) {
        return queryIntentActivitiesAsUser(intent, flags, mContext.getUserId());
    }

    /** @hide Same as above but for a specific user */
    @Override
    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
                                                   int flags, int userId) {
        try {
            return mPM.queryIntentActivities(
                intent,
                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                flags,
                userId);
        } catch (RemoteException e) {
            throw new RuntimeException("Package manager has died", e);
        }
    }
    可以看到,queryIntentActivities的真正实现是在PackageManagerService.java中,函数代码如下:
	public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) {
		if (!sUserManager.exists(userId))
			return Collections.emptyList();
		enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "query intent activities");
		ComponentName comp = intent.getComponent();
		if (comp == null) {
			if (intent.getSelector() != null) {
				intent = intent.getSelector();
				comp = intent.getComponent();
			}
		}

		if (comp != null) {
			// Explicit的Intent,直接根据component得到对应的ActivityInfo
			final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
			final ActivityInfo ai = getActivityInfo(comp, flags, userId);
			if (ai != null) {
				final ResolveInfo ri = new ResolveInfo();
				ri.activityInfo = ai;
				list.add(ri);
			}
			return list;
		}

		// reader
		synchronized (mPackages) {
			final String pkgName = intent.getPackage();
			if (pkgName == null) {
				// Implicit Intent
				return mActivities.queryIntent(intent, resolvedType, flags, userId);
			}
			final PackageParser.Package pkg = mPackages.get(pkgName);
			if (pkg != null) {
				// 指定了包名的Intent
				return mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities, userId);
			}
			return new ArrayList<ResolveInfo>();
		}
	}
    可以看到,Explicit Intent的实现较为简单,我们重点来看一下Implict Intent实现。Implicit Intent调用了queryIntent方法,我们来看一下queryIntent的实现代码:
		public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, int userId) {
			if (!sUserManager.exists(userId))
				return null;
			mFlags = flags;
			return super.queryIntent(intent, resolvedType, (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
		}
    继续跟踪到IntentResolver.java的queryIntent方法,源码如下:
    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
            int userId) {
        String scheme = intent.getScheme();

        ArrayList<R> finalList = new ArrayList<R>();

        // 最多有4轮匹配操作
        F[] firstTypeCut = null;
        F[] secondTypeCut = null;
        F[] thirdTypeCut = null;
        F[] schemeCut = null;

        // If the intent includes a MIME type, then we want to collect all of
        // the filters that match that MIME type.
        if (resolvedType != null) {
            int slashpos = resolvedType.indexOf('/');
            if (slashpos > 0) {
                final String baseType = resolvedType.substring(0, slashpos);
                if (!baseType.equals("*")) {
                    if (resolvedType.length() != slashpos+2
                            || resolvedType.charAt(slashpos+1) != '*') {
                        // Not a wild card, so we can just look for all filters that
                        // completely match or wildcards whose base type matches.
                        firstTypeCut = mTypeToFilter.get(resolvedType);
                        secondTypeCut = mWildTypeToFilter.get(baseType);
                    } else {
                        // We can match anything with our base type.
                        firstTypeCut = mBaseTypeToFilter.get(baseType);
                        secondTypeCut = mWildTypeToFilter.get(baseType);
                    }
                    // Any */* types always apply, but we only need to do this
                    // if the intent type was not already */*.
                    thirdTypeCut = mWildTypeToFilter.get("*");
                } else if (intent.getAction() != null) {
                    // The intent specified any type ({@literal *}/*).  This
                    // can be a whole heck of a lot of things, so as a first
                    // cut let's use the action instead.
                    firstTypeCut = mTypedActionToFilter.get(intent.getAction());
                }
            }
        }

        // If the intent includes a data URI, then we want to collect all of
        // the filters that match its scheme (we will further refine matches
        // on the authority and path by directly matching each resulting filter).
        if (scheme != null) {
            schemeCut = mSchemeToFilter.get(scheme);
        }

        // If the intent does not specify any data -- either a MIME type or
        // a URI -- then we will only be looking for matches against empty
        // data.
        if (resolvedType == null && scheme == null && intent.getAction() != null) {
            firstTypeCut = mActionToFilter.get(intent.getAction());
        }

        FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
        if (firstTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly,
                    resolvedType, scheme, firstTypeCut, finalList, userId);
        }
        if (secondTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly,
                    resolvedType, scheme, secondTypeCut, finalList, userId);
        }
        if (thirdTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly,
                    resolvedType, scheme, thirdTypeCut, finalList, userId);
        }
        if (schemeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly,
                    resolvedType, scheme, schemeCut, finalList, userId);
        }
        sortResults(finalList);

        return finalList;
    }
    具体的查询匹配过程是由buildResolveList函数完成了。查询的匹配实现我就不贴代码了,大家自己去查询看就好了。

参考文献

[1] 深入理解Android:卷二

Android Intent 源码学习

标签:

原文地址:http://blog.csdn.net/wzy_1988/article/details/42456423

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!