Activity组件之一Service--- startService
来源:互联网 发布:公司活动软文 知乎 编辑:程序博客网 时间:2024/05/01 21:15
关于Activity的四大组件之一Service, 这里面有几样东西可以分析呢?
1. 关于Service的启动,根据不同的需求可以分为有两种:一种是通过调用StartService,另一种就是BindService。
第一个话题,怎么去使用,然后这两种方法分别是怎么启动的,也就是说启动流程是怎么样的。(代码,UML图)
有什么区别呢?
2. 关于这两种 不同的方式,Service处理完成事务之后 怎么销毁?
3. 关于Service的ANR会在什么时候产生呢?
4. Service是时候被加载进AMS的,也就是说我要去start一个Service的时候,系统是怎么知道有没有这个Service呢?
第一,关于Service的声明还有如何加载到AMS中。
1. 首先Service必须要在应用的AndroidManifest.xml中申明
<manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application></manifest>
2. PackageManagerService如何去解析呢?
1) 当我们安装一个apk的时候,PMS会去解析新装的apk的package的一系列的信息。具体的过程需要debug查看代码,我们这里先指关注PackageParser.parsePackage()
private Package parsePackage( Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { ... ... while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (tagName.equals("application")) { if (foundApp) { ... ... foundApp = true; if (!parseApplication(pkg, res, parser, attrs, flags, outError)) { return null; } } else if (tagName.equals("permission")) { ... ... } else if (tagName.equals("permission-tree")) { ... ... } else if (tagName.equals("uses-permission")) { ... ... } return pkg; }
2) PackageParser.parseApplication()
在parse Manifest中的Application的标签时,如果我们在里面声明了Service就会通过 owner.services.add(s);把这个service添加到对应package的services数组中,然后PMS会去遍历所有的packages中的service,通过mServices.addService(s);把它加到PMS中的mServices的数组中。这样我们的Service便在PMS中有了记录。
在PMS中一个Package就对应一个应用程序,里面记录着apk相关的所有东西,包括package的名字,进程名,里面的Activity,service,receive等等。
private boolean parseApplication(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException, IOException { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { continue; } String tagName = parser.getName(); if (tagName.equals("activity")) { ... ... owner.activities.add(a); } else if (tagName.equals("receiver")) { ... ... owner.receivers.add(a); } else if (tagName.equals("service")) { Service s = parseService(owner, res, parser, attrs, flags, outError); if (s == null) { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return false; } owner.services.add(s);
第二,通过StartService()去启动一个Service
1. ContextImpl.StartService(Intent service)
StartService会直接去调用startServiceAsUser,而startServiceAsUser则会通过ActivityManagerNative通过binder去调用AMS的startService()
@Override public ComponentName startService(Intent service) { warnIfCallingFromSystemProcess(); return startServiceAsUser(service, mUser); }
@Override public ComponentName startServiceAsUser(Intent service, UserHandle user) { try { service.setAllowFds(false); ComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier()); ...... return cn; } catch (RemoteException e) { return null; } }
2 AMS.startService(IApplicationThread caller, Intent service, String resolvedType, int userId)
这个函数中caller就是一个binder对象,
servcie就是传过来的Intent
public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType, int userId) { synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); checkValidCaller(callingUid, userId); final long origId = Binder.clearCallingIdentity(); ComponentName res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, userId); Binder.restoreCallingIdentity(origId); return res; } }
3. ActiveServices.startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, int userId)
1) 首先根据caller去查找出与之对应的ProcessRecord对象 callerApp.
2) retrieveServiceLocked(service, resolvedType, callingPid, callingUid, userId, true); //
3) bringUpServiceLocked(r, service.getFlags(), false) //
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, int userId) { if (caller != null) { final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); } ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, callingPid, callingUid, userId, true); if (res == null) { return null; } ... ... ServiceRecord r = res.record; NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked( callingUid, r.packageName, service, service.getFlags(), null); if (unscheduleServiceRestartLocked(r)) { if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); } r.startRequested = true; r.callStart = false; r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, neededGrants)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); } String error = bringUpServiceLocked(r, service.getFlags(), false); if (error != null) { return new ComponentName("!!", error); } return r.name; }
3.1 ActiveServices.retrieveServiceLocked(Intent service, String resolvedType, int callingPid, int callingUid, int userId, boolean createIfNeeded)
1)先在mServiceMap中去查找是否它所在的进程里面已经有这个ServiceRecord了,如果有就直接取出来付给r.
如果传递过来的service是指定类名的,那么service.getComponent()就不等于null,于是就通过mServiceMap.getServiceByName(service.getComponent(), userId); 去寻找.
通过IntentFilter到userId对应的进程里面去查找是否已经有了。
2)如果mServiceMap中还不存在就去new一个新的ServiceRecord
首先会去通过AppGlobals.getPackageManager().resolveService()(service, resolvedType, ActivityManagerService.STOCK_PM_FLAGS, userId) 到PMS里去查找是有符合Intent的已经在PMS加载过的Service的信息。这个函数会返回一个ResolveInfo。
如果找到了对应的Service,就会new ServiceRecord(mAm, ss, name, filter, sInfo, res);并且把它放到mServiceMap说明当前已经有这个Service了,最后把找到的这个ServiceRecord封装到new ServiceLookupResult(r, null)中作为一个结果返回。
private ServiceLookupResult retrieveServiceLocked(Intent service, String resolvedType, int callingPid, int callingUid, int userId, boolean createIfNeeded) { ServiceRecord r = null; if (service.getComponent() != null) { r = mServiceMap.getServiceByName(service.getComponent(), userId); } if (r == null) { Intent.FilterComparison filter = new Intent.FilterComparison(service); r = mServiceMap.getServiceByIntent(filter, userId); } if (r == null) { try { ResolveInfo rInfo = AppGlobals.getPackageManager().resolveService( service, resolvedType, ActivityManagerService.STOCK_PM_FLAGS, userId); ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null; ... ... ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); if (userId > 0) { if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo, sInfo.name, sInfo.flags)) { userId = 0; } sInfo = new ServiceInfo(sInfo); sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId); } r = mServiceMap.getServiceByName(name, userId); if (r == null && createIfNeeded) { Intent.FilterComparison filter = new Intent.FilterComparison( service.cloneFilter()); ServiceRestarter res = new ServiceRestarter(); BatteryStatsImpl.Uid.Pkg.Serv ss = null; BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics(); synchronized (stats) { ss = stats.getServiceStatsLocked( sInfo.applicationInfo.uid, sInfo.packageName, sInfo.name); } r = new ServiceRecord(mAm, ss, name, filter, sInfo, res); res.setService(r); mServiceMap.putServiceByName(name, UserHandle.getUserId(r.appInfo.uid), r); mServiceMap.putServiceByIntent(filter, UserHandle.getUserId(r.appInfo.uid), r); // Make sure this component isn't in the pending list. int N = mPendingServices.size(); for (int i=0; i<N; i++) { ServiceRecord pr = mPendingServices.get(i); if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid && pr.name.equals(name)) { mPendingServices.remove(i); i--; N--; } } } } catch (RemoteException ex) { // pm is in same process, this will never happen. } } if (r != null) { if (mAm.checkComponentPermission(r.permission, callingPid, callingUid, r.appInfo.uid, r.exported) != PackageManager.PERMISSION_GRANTED) { if (!r.exported) { Slog.w(TAG, "Permission Denial: Accessing service " + r.name + " from pid=" + callingPid + ", uid=" + callingUid + " that is not exported from uid " + r.appInfo.uid); return new ServiceLookupResult(null, "not exported from uid " + r.appInfo.uid); } Slog.w(TAG, "Permission Denial: Accessing service " + r.name + " from pid=" + callingPid + ", uid=" + callingUid + " requires " + r.permission); return new ServiceLookupResult(null, r.permission); } return new ServiceLookupResult(r, null); } return null; }
3.3 ActiveServices.bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean whileRestarting)
获取ServiceRecord所在的application是否已经启动,如果启动了就直接调用realStartServiceLocked(r, app); 如果没有启动就先去mAm.startProcessLocked启动对应的process,然后把需要start的service添加到mPendingServices中,等到进程起来之后会调用attachApplicationLocked去启动相应的service,当然还是调用realStartServiceLocked(sr, proc);
private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean whileRestarting) { ... ... if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent); ... ... // Make sure that the user who owns this service is started. If not, // we don't want to allow it to run. if (mAm.mStartedUsers.get(r.userId) == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " + r.intent.getIntent() + ": user " + r.userId + " is stopped"; Slog.w(TAG, msg); bringDownServiceLocked(r, true); return msg; } final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; final String procName = r.processName; ProcessRecord app; if (!isolated) { app = mAm.getProcessRecordLocked(procName, r.appInfo.uid); if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app); if (app != null && app.thread != null) { try { app.addPackage(r.appInfo.packageName); realStartServiceLocked(r, app); return null; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting service " + r.shortName, e); } // If a dead object exception was thrown -- fall through to // restart the application. } } else { // If this service runs in an isolated process, then each time // we call startProcessLocked() we will get a new isolated // process, starting another process if we are currently waiting // for a previous process to come up. To deal with this, we store // in the service any current isolated process it is running in or // waiting to have come up. app = r.isolatedProc; } // Not running -- get it started, and enqueue this service record // to be executed when the app comes up. if (app == null) { if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, "service", r.name, false, isolated)) == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " + r.intent.getIntent() + ": process is bad"; Slog.w(TAG, msg); bringDownServiceLocked(r, true); return msg; } if (isolated) { r.isolatedProc = app; } } if (!mPendingServices.contains(r)) { mPendingServices.add(r); } return null; }
3.3.1 ActiveService.realStartServiceLocked(ServiceRecord r, ProcessRecord app)
1) app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo)); 通过app.thread.scheduleCreateService去调用ActivityThread的scheduleCreateService。在ActivityThread的中通过handle去发送和处理“CREATE_SERVICE”的消息。调用handleCreateService
2) requestServiceBindingLock() //这个函数在BindService的时候才会起作用
3) sendServiceArgsLocked(r, true); //处理传递给Service的Intent带来的参数
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app) throws RemoteException { r.app = app; r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); app.services.add(r); bumpServiceExecutingLocked(r, "create"); mAm.updateLruProcessLocked(app, true); boolean created = false; try { mAm.mStringBuilder.setLength(0); r.intent.getIntent().toShortString(mAm.mStringBuilder, true, false, true, false); EventLog.writeEvent(EventLogTags.AM_CREATE_SERVICE, r.userId, System.identityHashCode(r), r.shortName, mAm.mStringBuilder.toString(), r.app.pid); synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } mAm.ensurePackageDexOpt(r.serviceInfo.packageName); app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo)); r.postNotification(); created = true; } finally { ... ... } requestServiceBindingsLocked(r); // If the service is in the started state, and there are no // pending arguments, then fake up one so its onStartCommand() will // be called. if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), null, null)); } sendServiceArgsLocked(r, true); }
3.3.1.1 ActivityThread.handleCreateService()
在Client端,实例化对应的Service并通过attach,把相关的属性给它。通过mServices.put(data.token, service);把自己加到当前进程的mServices中。
此时就会调用service.onCreate(); 并且通过mServices.put(data.token, service); 把ActiveServices里面的ServiceRecord传过来的Token与本地Service对象放到mServices的Map中。
最后会通过ActivityManagerNative.getDefault().serviceDoneExecuting(token, 0, 0, 0);去通知ActiveServices。
private void handleCreateService(CreateServiceData data) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = (Service) cl.loadClass(data.info.name).newInstance(); } catch (Exception e) { ... ... } try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); ContextImpl context = new ContextImpl(); context.init(packageInfo, null, this); Application app = packageInfo.makeApplication(false, mInstrumentation); context.setOuterContext(service); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); service.onCreate(); mServices.put(data.token, service); try { ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, 0, 0, 0); } catch (RemoteException e) { // nothing to do. } } catch (Exception e) { ... ... } }
3.3.1.3 ActiveServices.sendServiceArgsLocked(ServiceRecord r,boolean oomAdjusted)
在startServiceLocked函数中bringupService之前,已经调用了r.pendingStarts.add去添加了一个pendingStarts,所以这里的pendingStarts.size()是大于0的。
1. 通过r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); 发送Message给Client端处理。
private final void sendServiceArgsLocked(ServiceRecord r, boolean oomAdjusted) { final int N = r.pendingStarts.size(); if (N == 0) { return; } while (r.pendingStarts.size() > 0) { try { ServiceRecord.StartItem si = r.pendingStarts.remove(0); si.deliveredTime = SystemClock.uptimeMillis(); r.deliveredStarts.add(si); si.deliveryCount++; if (si.neededGrants != null) { mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, si.getUriPermissionsLocked()); } int flags = 0; if (si.deliveryCount > 1) { flags |= Service.START_FLAG_RETRY; } if (si.doneExecutingCount > 0) { flags |= Service.START_FLAG_REDELIVERY; } r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); } catch (RemoteException e) { .... } }
3.3.1.3.1 ActivityThread.handleServiceArgs(ServiceArgsData data)
1. 调用s.onStartCommand去做相关的操作,onStartCommand会返回一个integer. The integer is a value that describes how the system should continue the service in the event that the system kills it.
2. 告诉AMS已经完成了ActivityManagerNative.getDefault().serviceDoneExecuting(data.token, 1, data.startId, res); 在ActiveServices.serviceDoneExecutingLocked中会对onStartCommand返回的值作出对应的处理。
private void handleServiceArgs(ServiceArgsData data) { Service s = mServices.get(data.token); if (s != null) { try { if (data.args != null) { data.args.setExtrasClassLoader(s.getClassLoader()); } int res; if (!data.taskRemoved) { res = s.onStartCommand(data.args, data.flags, data.startId); } else { s.onTaskRemoved(data.args); res = Service.START_TASK_REMOVED_COMPLETE; } QueuedWork.waitToFinish(); try { ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, 1, data.startId, res); } catch (RemoteException e) { // nothing to do. } ensureJitEnabled(); } catch (Exception e) { if (!mInstrumentation.onException(s, e)) { throw new RuntimeException( "Unable to start service " + s + " with " + data.args + ": " + e.toString(), e); } } } }
ActiveServices.serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res)
void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) { boolean inStopping = mStoppingServices.contains(r); if (r != null) { if (type == 1) { // This is a call from a service start... take care of // book-keeping. r.callStart = true; switch (res) { case Service.START_STICKY_COMPATIBILITY: case Service.START_STICKY: { // We are done with the associated start arguments. r.findDeliveredStart(startId, true); // Don't stop if killed. r.stopIfKilled = false; break; } case Service.START_NOT_STICKY: { // We are done with the associated start arguments. r.findDeliveredStart(startId, true); if (r.getLastStartId() == startId) { // There is no more work, and this service // doesn't want to hang around if killed. r.stopIfKilled = true; } break; } case Service.START_REDELIVER_INTENT: { // We'll keep this item until they explicitly // call stop for it, but keep track of the fact // that it was delivered. ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); if (si != null) { si.deliveryCount = 0; si.doneExecutingCount++; // Don't stop if killed. r.stopIfKilled = true; } break; } case Service.START_TASK_REMOVED_COMPLETE: { // Special processing for onTaskRemoved(). Don't // impact normal onStartCommand() processing. r.findDeliveredStart(startId, true); break; } default: throw new IllegalArgumentException( "Unknown service start result: " + res); } if (res == Service.START_STICKY_COMPATIBILITY) { r.callStart = false; } } final long origId = Binder.clearCallingIdentity(); serviceDoneExecutingLocked(r, inStopping); Binder.restoreCallingIdentity(origId); } else { Slog.w(TAG, "Done executing unknown service from pid " + Binder.getCallingPid()); } }
serviceDoneExecutingLocked(ServiceRecord r, boolean inStopping)
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inStopping) { if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r + ": nesting=" + r.executeNesting + ", inStopping=" + inStopping + ", app=" + r.app); else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName); r.executeNesting--; if (r.executeNesting <= 0 && r.app != null) { if (DEBUG_SERVICE) Slog.v(TAG, "Nesting at 0 of " + r.shortName); r.app.executingServices.remove(r); if (r.app.executingServices.size() == 0) { if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "No more executingServices of " + r.shortName); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); } if (inStopping) { if (DEBUG_SERVICE) Slog.v(TAG, "doneExecuting remove stopping " + r); mStoppingServices.remove(r); r.bindings.clear(); } mAm.updateOomAdjLocked(r.app); } }
到此StartService()启动结束,startService会返回一个ComponentName,而这个实际上就是ServiceRecord.name。
4. stopService
如果一个service通过start开始,想要destroy它只有两个方法,一个是通过调用自己的stopSelf(), 另一个就是通过ContextImpl.stopService(Intent service).
而这两个方法最终会走到ActiveServices的stopServiceTokenLocked 和 stopServiceLocked, 这两个方法都是去查找在ActivieServices中对应的ServiceRecord,找到之后最终调用bringDownServiceLocked去destroy相关的工作。
1. r.app.thread.scheduleStopService(r); 通过Binder调用Client端的销毁工作。
private final void bringDownServiceLocked(ServiceRecord r, boolean force) { ... ... if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent); EventLog.writeEvent(EventLogTags.AM_DESTROY_SERVICE, r.userId, System.identityHashCode(r), r.shortName, (r.app != null) ? r.app.pid : -1); mServiceMap.removeServiceByName(r.name, r.userId); mServiceMap.removeServiceByIntent(r.intent, r.userId); r.totalRestartCount = 0; unscheduleServiceRestartLocked(r); // Also make sure it is not on the pending list. int N = mPendingServices.size(); for (int i=0; i<N; i++) { if (mPendingServices.get(i) == r) { mPendingServices.remove(i); if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r); i--; N--; } } r.cancelNotification(); r.isForeground = false; r.foregroundId = 0; r.foregroundNoti = null; // Clear start entries. r.clearDeliveredStartsLocked(); r.pendingStarts.clear(); if (r.app != null) { synchronized (r.stats.getBatteryStats()) { r.stats.stopLaunchedLocked(); } r.app.services.remove(r); if (r.app.thread != null) { try { bumpServiceExecutingLocked(r, "stop"); mStoppingServices.add(r); mAm.updateOomAdjLocked(r.app); r.app.thread.scheduleStopService(r); } catch (Exception e) { Slog.w(TAG, "Exception when stopping service " + r.shortName, e); serviceDoneExecutingLocked(r, true); } updateServiceForegroundLocked(r.app, false); } else { if (DEBUG_SERVICE) Slog.v( TAG, "Removed service that has no process: " + r); } } else { if (DEBUG_SERVICE) Slog.v( TAG, "Removed service that is not running: " + r); } ... ... }
4.1 ActivityThread.handleStopService()
调用s.onDestroy()
private void handleStopService(IBinder token) { Service s = mServices.remove(token); if (s != null) { try { if (localLOGV) Slog.v(TAG, "Destroying service " + s); s.onDestroy(); Context context = s.getBaseContext(); if (context instanceof ContextImpl) { final String who = s.getClassName(); ((ContextImpl) context).scheduleFinalCleanup(who, "Service"); } try { ActivityManagerNative.getDefault().serviceDoneExecuting( token, 0, 0, 0); } catch (RemoteException e) { // nothing to do. } } catch (Exception e) { ... ... } } //Slog.i(TAG, "Running services: " + mServices); }
- Activity组件之一Service--- startService
- android四大组件之一Service的startService()与bindService()区别?
- Activity四大组件之Service 方式一startService
- Service组件 startService() bindService()
- Activity组件之一Service--- BindService & unBindService
- Android组件之Service之startService、bindService
- Android四大组件Service之StartService启动
- StartActivity的流程 Activity组件之一Service--- BindService & unBindService
- android Service(一) activity启动Service方式一:startService()
- android组件之一Service
- Service-四大组件之一
- 四大组件之一:Service
- Activity-四大组件之一
- Activity四大组件之一
- 四大组件之一:Activity
- 四大组件之一Activity
- 四大组件之一 ---Activity
- 四大组件之一Activity
- STL容器类vector,list,deque的比较
- VMware下,主机和虚拟机ping通IP设定问题 单向ping
- xcode 调试断点
- 关于javabean中get和set作用的疑问
- Activity组件
- Activity组件之一Service--- startService
- 用C++读写unicode文本
- Iphone代码片段导航
- svg参考文档
- linux下mysql用户的管理
- RVDS2.2:explicit type is missing ("int" assumed) register i
- 编译linux kernel及制作initrd ( by quqi99 )
- 删除n个数后最后剩下最小值
- Servlet学习——简单原理