实现唯一Launcher

来源:互联网 发布:淘宝 非典 编辑:程序博客网 时间:2024/06/06 08:31

在定制的系统中,Launcher通常自己来写,并需要把自己写的Launcher替换成系统唯一的Launcher。

原理分析

如果了解Android的启动流程的同学都知道,Zygote启动SystemServer,SystemServer的main函数开始启动各种服务。
首先启动init1,然后启动init2. init1这个方法是被Zygote调用来初始化系统的,init1会启动native的服务如SurfaceFlinger,AudioFlinger等等,
这些工作做完以后会回调init2来启动Android的service。

public static final void init2() {           Log.i(TAG, "Entered the Android system server!");           Thread thr = new ServerThread();           thr.setName("android.server.ServerThread");           thr.start();                                                                                   }  

init2中启动ServerThread线程,ServerThread中启动了一系列的服务,比如ActivityManagerService,EntropyService等等。
当这些服务起来以后,((ActivityManagerService)
ActivityManagerNative.getDefault()).
systemReady() 在systemReady后开始开始启动Launcher。
frameworks\base\services\Java\com\android\server\am\ActivityManagerService.java line 8422

public void systemReady(final Runnable goingCallback) {           // In the simulator, startRunning will never have been called, which           // normally sets a few crucial variables. Do it here instead.           .........................          resumeTopActivityLocked(null);       } 

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java (line 2576)

 private final boolean resumeTopActivityLocked(HistoryRecord prev) {                                  // Find the first activity that is not finishing.           HistoryRecord next = topRunningActivityLocked(null);     // Remember how we'll process this pause/resume situation, and ensure           // that the state is reset however we wind up proceeding.           final boolean userLeaving = mUserLeaving;           mUserLeaving = false;           if (next == null) {               // There are no more activities!  Let's just start up the               // Launcher...               return startHomeActivityLocked();           }          ....     }

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java (line 2457)

private boolean startHomeActivityLocked() {                                                          if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL                   && mTopAction == null) {               // We are running in factory test mode, but unable to find               // the factory test app, so just sit around displaying the               // error message and don't try to start anything.               return false;           }           Intent intent = new Intent(               mTopAction,               mTopData != null ? Uri.parse(mTopData) : null);           intent.setComponent(mTopComponent);           if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {               intent.addCategory(Intent.CATEGORY_HOME);  //这里就是了         }  ....     } 

然后我们发现在寻找Launcher的时候是根据HOME的filter(在Manifest中定义的)来过滤。

public static final String CATEGORY_HOME = "android.intent.category.HOME";

方法分析

既然如此,我们现在就可以更改我们的AndroidManifest.xml来安装自己的HOME。所以我们只需在AndroidManifest.xml添加两行代码:

<category android:name="android.intent.category.HOME" />    <category android:name="android.intent.category.DEFAULT" />    

现在重新编译我们的应用程序,把编译生成的APK放到相应的目录下,一般是/system/app,启动开发板,
我们可以看到在我们的LCD屏上面,要求用户选择launcher。
既然是这样那我们可以通过暴力修改这个值来达到唯一的launcher的目的,这就好比原来一把锁配了好多个钥匙,结果你看不下去了,想一个人独占这个这个房间,
最简单粗暴的方法就是把锁换掉(方法二),不过我觉得这样要改动的地方太多了,linux下面可以使用grep命令,本人推荐使用方法1。


第一种方式

原理:通过修改PackageParser文件

位置:frameworks\base\core\Java\Android\content\pm\PackageParser.java

在parseIntent这个私有方法,因为APK在安装的时候会通过这个方法解析Manifest清单文件,然后将每个Activity的intent-filter保存在内存中,所以修改这个方法就相当于修改了内存的应用注册表的信息,也就是偷换概念的做法,等于是在安装的时候就把问题解决了,扼杀在萌芽状态:

1.在你自己的launcher的Manifest的Activity里Intent-filter里加入:

2.那么在代码里修改的地方的xxx就替换成你自己起的名字
原理就是安装每个APK的时候 只要是launcher ,就把category是HOME的值改成x,只要是你的launcher就把category的值改成HOME,那么全局就只有你一个launcher了

/*************************************************************************************/              if(Intent.CATEGORY_HOME.equals(value)) {                  value = "x";              } else if((Intent.CATEGORY_HOME + ".xxx").equals(value)) {                  value = Intent.CATEGORY_HOME;              }  /*************************************************************************************/ 

使用这种方法修改时必须确保你的launcher被安装了或者放在了syste/app下了且Manifest的Activity里的intent-filter为android.category.HOME.xxx

具体做法:
修改frameworks\base\core\java\android\content\pm\PackageParser.java里的parseIntent方法

vim frameworks/base/core/java/android/content/pm/PackageParser.java

源代码为:

private boolean parseIntent(Resources res,              XmlPullParser parser, AttributeSet attrs, int flags,              IntentInfo outInfo, String[] outError, boolean isActivity)              throws XmlPullParserException, IOException {          TypedArray sa = res.obtainAttributes(attrs,                  com.android.internal.R.styleable.AndroidManifestIntentFilter);          int priority = sa.getInt(                  com.android.internal.R.styleable.AndroidManifestIntentFilter_priority, 0);          if (priority > 0 && isActivity && (flags&PARSE_IS_SYSTEM) == 0) {              Log.w(TAG, "Activity with priority > 0, forcing to 0 at "                      + mArchiveSourcePath + " "                      + parser.getPositionDescription());              priority = 0;          }          outInfo.setPriority(priority);          TypedValue v = sa.peekValue(                  com.android.internal.R.styleable.AndroidManifestIntentFilter_label);          if (v != null && (outInfo.labelRes=v.resourceId) == 0) {              outInfo.nonLocalizedLabel = v.coerceToString();          }          outInfo.icon = sa.getResourceId(                  com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);          sa.recycle();          int outerDepth = parser.getDepth();          int type;          while ((type=parser.next()) != parser.END_DOCUMENT                 && (type != parser.END_TAG || parser.getDepth() > outerDepth)) {              if (type == parser.END_TAG || type == parser.TEXT) {                  continue;              }              String nodeName = parser.getName();              if (nodeName.equals("action")) {                  String value = attrs.getAttributeValue(                          ANDROID_RESOURCES, "name");                  if (value == null || value == "") {                      outError[0] = "No value supplied for <android:name>";                      return false;                  }                  XmlUtils.skipCurrentTag(parser);                  outInfo.addAction(value);              } else if (nodeName.equals("category")) {                  String value = attrs.getAttributeValue(                          ANDROID_RESOURCES, "name");                  if (value == null || value == "") {                      outError[0] = "No value supplied for <android:name>";                      return false;                  }                  XmlUtils.skipCurrentTag(parser);                  /*************************************************************************************/                  if(Intent.CATEGORY_HOME.equals(value)) {                      value = "x";                  } else if((Intent.CATEGORY_HOME + ".xxx").equals(value)) {                      value = Intent.CATEGORY_HOME;                  }                  /*************************************************************************************/                  outInfo.addCategory(value);              } else if (nodeName.equals("data")) {                  sa = res.obtainAttributes(attrs,                          com.android.internal.R.styleable.AndroidManifestData);                  String str = sa.getNonConfigurationString(                          com.android.internal.R.styleable.AndroidManifestData_mimeType, 0);                  if (str != null) {                      try {                          outInfo.addDataType(str);                      } catch (IntentFilter.MalformedMimeTypeException e) {                          outError[0] = e.toString();                          sa.recycle();                          return false;                      }                  }                  str = sa.getNonConfigurationString(                          com.android.internal.R.styleable.AndroidManifestData_scheme, 0);                  if (str != null) {                      outInfo.addDataScheme(str);                  }                  String host = sa.getNonConfigurationString(                          com.android.internal.R.styleable.AndroidManifestData_host, 0);                  String port = sa.getNonConfigurationString(                          com.android.internal.R.styleable.AndroidManifestData_port, 0);                  if (host != null) {                      outInfo.addDataAuthority(host, port);                  }                  str = sa.getNonConfigurationString(                          com.android.internal.R.styleable.AndroidManifestData_path, 0);                  if (str != null) {                      outInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL);                  }                  str = sa.getNonConfigurationString(                          com.android.internal.R.styleable.AndroidManifestData_pathPrefix, 0);                  if (str != null) {                      outInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX);                  }                  str = sa.getNonConfigurationString(                          com.android.internal.R.styleable.AndroidManifestData_pathPattern, 0);                  if (str != null) {                      outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);                  }                  sa.recycle();                  XmlUtils.skipCurrentTag(parser);              } else if (!RIGID_PARSER) {                  Log.w(TAG, "Unknown element under <intent-filter>: "                          + parser.getName() + " at " + mArchiveSourcePath + " "                          + parser.getPositionDescription());                  XmlUtils.skipCurrentTag(parser);              } else {                  outError[0] = "Bad element under <intent-filter>: " + parser.getName();                  return false;              }          }          outInfo.hasDefault = outInfo.hasCategory(Intent.CATEGORY_DEFAULT);          if (false) {              String cats = "";              Iterator<String> it = outInfo.categoriesIterator();              while (it != null && it.hasNext()) {                  cats += " " + it.next();              }              System.out.println("Intent d=" +                      outInfo.hasDefault + ", cat=" + cats);          }          return true;      }  

