探索下Intent.getxxxExtra()是否需要捕获异常

本文探讨了在使用Intent.getxxxExtra()方法时是否需要捕获异常的问题,以getStringExtra为例,指出虽然BaseBundle的getString方法要求key非空,但为确保安全,仍建议在调用前进行判空检查。此外,当出现BadParcelableException时,系统会将其抛出,提示开发者注意异常处理。

偷个懒:和大家共同探索下Intent.getxxxExtra()是否需要捕获异常

下面我们以getStringExtra为例,共同探索下:

    private Bundle mExtras;

    /**
     * Retrieve extended data from the intent.
     *
     * @param name The name of the desired item.
     *
     * @return the value of an item previously added with putExtra(),
     * or null if no String value was found.
     *
     * @see #putExtra(String, String)
     */
    public @Nullable String getStringExtra(String name) {
        return mExtras == null ? null : mExtras.getString(name);
    }

Bundle中并没有实现getString方法,其中Bundle是继承BaseBundle的,我们去BaseBundle下看下

public final class Bundle extends BaseBundle implements Cloneable, Parcelable {

BaseBundle 的getString要求传入的key是非空的,实际上是非常有意义的,public的接口如果我们不控制入参@Nullable,
就需要在使用入参前,对入参进行非空判断,即使不可能为空,还是需要例行判空。因为没有人知道调用该接口的
是使用者还是恶意的攻击者。
言归正传,看unparcel()方法:

    /**
     * Returns the value associated with the given key, or null if
     * no mapping of the desired type exists for the given key or a null
     * value is explicitly associated with the key.
     *
     * @param key a String, or null
     * @return a String value, or null
     */
    @Nullable
    public String getString(@Nullable String key) {
        unparcel();
        final Object o = mMap.get(key);
        try {
            return (String) o;
        } catch (ClassCastException e) {
            typeWarning(key, o, "String", e);
            return null;
        }
    }
    @UnsupportedAppUsage
    /* package */ void unparcel() {
        synchronized (this) {
            final Parcel source = mParcelledData;
            if (source != null) {
                initializeFromParcelLocked(source, /*recycleParcel=*/ true, mParcelledByNative);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "unparcel "
                            + Integer.toHexString(System.identityHashCode(this))
                            + ": no parcelled data");
                }
            }
        }
    }

可以看见,在捕获到了BadParcelableException 后,它并没有把该异常内部消化掉,而是throw出去了。

    private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
            boolean parcelledByNative) {
        if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
            Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
                    + "clobber all data inside!", new Throwable());
        }

        if (isEmptyParcel(parcelledData)) {
            if (DEBUG) {
                Log.d(TAG, "unparcel "
                        + Integer.toHexString(System.identityHashCode(this)) + ": empty");
            }
            if (mMap == null) {
                mMap = new ArrayMap<>(1);
            } else {
                mMap.erase();
            }
            mParcelledData = null;
            mParcelledByNative = false;
            return;
        }

        final int count = parcelledData.readInt();
        if (DEBUG) {
            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + ": reading " + count + " maps");
        }
        if (count < 0) {
            return;
        }
        ArrayMap<String, Object> map = mMap;
        if (map == null) {
            map = new ArrayMap<>(count);
        } else {
            map.erase();
            map.ensureCapacity(count);
        }
        try {
            if (parcelledByNative) {
                // If it was parcelled by native code, then the array map keys aren't sorted
                // by their hash codes, so use the safe (slow) one.
                parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
            } else {
                // If parcelled by Java, we know the contents are sorted properly,
                // so we can use ArrayMap.append().
                parcelledData.readArrayMapInternal(map, count, mClassLoader);
            }
        } catch (BadParcelableException e) {
            if (sShouldDefuse) {
                Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
                map.erase();
            } else {
                throw e;
            }
        } finally {
            mMap = map;
            if (recycleParcel) {
                recycleParcel(parcelledData);
            }
            mParcelledData = null;
            mParcelledByNative = false;
        }
        if (DEBUG) {
            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + " final map: " + mMap);
        }
    }

好了,今天的学习先就此告一段落。“goodNight my stranger”

