android4.0 launcher分析整理

一、Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:
<manifest 
xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.android.launcher"> 
 
<original-package android:name="com.android.launcher2" /> 
... 
<application  

  android:name="com.android.launcher2.LauncherApplication"
  android:label="@string/application_name"
  android:icon="@drawable/ic_launcher_home"
  android:hardwareAccelerated="@bool/config_hardwareAccelerated"     --硬加速
  android:largeHeap="@bool/config_largeHeap">      --运行时最小堆内存,避免内存out of memory错误的出现

    > 

    <activity 
        android:name="com.android.launcher2.Launcher" 
        ... 
        > 
        <intent-filter> 
            <action android:name="android.intent.action.MAIN" /> 
            <category android:name="android.intent.category.HOME" /> 
            <category android:name="android.intent.category.DEFAULT" /> 
            <category android:name="android.intent.category.MONKEY"/> 
        </intent-filter> 
    </activity> 
    ... 
</application> 
</manifest> 

二、

LauncherApplication----onCreate 应用入口

1)获取屏幕的显示尺寸、来判断是否是大屏幕,同时得到它的屏幕密度全局变量。

final int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE || screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;
sScreenDensity = getResources().getDisplayMetrics().density;

2)建立应用图标缓存器;创建LauncherModel 对象,在launcher数据变更时操作数据库。

mIconCache = new IconCache(this);
mModel = new LauncherModel(this, mIconCache);

//LauncherModel主要用于加载桌面的图标、插件和文件夹,同时LaucherModel是一个广播接收器,在程序包发生改变、区域、或者配置文件发生改变时,都会发送广播给LaucherModel,LaucherModel会根据不同的广播来做相应加载操作

3)注册(Intent.ACTION_PACKAGE_ADDED  Intent.ACTION_PACKAGE_REMOVED  Intent.ACTION_PACKAGE_CHANGED)应用添加、删除、改变监听等;

  LauncherModel提供接收器对上面事件进行监听。

IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
registerReceiver(mModel, filter);

4)注册本地化配置变化监听,搜寻相关变化监听,外部存储上的应用变化监听。接收器同上

filter = new IntentFilter();
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
registerReceiver(mModel, filter);
filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
registerReceiver(mModel, filter);
filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
registerReceiver(mModel, filter);

5)注册对桌面favorites content provider 数据变化监听器,触发后执行onChang方法。

ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
mFavoritesObserver);

 三、Launcher Activity:实现了点击、长按、触屏、LauncherModelAllAppViews接口。

protected void onCreate(Bundle savedInstanceState) { 
    ... 
    mModel = app.setLauncher(this); 
    mIconCache = app.getIconCache(); 
    ... 
    mAppWidgetManager = AppWidgetManager.getInstance(this); 
    mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); 
    mAppWidgetHost.startListening(); 
    ... 
    //检查本地保存的配置是否需要更新 
    checkForLocaleChange(); 
    setContentView(R.layout.launcher); 
    //对UI控件进行初始化和配置 
    setupViews(); 
    //向用户展示指导的页面 
    showFirstRunWorkspaceCling(); 
    registerContentObservers(); 
    ... 
    if (!mRestoring) { 
    //为Launcher加载数据 
        mModel.startLoader(this, true); 
    } 
    ... 

可以将Launcher.onCreate()所执行的操作大概分为七步:
1、LauncherAppliaction.setLauncher()。
2、AppWidgetHost.startListening(),对widget事件进行监听
3、checkForLocaleChange(),检查更新本地保存的配置文件
4、setupViews(),配置UI控件
5、showFirstRunWorkspaceCling(),第一次启动时显示的指导画面
6、registerContentObservers(),设置内容监听器
7、LauncherModel.startLoader(),为Launcher加载Workspace和AllApps中的内容

三、下面具体来看启动过程中到底做了些什么

1、LauncherModel setLauncher(Launcher launcher) {
  mModel.initialize(launcher);
  return mModel;
}

/**
* Set this as the current Launcher activity object for the loader.
*/
public void initialize(Callbacks callbacks) {
  synchronized (mLock) {
    mCallbacks = new WeakReference<Callbacks>(callbacks);
  }
}

由于Launcher实现了Callback接口。在mModel中,将传入的Launcher对象向下转型为Callback赋值给mCallbacks变量。并在LauncherModel中获得了一个Callbacks的软引用。通过这一过程,将Launcher对象作为Callback与mModel进行绑定,当mModel后续进行操作时,Launcher可以通过回调得到结果。

2、mAppWidgetHost.startListening()

LauncherAppWidgetHost继承自AppWidgetHost,它的作用就是帮助Launcher管理AppWidget,并且能够捕获长按事件,使得应用可以正常的删除、添加AppWidget。通过调用mAppWidgetHost.startListening()方法,开启监听。

3、checkForLocaleChange()

