Launcher的含义、如何启动的详解

当我们手指触摸屏幕上指定App图标Logo的时候,App就由Launcher开始启动了。
Launcher到底是一个概念呢?

Launcher本质上也是一个应用程序,和我们的App一样,也是继承自Activity

packages/apps/Launcher2/src/com/android/launcher2/Launcher.java

 

public final class Launcher extends Activity
        implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                   View.OnTouchListener {
                   }

Launcher类中实现了点击OnClickListener、长按OnLongClickListener等回调接口,来接收用户的输入操作。

 比如,我们点击一个图标,是怎么打来应用呢?
 通过捕捉图标对应的点击事件操作,然后调用startActivity()发送对应的Intent请求,Launcher就是这么做的。

那么到底是处理的哪个对象的点击事件呢?既然Launcher是App,并且有界面,那么肯定有布局文件,是的,我找到了布局文件launcher.xml
 

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
    android:id="@+id/launcher">

    <com.android.launcher2.DragLayer
        android:id="@+id/drag_layer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <!-- Keep these behind the workspace so that they are not visible when
             we go into AllApps -->
        //底部操作栏和上面图标布局的分割线
        <include
            android:id="@+id/dock_divider"
            layout="@layout/workspace_divider"
            android:layout_marginBottom="@dimen/button_bar_height"
            android:layout_gravity="bottom" />

        //页面指示器
        <include
            android:id="@+id/paged_view_indicator"
            layout="@layout/scroll_indicator"
            android:layout_gravity="bottom"
            android:layout_marginBottom="@dimen/button_bar_height" />

        <!-- The workspace contains 5 screens of cells -->
        //包含了5个屏幕的单元格。类似于手机首页 底部打电话 短信 浏览器 通讯录等底部按钮
        <com.android.launcher2.Workspace
            android:id="@+id/workspace"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingStart="@dimen/workspace_left_padding"
            android:paddingEnd="@dimen/workspace_right_padding"
            android:paddingTop="@dimen/workspace_top_padding"
            android:paddingBottom="@dimen/workspace_bottom_padding"
            launcher:defaultScreen="2"
            launcher:cellCountX="@integer/cell_count_x"
            launcher:cellCountY="@integer/cell_count_y"
            launcher:pageSpacing="@dimen/workspace_page_spacing"
            launcher:scrollIndicatorPaddingLeft="@dimen/workspace_divider_padding_left"
            launcher:scrollIndicatorPaddingRight="@dimen/workspace_divider_padding_right">

            <include android:id="@+id/cell1" layout="@layout/workspace_screen" />
            <include android:id="@+id/cell2" layout="@layout/workspace_screen" />
            <include android:id="@+id/cell3" layout="@layout/workspace_screen" />
            <include android:id="@+id/cell4" layout="@layout/workspace_screen" />
            <include android:id="@+id/cell5" layout="@layout/workspace_screen" />
        </com.android.launcher2.Workspace>

    ...ignore some code...

    </com.android.launcher2.DragLayer>
</FrameLayout>

从上面这些我们应该可以看出一些东西来:Launcher大量使用include标签来实现界面的复用,而且定义了很多的自定义控件实现界面效果,dock_divider从布局的参数声明上可以猜出,是底部操作栏和上面图标布局的分割线,而paged_view_indicator则是页面指示器,和App首次进入的引导页下面的界面引导是一样的道理。当然,我们最关心的是Workspace这个布局,因为注释里面说在这里面包含了5个屏幕的单元格,想必你也猜到了,这个就是在首页存放我们图标的那五个界面(不同的ROM会做不同的DIY,数量不固定)。

workspace_screen布局:
 

<com.android.launcher2.CellLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:launcher="http://schemas.android.com/apk/res/com.android.launcher"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingStart="@dimen/cell_layout_left_padding"
    android:paddingEnd="@dimen/cell_layout_right_padding"
    android:paddingTop="@dimen/cell_layout_top_padding"
    android:paddingBottom="@dimen/cell_layout_bottom_padding"
    android:hapticFeedbackEnabled="false"
    launcher:cellWidth="@dimen/workspace_cell_width"
    launcher:cellHeight="@dimen/workspace_cell_height"
    launcher:widthGap="@dimen/workspace_width_gap"
    launcher:heightGap="@dimen/workspace_height_gap"
    launcher:maxGap="@dimen/workspace_max_gap" />

