JasonWang's Blog

Android Launcher工作原理

字数统计: 2.2k阅读时长: 12 min
2017/02/18

Android系统启动完成后,第一个启动的Activity就是主界面应用程序Launcher,相当于电脑的桌面。Launcher界面可以看到系统中安装的所有APP,点击APP即可启动该APP应用了。那么,Launcher到底是如何启动的以及如何自定义自己的Launcher了?这篇文章我们就来讲讲这两个问题。

Launcher的启动

系统进程SystemServer启动后,开始加载系统核心服务如 ActivityManagerService,WindowManagerService,PowerManagerService,同时加载系统的其他如VibratorServiceConnectivityService以及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() {

// 准备启动SystemUI以及HOME界面
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) {

// Start up initial activity.
mBooting = true;
// Enable home activity for system user, so that the system can always boot
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();
}
}
// 启动HOME应用
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的类别为HOME
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));
// 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) {
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) {
//将HOME activity放在任务栈的顶部
mSupervisor.moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason);
//向AMS发送binder请求,启动activity
startActivityLocked(null /*caller*/, intent, null /*ephemeralIntent*/,
null /*resolvedType*/, aInfo, null /*rInfo*/, 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 (mSupervisor.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.
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;
//FIXME: sometimes the gridview is empty; it seems tha data is lost
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();
//mGvAppsList.requestLayout();
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;
// disk file cache for app icon
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();
// only launchable app is added
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;
}

}

// take care of releasing resources
protected void onReleaseResources(List<AppItem> apps) {
// for cursor, we may want to close it
}

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();
}
// write to disk
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

/**
* an app item adapter to bind data to grid view
*/
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) {
// if use convertView, the binded data list seems to be wrong
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);
//TODO: icon size may be different for different apps
// keep the size the same
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

原文作者:Jason Wang

更新日期:2021-08-11, 15:11:49

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. Launcher的启动
  2. 2. 自定义Launcher