<think> 我们正在分析的是Android中Intent的两个方法:setComponent和setPackage。这两个方法在调整Intent的目标组件时起到关键作用。 1. **intent.setComponent(this.intent.getComponent())**: - `setComponent`方法用于直接指定Intent的目标组件(Activity/Service等),参数是一个`ComponentName`对象,包含包名和类名。 - 这里将临时Intent(从传入的ActivityRecord复制而来)的组件设置为当前任务记录的Intent的组件(`this.intent.getComponent()`)。 - **目的**:确保临时Intent的组件与当前任务记录的组件完全一致。这样在后续比较时,如果两个Activity是别名关系(指向同一个实现类),但组件名不同,通过统一设置为相同的组件名,可以避免因别名导致的误判。 2. **intent.setPackage(this.intent.getPackage())**: - `setPackage`方法用于设置Intent的目标包名。它不会指定具体类,但会限制Intent只能发送到指定包内的组件。 - 这里将临时Intent的包名设置为当前任务记录的Intent的包名(`this.intent.getPackage()`)。 - **目的**:统一包名,防止一个Intent设置了包名而另一个没有设置,导致`filterEquals`比较时因包名差异而产生错误结果。注意,这个操作仅在临时Intent没有选择器(Selector)时执行,因为选择器本身已经包含了目标信息。 **为什么需要这两步?** 在比较两个Intent是否具有相同的过滤器(即能否被同一个过滤器匹配)时,需要确保它们的关键属性(如组件或包名)一致。特别是当Activity使用别名(在AndroidManifest中通过`<activity-alias>`定义)时,同一个实际Activity可能对应多个组件名。通过强制设置相同的组件和包名,可以避免因别名不同而被视为不同过滤器。 **示例**: 假设任务记录的Intent组件是`com.example.RealActivity`,而传入的ActivityRecord的Intent组件是其别名`com.example.AliasActivity`。如果不做处理,直接比较这两个Intent,会因组件名不同而被认为过滤器不同。但通过: ```java intent.setComponent(this.intent.getComponent()); // 将别名组件的Intent强制设置为RealActivity ``` 就使得两个Intent的组件名相同,从而能正确比较过滤条件。 §§相关问题§§ 1. `Intent.setComponent()`和`Intent.setPackage()`方法在功能上有何区别? 2. 在Android中,Activity别名(Activity Alias)的使用场景是什么? 3. 为什么在设置包名之前需要检查选择器(Selector)是否为null? 4. 如何创建一个`ComponentName`对象?它包含哪些必要信息? 5. 在什么情况下,两个不同的`ComponentName`可能指向同一个Activity? </think>在Android的Intent机制中,`intent.setComponent()`和`intent.setPackage()`都是用于精确控制Intent目标组件的方法,但作用层级不同: ### 1. `intent.setComponent(this.intent.getComponent())` - **作用**:精确指定目标组件 - **参数**:`ComponentName`对象(包含完整包名+类名) - **效果**: - 将Intent的接收方锁定为特定包中的特定类(如`com.example.app.MainActivity`) - 覆盖Intent中的其他匹配属性(action/data等),使Intent变为**显式Intent** - **使用场景**: ```java // 示例:精确启动指定Activity Intent intent = new Intent(); intent.setComponent(new ComponentName("com.example", "com.example.MainActivity")); startActivity(intent); ``` ### 2. `intent.setPackage(this.intent.getPackage())` - **作用**:限定目标应用范围 - **参数**:包名字符串(如`com.example.app`) - **效果**: - 将Intent限制在指定包的应用内寻找匹配组件 - 不指定具体类,仍是**隐式Intent**,但缩小了搜索范围 - **使用场景**: ```java // 示例:只允许在指定包内处理Intent Intent intent = new Intent(Intent.ACTION_SEND); intent.setPackage("com.example.mailapp"); // 只允许此包内应用处理 ``` ### 在代码中的协同作用 在您提供的代码片段中: ```java intent.setComponent(this.intent.getComponent()); intent.setPackage(this.intent.getPackage()); ``` 1. **先设置组件**:确保两个Intent比较时使用**完全相同的目标组件**(包名+类名) 2. **再设置包名**:确保即使组件未显式设置时,**包名层面也能保持一致** 3. **目的**:使`intent.filterEquals()`能准确比较两个Intent的**核心过滤能力**,避免因组件/包名差异导致误判 ### 关键区别 | 特性 | `setComponent()` | `setPackage()` | |------|------------------|----------------| | 作用层级 | 组件级(具体类) | 应用级(整个包) | | Intent类型 | 转为显式Intent | 保持隐式Intent | | 匹配精度 | 精确指定单个组件 | 限定应用范围 | | 常用场景 | 启动特定Activity/Service | 限制广播接收范围 | 在任务栈管理中,这种组合确保了即使Activity使用别名(alias),也能正确识别是否为同一功能入口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值