15 深入学习intent和任务

创建NerdLauncher项目

  1. 新建继承自AppCompatActivity类的SingleFragmentActivity抽象类,使NerdLauncherActivity类继承它;
  2. 新建布局文件fragment_nerd_launcher.xml,布置RecyclerView视图;
  3. 新建NerdLauncherFragment类,使其继承Fragment,初始化RecyclerView视图。

NerdLauncherFragment.java

public class NerdLauncherFragment extends Fragment {
    private RecyclerView mRecyclerView;
    
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_nerd_launcher, container, false);

        mRecyclerView = view.findViewById(R.id.fragment_nerd_launcher_recycler_view);
        mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 4));

        setupAdapter();
        return view;
    }
}

解析隐式intent

NerdLaucher应用能以列表的形式展示设备上的可启动应用。(可启动应用是指用户点击主屏幕或启动器界面上的图标就能打开的应用。)要实现该功能,它会使用PackageManager获取所有可启动主activity。可启动主activity都带有包含 MAIN 操作和 LAUNCHER 类别的intent过滤器。

在项目的AndroidManifest.xml文件中,可以看到这种intent过滤器:

...
    <intent-filter>
        <!-- 应用显示在程序列表里,且此activity是应用最先启动的activity -->
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
        ...
    </intent-filter>
...

新增setupAdapter方法,该方法创建隐式intent并从PackageManager获取匹配它的所有activity(获得一个List),并对activity标签排序:

NerdLauncherFragment.java

private void setupAdapter() {
    //目标隐式intent包含所有可启动的主activity
    Intent startupIntent = new Intent(Intent.ACTION_MAIN);
    startupIntent.addCategory(Intent.CATEGORY_LAUNCHER);

    final PackageManager pm = getActivity().getPackageManager();
    List<ResolveInfo> activityList = pm.queryIntentActivities(startupIntent, 0);

    //对activity标签排序
    Collections.sort(activityList, new Comparator<ResolveInfo>() {
        @Override
        public int compare(ResolveInfo a, ResolveInfo b) {
            return String.CASE_INSENSITIVE_ORDER.compare(
                    a.loadLabel(pm).toString(),
                    b.loadLabel(pm).toString()
            );
        }
    });

    //为RecyclerView设置Adapter
    mRecyclerView.setAdapter(new ActivityAdapter(activityList));

    String msg = "共找到 " + activityList.size() + " 个activity。";
    Log.i(TAG, msg);
    Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
}

要注意的是:

  • startActivity(Intent) 方法意味着“启动匹配隐式intent的默认activity”,而不是
    “启动匹配隐式intent的activity”。
  • 调用 startActivity(Intent) 方法(或 startActivityForResult(…) 方法)发送隐式intent时,操作系统会悄悄为目标intent添加Intent.CATEGORY_DEFAULT 类别。

实现ViewHolder

需要在 NerdLauncherFragment 的 RecyclerView 视图中显示查询到的activity标签。activity标签是用户可以识别的展示名称。既然查询到的activity都是启动activity,标签名通常也就是应用名。

NerdLauncherFragment.java

private class ActivityHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private ResolveInfo mResolveInfo;
    private ImageView mIconImageView;
    private TextView mNameTextView;

    public ActivityHolder(@NonNull View itemView) {
        super(itemView);

        mIconImageView = itemView.findViewById(R.id.app_icon);
        mNameTextView = itemView.findViewById(R.id.app_name);
        mIconImageView.setOnClickListener(this);
    }

    public void bindActivity(ResolveInfo resolveInfo) {
        mResolveInfo = resolveInfo;
        PackageManager pm = getActivity().getPackageManager();

        Drawable appIcon = mResolveInfo.loadIcon(pm);
        String appName = mResolveInfo.loadLabel(pm).toString();

        mIconImageView.setImageDrawable(appIcon);
        mNameTextView.setText(appName);
    }

    @Override
    public void onClick(View v) {
        ActivityInfo activityInfo = mResolveInfo.activityInfo;

        //intent包含要启动的应用的包名和类名
        //ACTION_MAIN表示应用的主activity
        //FLAG_ACTIVITY_NEW_TASK表示在新任务启动新的activity,如果activity已在新任务中启动,会切换到该任务
        Intent intent = new Intent(Intent.ACTION_MAIN)
                .setClassName(activityInfo.packageName, activityInfo.name)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

实现ViewAdapter

NerdLauncherFragment.java

private class ActivityAdapter extends RecyclerView.Adapter<ActivityHolder> {
    private final List<ResolveInfo> mActivityList;

    public ActivityAdapter(List<ResolveInfo> activityList) {
        mActivityList = activityList;
    }

    @NonNull
    @Override
    public ActivityHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(getActivity());
        View view = layoutInflater.inflate(R.layout.list_item_app, parent, false);

        return new ActivityHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ActivityHolder holder, int position) {
        ResolveInfo resolveInfo = mActivityList.get(position);
        holder.bindActivity(resolveInfo);
    }

    @Override
    public int getItemCount() {
        return mActivityList.size();
    }
}

