Android6.0 Launcher2应用解析
在之前我们分析了android6.0系统在启动时安装应用程序的过程,这些应用程序安装好之后,launcher应用就负责把它们在桌面上展示出来。
一、ams启动launcher
launcher应用是在ams的systemready方法中直接调用starthomeactivitylocked启动的,下面是systemready启动launcher的代码。
starthomeactivitylocked(mcurrentuserid, "systemready");我们来看下这个函数,先调用了gethomeintent方法来获取intent,然后也是调用resolveactivityinfo函数从pkms获取activityinfo,接着当进程没有启动的话,调用activitystacksupervisor的starthomeactivity函数
boolean starthomeactivitylocked(int userid, string reason) { if (mfactorytest == factorytest.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 = gethomeintent();//获取intent activityinfo ainfo = resolveactivityinfo(intent, stock_pm_flags, userid);//获取activityinfo if (ainfo != null) { intent.setcomponent(new componentname( ainfo.applicationinfo.packagename, ainfo.name)); // don't do this if the home app is currently being // instrumented. ainfo = new activityinfo(ainfo); ainfo.applicationinfo = getappinfoforuser(ainfo.applicationinfo, userid); processrecord app = getprocessrecordlocked(ainfo.processname, ainfo.applicationinfo.uid, true); if (app == null || app.instrumentationclass == null) {//进程没有启动调用 eventlog.writeevent(eventlogtags.am_proc_start,"ams -> starthomeactivitylocked starthomeactivity then startactivitylock : "+ ainfo.processname); intent.setflags(intent.getflags() | intent.flag_activity_new_task); mstacksupervisor.starthomeactivity(intent, ainfo, reason); } } return true; }
我们先来看看gethomeintent这个函数。
intent gethomeintent() { intent intent = new intent(mtopaction, mtopdata != null ? uri.parse(mtopdata) : null); intent.setcomponent(mtopcomponent); if (mfactorytest != factorytest.factory_test_low_level) { intent.addcategory(intent.category_home); } return intent; }
然后我们来看下activitystacksupervisor的starthomeactivity函数,它也是调用了startactivitylocked来启动activity的,在之前的博客分析过这个函数这里我们就不介绍了。
void starthomeactivity(intent intent, activityinfo ainfo, string reason) { movehomestacktasktotop(home_activity_type, reason); startactivitylocked(null /* caller */, intent, null /* resolvedtype */, ainfo, null /* voicesession */, null /* voiceinteractor */, null /* resultto */, null /* resultwho */, 0 /* requestcode */, 0 /* callingpid */, 0 /* callinguid */, null /* callingpackage */, 0 /* realcallingpid */, 0 /* realcallinguid */, 0 /* startflags */, null /* options */, false /* ignoretargetsecurity */, false /* componentspecified */, null /* outactivity */, null /* container */, null /* intask */); if (inresumetopactivity) { // if we are in resume section already, home activity will be initialized, but not // resumed (to avoid recursive resume) and will stay that way until something pokes it // again. we need to schedule another resume. scheduleresumetopactivities(); } }
二、launcher启动
接着我们来看下launcher的androidmanifest.xml,我们看下其主activity有一个category为android.intent.category.home
<application android:name="com.android.launcher2.launcherapplication" android:label="@string/application_name" android:icon="@mipmap/ic_launcher_home" android:hardwareaccelerated="true" android:largeheap="@bool/config_largeheap" android:supportsrtl="true"> <activity android:name="com.android.launcher2.launcher" android:launchmode="singletask" android:cleartaskonlaunch="true" android:statenotneeded="true" android:resumewhilepausing="true" android:theme="@style/theme" android:windowsoftinputmode="adjustpan" android:screenorientation="nosensor"> <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> ......
在launcher.java的oncreate函数中调用了mmodel.startloader函数
protected void oncreate(bundle savedinstancestate) { ...... if (!mrestoring) { if (spausedfromuseraction) { // if the user leaves launcher, then we should just load items asynchronously when // they return. mmodel.startloader(true, -1); } else { // we only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground mmodel.startloader(true, mworkspace.getcurrentpage()); } } ......
startloader函数会post一个runnable消息,我们来看下它的run方法
public void startloader(boolean islaunching, int synchronousbindpage) { synchronized (mlock) { if (debug_loaders) { log.d(tag, "startloader islaunching=" + islaunching); } // clear any deferred bind-runnables from the synchronized load process // we must do this before any loading/binding is scheduled below. mdeferredbindrunnables.clear(); // don't bother to start the thread if we know it's not going to do anything if (mcallbacks != null && mcallbacks.get() != null) { // if there is already one running, tell it to stop. // also, don't downgrade islaunching if we're already running islaunching = islaunching || stoploaderlocked(); mloadertask = new loadertask(mapp, islaunching); if (synchronousbindpage > -1 && mallappsloaded && mworkspaceloaded) { mloadertask.runbindsynchronouspage(synchronousbindpage); } else { sworkerthread.setpriority(thread.norm_priority); sworker.post(mloadertask); } } } }
在它的run方法中会调用loadandbindallapps函数,在loadandbindallapps函数中又会调用loadallappsbybatch函数
public void run() { synchronized (mlock) { misloadertaskrunning = true; } final callbacks cbk = mcallbacks.get(); final boolean loadworkspacefirst = cbk != null ? (!cbk.isallappsvisible()) : true; keep_running: { // elevate priority when home launches for the first time to avoid // starving at boot time. staring at a blank home is not cool. synchronized (mlock) { if (debug_loaders) log.d(tag, "setting thread priority to " + (mislaunching ? "default" : "background")); process.setthreadpriority(mislaunching ? process.thread_priority_default : process.thread_priority_background); } // first step. load workspace first, this is necessary since adding of apps from // managed profile in all apps is deferred until onresume. see http://b/17336902. if (loadworkspacefirst) { if (debug_loaders) log.d(tag, "step 1: loading workspace"); loadandbindworkspace(); } else { log.d(tag, "step 1: special: loading all apps"); loadandbindallapps(); }
我们先来看下loadandbindallapps函数,这个函数先进入while循环,然后调用了launcherapps的getactivitylist函数,后面又会调用callbacks的bindallapplications
private void loadallappsbybatch() { final long t = debug_loaders ? systemclock.uptimemillis() : 0; ...... mbgallappslist.clear(); final int profilecount = profiles.size(); for (int p = 0; p < profilecount; p++) { ...... while (i < n && !mstopped) { if (i == 0) { final long qiatime = debug_loaders ? systemclock.uptimemillis() : 0; apps = mlauncherapps.getactivitylist(null, user); ...... mhandler.post(new runnable() { public void run() { final long t = systemclock.uptimemillis(); if (callbacks != null) { if (firstprofile) { callbacks.bindallapplications(added); } else { callbacks.bindappsadded(added); } if (debug_loaders) { log.d(tag, "bound " + added.size() + " apps in " + (systemclock.uptimemillis() - t) + "ms"); } } else { log.i(tag, "not binding apps: no launcher activity"); } } }); ......
我们先来看launcherapps的getactivitylist函数,它先用mservice成员变量调用getlauncheractivities函数获取到list<resolveinfo>,然后封装在arraylist<launcheractivityinfo> 中。
public list<launcheractivityinfo> getactivitylist(string packagename, userhandle user) { list<resolveinfo> activities = null; try { activities = mservice.getlauncheractivities(packagename, user); } catch (remoteexception re) { throw new runtimeexception("failed to call launcherappsservice"); } if (activities == null) { return collections.empty_list; } arraylist<launcheractivityinfo> lais = new arraylist<launcheractivityinfo>(); final int count = activities.size(); for (int i = 0; i < count; i++) { resolveinfo ri = activities.get(i); long firstinstalltime = 0; try { firstinstalltime = mpm.getpackageinfo(ri.activityinfo.packagename, packagemanager.get_uninstalled_packages).firstinstalltime; } catch (namenotfoundexception nnfe) { // sorry, can't find package } launcheractivityinfo lai = new launcheractivityinfo(mcontext, ri, user, firstinstalltime); if (debug) { log.v(tag, "returning activity for profile " + user + " : " + lai.getcomponentname()); } lais.add(lai); } return lais; }
其service是class launcherappsimpl extends ilauncherapps.stub 下面是getlauncheractivities函数,肯定也是通过pkms来获取相关activity的resolveinfo的。
@override public list<resolveinfo> getlauncheractivities(string packagename, userhandle user) throws remoteexception { ensureinuserprofiles(user, "cannot retrieve activities for unrelated profile " + user); if (!isuserenabled(user)) { return new arraylist<resolveinfo>(); } final intent mainintent = new intent(intent.action_main, null); mainintent.addcategory(intent.category_launcher); mainintent.setpackage(packagename); long ident = binder.clearcallingidentity(); try { list<resolveinfo> apps = mpm.queryintentactivitiesasuser(mainintent, 0 /* flags */, user.getidentifier()); return apps; } finally { binder.restorecallingidentity(ident); } }
最后回调launcher.java的bindallapplications函数,最后在这个函数中可以在桌面上展示系统中所有的应用程序了。
public void bindallapplications(final arraylist<applicationinfo> apps) { runnable setallappsrunnable = new runnable() { public void run() { if (mappscustomizecontent != null) { mappscustomizecontent.setapps(apps); } } }; // remove the progress bar entirely; we could also make it gone // but better to remove it since we know it's not going to be used view progressbar = mappscustomizetabhost. findviewbyid(r.id.apps_customize_progress_bar); if (progressbar != null) { ((viewgroup)progressbar.getparent()).removeview(progressbar); // we just post the call to setapps so the user sees the progress bar // disappear-- otherwise, it just looks like the progress bar froze // which doesn't look great mappscustomizetabhost.post(setallappsrunnable); } else { // if we did not initialize the spinner in oncreate, then we can directly set the // list of applications without waiting for any progress bars views to be hidden. setallappsrunnable.run(); } }
三、显示应用图标
我们再来看下launcher的onclick函数,当调用showworkspace可以显示所有应用的图标。
public void onclick(view v) { // make sure that rogue clicks don't get through while allapps is launching, or after the // view has detached (it's possible for this to happen if the view is removed mid touch). if (v.getwindowtoken() == null) { return; } if (!mworkspace.isfinishedswitchingstate()) { return; } 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())); 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) { if (isallappsvisible()) { showworkspace(true); } else { onclickallappsbutton(v); } } }
在showworkspace中会显示所有的图标
void showworkspace(boolean animated, runnable oncompleterunnable) { if (mstate != state.workspace) { boolean wasinspringloadedmode = (mstate == state.apps_customize_spring_loaded); mworkspace.setvisibility(view.visible); hideappscustomizehelper(state.workspace, animated, false, oncompleterunnable); // show the search bar (only animate if we were showing the drop target bar in spring // loaded mode) if (msearchdroptargetbar != null) { msearchdroptargetbar.showsearchbar(wasinspringloadedmode); } // we only need to animate in the dock divider if we're going from spring loaded mode showdockdivider(animated && wasinspringloadedmode); // set focus to the appscustomize button if (mallappsbutton != null) { mallappsbutton.requestfocus(); } } mworkspace.flashscrollingindicator(animated); // change the state *after* we've called all the transition code mstate = state.workspace; // resume the auto-advance of widgets muserpresent = true; updaterunning(); // send an accessibility event to announce the context change getwindow().getdecorview() .sendaccessibilityevent(accessibilityevent.type_window_state_changed); }
而点击应用图标,最终会调用launcher.java的startactivitysafely来启动应用。这里调用的startactivity就是activity的startactivity函数。
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; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。