第二种方式

定义一个私有的filter选项,然后用这个选项来过滤HOME.一般情况下我们使用Manifest中定义的

category android:name=”android.intent.category.HOME”

来过滤的,我们现在增加一个私有的HOME_FIRST过滤。(HOME_FIRST随意)

在Intent.java(frameworks/base/core/java/android/content/Intent.java)中添加两行代码//larosn:添加CATEGORY_HOME_FIRST@SdkConstant(SdkConstantType.INTENT_CATEGORY)public static final String CATEGORY_HOME_FIRST = "android.intent.category.HOME_FIRST";

修改和CATEGORY_HOME相关的所有的地方,都改成HOME_FIRST,主要是framework中的这几个地方:

  • frameworks/base/services/java/com/android/server/am/ActivityManagerService.java中

 //intent.addCategory(Intent.CATEGORY_HOME);改成intent.addCategory(Intent.CATEGORY_HOME_FIRST); //larson: //if (r.intent.hasCategory(Intent.CATEGORY_HOME)) {改成if (r.intent.hasCategory(Intent.CATEGORY_HOME_FIRST)) { //larson: Intent.CATEGORY_HOME -> Intent.CATEGORY_HOME_FIRST
  • frameworks/base/services/java/com/android/server/am/HistoryRecorder.java中