在运行时创建显式intent

(见实现ViewHolder => OnClick(View)方法)


任务与后退栈

任务是用户比较关心的activity栈。栈底部的activity通常称为基activity。用户可以看到栈顶的activity。用户点击后退键时,栈顶activity会弹出栈外。如果当前屏幕上显示的是基activity,点击后退键,系统会退回主屏幕。

默认情况下,新activity都在当前任务中启动。在CriminalIntent应用中,无论何时启动新activity,它都会被添加到当前任务中,如图22-5所示。即使要启动的activity不属于CriminalIntent应用,它同样也在当前任务中启动。启动activity发送 crime 报告就是这样的一个例子。

在这里插入图片描述

在当前任务中启动activity的好处是,用户可以在任务内而不是在应用层级间导航返回。

在这里插入图片描述

在任务间切换

在不影响各个任务状态的情况下,overview screen可以让我们在任务间切换。例如,如果进入联系人应用,然后切换到Twitter应用查看信息,这时我们就启动了两个任务。如果再切换回联系人应用,我们在两项任务中所处的状态位置都会被保存下来。

启动新任务

我们有时需要在当前任务中启动activity,而有时又需要在新任务中启动activity。为了在启动新activity时启动新任务,需要为intent添加一个标志:

(见实现ViewHolder => OnClick(View)方法)

先清除overview screen显示的所有任务,再运行NerdLauncher应用并启动CriminalIntent。这次,如果启动overview screen,就会看到CriminalIntent应用处于一个单独的任务中。

如果从NerdLauncher应用中再次启动CriminalIntent应用,不会创建第二个 CriminalIntent 任务。 FLAG_ACTIVITY_NEW_TASK 标志控制每个activity仅创建一个任务。 CrimeListActivity 已经有了一个运行的任务,因此Android会自动切换到原来的任务,而不是创建全新的任务。


使用NerdLauncher应用作为设备主屏幕

AndroidManifest.xml

...
    <intent-filter>
        ...
        <!-- 应用的此activity会成为可选的主界面(启动器) -->
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
...

深入学习

进程与任务

对象需要内存和虚拟机的支持才能存在。进程是操作系统创建的、供应用对象生存以及应用运行的地方。

进程通常占用由操作系统管理着的系统资源,如内存、网络端口以及打开的文件等。进程还拥有至少一个(可能多个)执行线程。在Android系统中,进程总会有一个运行的虚拟机。

每一个activity实例都仅存在于一个进程和一个任务中。这也是进程与任务的唯一相似之处。任务只包含activity,这些activity通常来自于不同的应用;而进程则包含了应用的全部运行代码和对象。

打开CriminalIntent应用,选择任何crime项,然后点击CHOOSE SUSPECT按钮。这会打开联系人应用让我们选择目标联系人。随即,联系人activity会被加入CriminalIntent应用任务。如果此时点击后退键在不同activity间切换的话,用户可能意识不到他们正在进程间切换。

然而,联系人activity实例确实是在联系人应用进程的内存空间创建的,而且也是在该应用进程里的虚拟机上运行的。

在这里插入图片描述

并发文档

在Lollipop设备上,对以 android.intent.action.SEND 或 action.intent.action.SEND_MULTIPLE 操作启动的activity,隐式intent选择器会创建独立的新任务。在旧设备上,activity是直接添加给CriminalIntent应用任务的。

这种现象要归因于Lollipop中叫作并发文档(concurrent documents)的新概念。有了并发文档,我们就可以在应用运行时动态创建任意数目的任务。在Lollipop之前,应用任务只能预先定义好,而且还要在manifest文件中明确指定。

在Lollipop设备上,如果需要应用启动多个任务,可采用两种方式:给intent打上 Intent.FLAG_ACTIVITY_NEW_DOCUMENT 标签,再调用 startActivity(…) 方法;或者在manifest文件中,为activity标签添加documentLaunchMode属性。

使用上述方法,一份文档只会对应一个任务。(如果发送带有和已存在任务相同数据的intent,系统就不会再创建新任务。)如果无论如何都想创建新任务,那就给intent同时打上Intent.FLAG_ACTIVITY_NEW_DOCUMENT 和 Intent.FLAG_ACTIVITY_MULTIPLE_TASK 标签,或者把manifest文件中的 documentLaunchMode 属性值修改为 always 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值