private void checkForLocaleChange() {
  if (sLocaleConfiguration == null) {
    new AsyncTask<Void, Void, LocaleConfiguration>() {
    @Override
      protected LocaleConfiguration doInBackground(Void... unused) {
        LocaleConfiguration localeConfiguration = new LocaleConfiguration();
        readConfiguration(Launcher.this, localeConfiguration);
        return localeConfiguration;
    }

    @Override
    protected void onPostExecute(LocaleConfiguration result) {
      sLocaleConfiguration = result;
      checkForLocaleChange(); // recursive, but now with a locale configuration
    }
      }.execute();
    return;
    }

  final Configuration configuration = getResources().getConfiguration();

  final String previousLocale = sLocaleConfiguration.locale;
  final String locale = configuration.locale.toString();

  final int previousMcc = sLocaleConfiguration.mcc;
  final int mcc = configuration.mcc;

  final int previousMnc = sLocaleConfiguration.mnc;
  final int mnc = configuration.mnc;

  boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;

  if (localeChanged) {
    sLocaleConfiguration.locale = locale;
    sLocaleConfiguration.mcc = mcc;
    sLocaleConfiguration.mnc = mnc;

    mIconCache.flush();

    final LocaleConfiguration localeConfiguration = sLocaleConfiguration;
    new Thread("WriteLocaleConfiguration") {
      @Override
      public void run() {
      writeConfiguration(Launcher.this, localeConfiguration);
      }
    }.start();
  }
}

在这个方法中,先是检查了本地文件的配置与当前设备的配置是否一致,如果不一致,则更新配置,并且清空IconCache,因为配置的改变可能会改变语言环境,所以需要清空IconCache中的内容重新加载。

4、setupViews()  这个方法中简单的对所有的UI控件进行加载和配置

I、mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
mQsbDivider = (ImageView) findViewById(R.id.qsb_divider);
mDockDivider = (ImageView) findViewById(R.id.dock_divider);

// Setup the drag layer
mDragLayer.setup(this, dragController);

DragLayer继承自FrameLayout,是整个Launcher的根容器。当快捷图标或者AppWidget被拖拽时,事件的处理就在DragLayer进行操作的。

public void setup(Launcher launcher, DragController controller) {
  mLauncher = launcher;
  mDragController = controller;
}

只是简单的做了赋值操作,使DragLayer持有Launcher和DragController对象的引用。DragController可以帮助其实现拖拽操作。

II、Hotseat也是FrameLayout的直接子类,代表主屏幕下方的dock栏,可以放置4个快捷图标和一个进入AllApps的按钮。

mHotseat = (Hotseat) findViewById(R.id.hotseat);
if (mHotseat != null) {
  mHotseat.setup(this);
}

public void setup(Launcher launcher) {
  mLauncher = launcher;
  setOnKeyListener(new HotseatIconKeyEventListener());
}

mHotseat.setup()方法调用之后,Hotseat持有Launcher对象的引用,并且用HotseatIconKeyEvenListener对自身的按键进行监听,进入HotseatIconKeyEvenListener

/**
* A keyboard listener we set on all the hotseat buttons.
*/
class HotseatIconKeyEventListener implements View.OnKeyListener {
  public boolean onKey(View v, int keyCode, KeyEvent event) {
    final Configuration configuration = v.getResources().getConfiguration();
    return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation);
  }
}

调用方法handleHotseatButtonKeyEvent()来处理相应的事件

/**
* Handles key events in the workspace hotseat (bottom of the screen).
*/
static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) {
  final ViewGroup parent = (ViewGroup) v.getParent();
  final ViewGroup launcher = (ViewGroup) parent.getParent();
  final Workspace workspace = (Workspace) launcher.findViewById(R.id.workspace);
  final int buttonIndex = parent.indexOfChild(v);
  final int buttonCount = parent.getChildCount();
  final int pageIndex = workspace.getCurrentPage();

// NOTE: currently we don't special case for the phone UI in different
// orientations, even though the hotseat is on the side in landscape mode. This
// is to ensure that accessibility consistency is maintained across rotations.

  final int action = e.getAction();
  final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
  boolean wasHandled = false;
  switch (keyCode) {
    case KeyEvent.KEYCODE_DPAD_LEFT:
      if (handleKeyEvent) {
      // Select the previous button, otherwise snap to the previous page
        if (buttonIndex > 0) {
          parent.getChildAt(buttonIndex - 1).requestFocus();
        } else {
          workspace.snapToPage(pageIndex - 1);
        }
      }
      wasHandled = true;
      break;
  case KeyEvent.KEYCODE_DPAD_RIGHT:
    if (handleKeyEvent) {
      // Select the next button, otherwise snap to the next page
      if (buttonIndex < (buttonCount - 1)) {
        parent.getChildAt(buttonIndex + 1).requestFocus();
      } else {
        workspace.snapToPage(pageIndex + 1);
        }
      }
      wasHandled = true;
      break;
  case KeyEvent.KEYCODE_DPAD_UP:
    if (handleKeyEvent) {
      // Select the first bubble text view in the current page of the workspace
      final CellLayout layout = (CellLayout) workspace.getChildAt(pageIndex);
      final CellLayoutChildren children = layout.getChildrenLayout();
      final View newIcon = getIconInDirection(layout, children, -1, 1);
      if (newIcon != null) {
        newIcon.requestFocus();
      } else {
        workspace.requestFocus();
       }
     }
    wasHandled = true;
    break;
  case KeyEvent.KEYCODE_DPAD_DOWN:
    // Do nothing
    wasHandled = true;
    break;
  default: break;
  }
  return wasHandled;
}

III、未完待续

 

 

posted @ 2012-09-21 12:00  罗小姿  阅读(912)  评论(0编辑  收藏  举报