Android系统启动完成后,第一个启动的Activity就是主界面应用程序Launcher,相当于电脑的桌面。Launcher界面可以看到系统中安装的所有APP,点击APP即可启动该APP应用了。那么,Launcher到底是如何启动的以及如何自定义自己的Launcher了?这篇文章我们就来讲讲这两个问题。
Launcher的启动 系统进程SystemServer
启动后,开始加载系统核心服务如 ActivityManagerService
,WindowManagerService
,PowerManagerService
,同时加载系统的其他如VibratorService
,ConnectivityService
以及TelephonyRegistry
等系统服务,最后着手启动HOME activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private void startOtherServices () { mActivityManagerService.systemReady(new Runnable () { @Override public void run () { mSystemServiceManager.startBootPhase( SystemService.PHASE_ACTIVITY_MANAGER_READY); try { mActivityManagerService.startObservingNativeCrashes(); } catch (Throwable e) { reportWtf("observing native crashes" , e); } Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartSystemUI" ); try { startSystemUi(context); } catch (Throwable e) { reportWtf("starting System UI" , e); } .... mSystemServiceManager.startBootPhase( SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); .... }); }
AMS准备工作完毕,着手启动HOME:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public void systemReady (final Runnable goingCallback) { .... retrieveSettings(); final int currentUserId; synchronized (this ) { currentUserId = mUserController.getCurrentUserIdLocked(); readGrantedUriPermissionsLocked(); } if (goingCallback != null ) goingCallback.run(); synchronized (this ) { mBooting = true ; if (UserManager.isSplitSystemUser()) { ComponentName cName = new ComponentName (mContext, SystemUserHomeActivity.class); try { AppGlobals.getPackageManager().setComponentEnabledSetting(cName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0 , UserHandle.USER_SYSTEM); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } startHomeActivityLocked(currentUserId, "systemReady" ); }
启动HOME界面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 String mTopAction = Intent.ACTION_MAIN; Intent getHomeIntent () { Intent intent = new Intent (mTopAction, mTopData != null ? Uri.parse(mTopData) : null ); intent.setComponent(mTopComponent); intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING); if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) { intent.addCategory(Intent.CATEGORY_HOME); } return intent; } boolean startHomeActivityLocked (int userId, String reason) { Intent intent = getHomeIntent(); ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS | (needToCheckBbc ? PackageManager.GET_META_DATA : 0 ), userId); if (aInfo != null ) { intent.setComponent(new ComponentName (aInfo.applicationInfo.packageName, aInfo.name)); 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 ) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); if (needToCheckBbc) { aInfo = PersonaManagerService.changeInfoIfBBC(mContext, aInfo, intent); } mActivityStarter.startHomeActivityLocked(intent, aInfo, reason); } } else { Slog.wtf(TAG, "No home screen found for " + intent, new Throwable ()); } return true ; }
向AMS发送启动Activity的Binder请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ActivityStarter { .... void startHomeActivityLocked (Intent intent, ActivityInfo aInfo, String reason) { mSupervisor.moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason); startActivityLocked(null , intent, null , null , aInfo, null , null , null , null , null , 0 , 0 , 0 , null , 0 , 0 , 0 , null , false , false , null , null , null ); if (mSupervisor.inResumeTopActivity) { mSupervisor.scheduleResumeTopActivities(); } } }
至此HOME启动完成了。
自定义Launcher 那么,如何来定义自己的Launcher了?这里,我们写一个简单的Launcher界面,上面列出了系统的应用程序,点击上面的图标即可启动应用。
首先,新建一个Activity: CustomHomeActivity:其中主要包含了一个GridView用于显示APP的图标跟名字,APP数据采用Loader进行异步加载,加载完后,数据跟GridView进行绑定。注意,在AndroidManifest.xml
中声明activity,必须加上如下intent-filter
:
1 2 3 4 5 6 7 8 <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>
这样,每次用户点击Home按键时,系统就会弹出对话框让你选择启动哪一个Launcher。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class CustomHomeActivity extends AppCompatActivity implements LoaderManager .LoaderCallbacks<List<AppItem>>, AppItemAdapter.ItemClickListener{ private static final String TAG = "CustomHomeActivity" ; private static final int APP_LOADER = 0x01 ; private GridView mGvAppsList; private AppItemAdapter mAdapter; private List<AppItem> mAppList; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); Log.v(TAG, "onCreate()" ); setContentView(R.layout.activity_custom_home); mGvAppsList = (GridView)findViewById(R.id.gv_app_list); mAdapter = new AppItemAdapter (this ); mGvAppsList.setAdapter(mAdapter); mAdapter.setListener(this ); getSupportLoaderManager().initLoader(APP_LOADER,null ,CustomHomeActivity.this ); } @Override protected void onResume () { super .onResume(); Log.v(TAG,"onResume()" ); Loader loader = getSupportLoaderManager().getLoader(APP_LOADER); if (mAppList == null || !loader.isStarted()){ loader.startLoading(); } } @Override public Loader<List<AppItem>> onCreateLoader (int id, Bundle args) { Log.v(TAG,"onCreateLoader()" ); return new AppLoader (getApplicationContext()); } @Override public void onLoadFinished (Loader<List<AppItem>> loader, List<AppItem> data) { Log.v(TAG, "onLoadFinished" ); mAdapter.setData(data); mAdapter.notifyDataSetChanged(); mAppList = data; } @Override public void onLoaderReset (Loader<List<AppItem>> loader) { loader.forceLoad(); } @Override public void onItemClick (int position) { Log.v(TAG,"onItemClick(): pos = " + position); PackageManager pm = getPackageManager(); String pkgName = mAppList.get(position).getAppInfo().packageName; Intent intent = pm.getLaunchIntentForPackage(pkgName); if (intent != null ){ startActivity(intent); } } }
现在,就要定义一个Loader用于APP数据的读取与加载,加载完后,把数据传给主线程;这里为了,提高加载的速度,利用DiskLruCache来做数据缓存(只是个示例,具体性能估计提高不大):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 public class AppLoader extends AsyncTaskLoader <List<AppItem>> { private static final String TAG = "AppLoader" ; private static final int DISK_CACHE_SIZE = 1024 *1024 *50 ; private static final String DISK_CACHE_NAME = "MyLauncher" ; private static final int DISK_CACHE_INDEX = 0 ; private PackageManager mPackageMgr; private List<AppItem> mAppList; private HashSet<String> mAppsName; private DiskLruCache mDiskCache; public AppLoader (Context context) { super (context); mPackageMgr = context.getPackageManager(); File cachePath = getDiskCachePath(context); if (!cachePath.exists()){ cachePath.mkdirs(); } try { if (getUsableSpace(cachePath) > DISK_CACHE_SIZE) { mDiskCache = DiskLruCache.open(cachePath, 1 , 1 , DISK_CACHE_SIZE); } }catch (IOException e){ e.printStackTrace(); } } @Override public List<AppItem> loadInBackground () { Log.v(TAG, "loadInBackground()" ); List<ApplicationInfo> appInfoList = mPackageMgr.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES); if (appInfoList == null ){ appInfoList = new ArrayList <>(); } Log.v(TAG,"loadInBackground(): installed apps size = " + appInfoList.size()); mAppList = new ArrayList <>(appInfoList.size()); mAppsName = new HashSet <>(); for (ApplicationInfo info: appInfoList){ String pkgName = info.packageName; String label = mPackageMgr.getApplicationLabel(info).toString(); if (!mAppsName.contains(label) && mPackageMgr.getLaunchIntentForPackage(pkgName) != null ){ String key = getCacheKey(pkgName); Drawable d = loadFromDiskCache(key); AppItem item; if (d != null ){ item = new AppItem (mPackageMgr,d,label); saveIconToDiskCache(key,d); }else { item = new AppItem (mPackageMgr, info); saveIconToDiskCache(key,item.getAppIcon()); } mAppList.add(item); mAppsName.add(label); Log.v(TAG,"application label : label " + label); } } Log.v(TAG, "loadInBackground(): app size = " + mAppList.size()); return mAppList; } @Override public void deliverResult (List<AppItem> appList) { Log.v(TAG,"deliverResult()" ); if (isReset()){ if (appList != null ){ onReleaseResources(appList); } } List<AppItem> oldApps = mAppList; mAppList = appList; if (isStarted()){ super .deliverResult(appList); } if (oldApps != null ){ onReleaseResources(oldApps); } } @Override protected void onStartLoading () { Log.v(TAG,"onStartLoading()" ); if (mAppList != null ){ deliverResult(mAppList); } if (takeContentChanged() || mAppList == null ){ forceLoad(); } } @Override protected void onStopLoading () { cancelLoad(); } @Override public void onCanceled (List<AppItem> apps) { super .onCanceled(apps); onReleaseResources(apps); } @Override protected void onReset () { super .onReset(); onStopLoading(); if (mAppList != null ){ onReleaseResources(mAppList); mAppList = null ; mAppsName = null ; } } protected void onReleaseResources (List<AppItem> apps) { } private String getCacheKey (String pkgName) { Log.v(TAG,"getCacheKey()" ); String key; try { MessageDigest digest = MessageDigest.getInstance("SHA" ); digest.update(pkgName.getBytes()); key = bytesToHexString(digest.digest()); }catch (NoSuchAlgorithmException e){ key = pkgName; } return key; } private boolean saveIconToDiskCache (String key,Drawable icon) { Log.v(TAG,"saveIconToDiskCache()" ); if (mDiskCache == null ){ Log.e(TAG,"saveIconToDiskCache(): disk cache is not available" ); return false ; } Bitmap bitmap = BitmapUtil.drawableToBitmap(icon); ByteArrayOutputStream bos = new ByteArrayOutputStream (bitmap.getByteCount()); bitmap.compress(Bitmap.CompressFormat.PNG,100 ,bos); try { DiskLruCache.Editor editor = mDiskCache.edit(key); if (editor != null ){ OutputStream os = editor.newOutputStream(DISK_CACHE_INDEX); os.write(bos.toByteArray()); editor.commit(); } mDiskCache.flush(); }catch (IOException e){ e.printStackTrace(); return false ; } return true ; } private Drawable loadFromDiskCache (String key) { Log.v(TAG,"loadFromDiskCache()" ); if (mDiskCache == null || mDiskCache.size() <= 0 ){ return null ; } try { DiskLruCache.Snapshot snapshot = mDiskCache.get(key); if (snapshot != null ) { FileInputStream fis = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX); Bitmap bitmap = BitmapFactory.decodeStream(fis); return new BitmapDrawable (getContext().getResources(),bitmap); } }catch (IOException e){ e.printStackTrace(); } return null ; } private File getDiskCachePath (Context context) { boolean isExternalMounted = Environment.getExternalStorageState(). equals(Environment.MEDIA_MOUNTED); File diskCacheDir; if (!isExternalMounted){ diskCacheDir = new File (context.getCacheDir(),DISK_CACHE_NAME); }else { diskCacheDir = new File (Environment.getExternalStorageDirectory(),DISK_CACHE_NAME); } return diskCacheDir; } private long getUsableSpace (File path) { long size = path.getUsableSpace(); Log.v(TAG,"getUsableSpace(): size = " + size); return size; } private String bytesToHexString (byte [] bytes) { StringBuilder sb = new StringBuilder (); for (int i = 0 ; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1 ) { sb.append('0' ); } sb.append(hex); } return sb.toString(); } }
最后,建立一个ListAdapter,用于将数据与GridView进行绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public class AppItemAdapter extends BaseAdapter { private static final String TAG = "AppItemAdapter" ; private static final int DEFAULT_WIDTH = 150 ; private static final int DEFAULT_HEIGHT = 150 ; private Context mContext; private List<AppItem> mAppList = Collections.EMPTY_LIST; private static SparseArray<View> sViewCache; private ItemClickListener mListener; public interface ItemClickListener { void onItemClick (int position) ; } public AppItemAdapter (Context context) { mContext = context; } public void setListener (ItemClickListener listener) { mListener = listener; } public void setData (List<AppItem> data) { mAppList = data; sViewCache = new SparseArray <>(data.size()); } @Override public int getCount () { return mAppList.size(); } @Override public AppItem getItem (int position) { return mAppList.get(position); } @Override public long getItemId (int position) { return 0 ; } @Override public View getView (int position, View convertView, ViewGroup parent) { View root = sViewCache.get(position); if (root == null ) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); root = inflater.inflate(R.layout.layout_app_item, parent, false ); ViewHolder vh = new ViewHolder (root, position); sViewCache.setValueAt(position,root); Drawable d = mAppList.get(position).getAppIcon(); Bitmap bitmap = BitmapUtil.drawableToBitmap(d); Bitmap scaleBitmap = Bitmap.createScaledBitmap(bitmap, DEFAULT_WIDTH, DEFAULT_HEIGHT, false ); vh.ivIcon.setImageBitmap(scaleBitmap); vh.tvName.setText(mAppList.get(position).getAppName()); Log.v(TAG, "application label = " + vh.tvName.getText()); } return root; } private class ViewHolder { ImageView ivIcon; TextView tvName; public ViewHolder (View root, final int pos) { ImageView icon = (ImageView)root.findViewById(R.id.iv_app_icon); TextView name = (TextView)root.findViewById(R.id.tv_app_name); ivIcon = icon; tvName = name; root.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { mListener.onItemClick(pos); } }); } public ViewHolder (ImageView icon,TextView name) { ivIcon = icon; tvName = name; } } }
这样一个简单的Launcher就写好了,项目详情请参考:
https://github.com/runningforlife/AndroidExamples