系统界面布局分析:
1.底部快捷入口View:
 就一个CellLayout自定义布局,那么我们就可以猜到了,既然可以存放图标,那么这个自定义的布局很有可能是继承自ViewGroup或者是其子类,实际上,CellLayout确实是继承自ViewGroup。在CellLayout里面,只放了一个子View,那就是ShortcutAndWidgetContainer。从名字也可以看出来,ShortcutAndWidgetContainer这个类就是用来存放快捷图标和Widget小部件的,那么里面放的是什么对象呢?

在桌面上的图标,使用的是BubbleTextView对象,这个对象在TextView的基础之上,添加了一些特效,比如你长按移动图标的时候,图标位置会出现一个背景(不同版本的效果不同),所以我们找到BubbleTextView对象的点击事件,就可以找到Launcher如何开启一个App了。

2.程序列表中的点击:
除了桌面上图标之外,在程序列表中点击图标,也可以开启对应的程序。这里的图标使用的不是BubbleTextView对象,而是PagedViewIcon对象,我们如果找到它的点击事件,就也可以找到Launcher如何开启一个App。


底部快捷的BubbleTextView对象的点击事件在哪里呢?我来告诉你:在Launcher.onClick(View v)里面。
 

   /**
     * Launches the intent referred by the clicked shortcut
     */
    public void onClick(View v) {

          ...ignore some code...

         Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
        //开始开启Activity咯~
            boolean success = startActivitySafely(v, intent, tag);

            if (success && v instanceof BubbleTextView) {
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
            //如果点击的是图标文件夹,就打开文件夹
            if (v instanceof FolderIcon) {
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton) {
        ...ignore some code...
        }
    }

程序列表中的点击:
 

程序列表界面使用的是AppsCustomizePagedView对象,所以我在这个类里面找到了onClick(View v)。

com.android.launcher2.AppsCustomizePagedView.java

/**
 * The Apps/Customize page that displays all the applications, widgets, and shortcuts.
 */
public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
        View.OnClickListener, View.OnKeyListener, DragSource,
        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
        LauncherTransitionable {

     @Override
    public void onClick(View v) {

         ...ignore some code...

        if (v instanceof PagedViewIcon) {
            mLauncher.updateWallpaperVisibility(true);
            mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
        } else if (v instanceof PagedViewWidget) {
                 ...ignore some code..
         }
     }      
   }

两种方式,殊途同归!

不管从哪里点击图标,调用的都是Launcher.startActivitySafely()方法。

下面一起来看一下Launcher.startActivitySafely()的流程

 boolean startActivitySafely(View v, Intent intent, Object tag) {
        boolean success = false;
        try {
            success = startActivity(v, intent, tag);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
        }
        return success;
    }


boolean startActivity(View v, Intent intent, Object tag) {

        //Activity会添加到一个新的Task栈
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            boolean useLaunchAnimation = (v != null) &&
                    !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);

            if (useLaunchAnimation) {
                if (user == null || user.equals(android.os.Process.myUserHandle())) {
                    startActivity(intent, opts.toBundle());
                } else {
                    launcherApps.startMainActivity(intent.getComponent(), user,
                            intent.getSourceBounds(),
                            opts.toBundle());
                }
            } else {
                if (user == null || user.equals(android.os.Process.myUserHandle())) {
                    startActivity(intent);
                } else {
                    launcherApps.startMainActivity(intent.getComponent(), user,
                            intent.getSourceBounds(), null);
                }
            }
            return true;
        } catch (SecurityException e) {
        ...
        }
        return false;
    }

//实际调用的是 startActivityForResult方法

@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

所以,Launcher开启一个App,其实和我们在Activity中直接startActivity()基本一样,都是调用Activity.startActivityForResult()

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值