//  _intent.hasCategory(Intent.CATEGORY_HOME) &&改成 _intent.hasCategory(Intent.CATEGORY_HOME_FIRST) && //larson: Intent.CATEGORY_HOME->Intent.CATEGORY_HOME_FIRST
  • frameworks/policies/base/mid/com/android/internal/policy/impl/MidWindowManager.java中
//  mHomeIntent.addCategory(Intent.CATEGORY_HOME);改成 mHomeIntent.addCategory(Intent.CATEGORY_HOME_FIRST); //larson
  • frameworks/policies/base/mid/com/android/internal/policy/impl/RecentApplicationsDialog.java中
//  new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),0);改成 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME_FIRST),0); 
  • frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java中
//  mHomeIntent.addCategory(Intent.CATEGORY_HOME);改成 mHomeIntent.addCategory(Intent.CATEGORY_HOME_FIRST); //larson
  • frameworks/policies/base/phone/com/android/internal/policy/impl/RecentApplicationsDialog.java中
//  ResolveInfo homeInfo = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),0);改成 ResolveInfo homeInfo = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME_FIRST),0); //larson

这里我们有一种比较暴力的更改方法,其实原理和第二种方式差不多,就是把系统中原有的public static final String CATEGORY_HOME = “android.intent.category.HOME”;
更改成public static final String CATEGORY_FS_HOME = “android.intent.category.LS_HOME”;
然后修改和CATEGORY_HOME相关的所有的地方,都改成CATEGORY_FS_HOME.如果不知道修改哪些地方,可以使用如下命令去查找:

grep CATEGORY_HOME -l * -R

将上述结果文件中和CATEGORY_HOME相关的所有的地方,都改成CATEGORY_LS_HOME即可。(别家的launcher里面的CATEGORY_HOME就不要改了)。

然后把自己app的AndroidManifest.xml更改为CATEGORY_FS_HOME,重新编译到系统,刷机后看到的就是我们的launcher。


第三种方式

在Android5.1的源码里面有两个launcher,一个叫做Launcher2,一个叫Launcher3,分别位于源码目录的/packages/apps/Launcher2,以及/packages/apps/Launcher3 。

直接删除上述两个应用的Android.mk(建议将Android.mk改成Android.mk.bak,以免以后再次启用)

假设我的应用为XLauncher;
自己的XLauncher程序拷贝到packages/apps目录下,要在目录里添加Android.mk,具体参考packages/apps下面应用自带的Android.mk,必须保证AndroidManifest.xml下里的值为:

<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"/>

然后修改Build目录下的core文件:
vim build/target/product/core.mk

我把里面的Launcher2替换成了自己的XLauncher,保存退出,然后重新make一遍。

刷机,运行,看效果。

注意事项

在实际开发过程中,我三种方式都使用了,但是没有效果,后来才发现,修改了build下的mk文件是需要整编的。

也就是说需要先make clean一下。我才用的第三种方法,亲测成功,有问题的同学可以在下面留言。

0 0
原创粉丝点击