一、Application启动过程分析:
一个app启动的入口就是这个ActivityThread,ctivityThread方法有一个main方法,就是程序的入口了。
public final class ActivityThread {
final ApplicationThread mAppThread = new ApplicationThread();
final ApplicationThread mAppThread = new ApplicationThread();
final Looper mLooper = Looper.myLooper();
final H mH = new H();
public static void main(String[] args) {
6084 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
6085 SamplingProfilerIntegration.start();
6086
6087 // CloseGuard defaults to true and can be quite spammy. We
6088 // disable it here, but selectively enable it later (via
6089 // StrictMode) on debug builds, but using DropBox, not logs.
6090 CloseGuard.setEnabled(false);
6091
6092 Environment.initForCurrentUser();
6093
6094 // Set the reporter for event logging in libcore
6095 EventLogger.setReporter(new EventLoggingReporter());
6096
6097 // Make sure TrustedCertificateStore looks in the right place for CA certificates
6098 final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
6099 TrustedCertificateStore.setDefaultUserDirectory(configDir);
6100
6101 Process.setArgV0("<pre-initialized>");
6102
//(1)初始化主线程的Looper
6103 Looper.prepareMainLooper();
6104
//(2)创建一个ActivityThread对象
6105 ActivityThread thread = new ActivityThread();
//(3)调用attach()方法
6106 thread.attach(false);
6107
//获取 ActivityThread.mH Handler
6108 if (sMainThreadHandler == null) {
6109 sMainThreadHandler = thread.getHandler();
6110 }
6111
6112 if (false) {
6113 Looper.myLooper().setMessageLogging(new
6114 LogPrinter(Log.DEBUG, "ActivityThread"));
6115 }
6116
6117 // End of event ActivityThreadMain.
6118 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//(4线程 loop开启消息循环。
6119 Looper.loop();
6120
6121 throw new RuntimeException("Main thread loop unexpectedly exited");
6122 }
6123}
}
ActivityThread的main方法,主要做了一下几件事情:
1、初始化主线程Looper
//(1)初始化主线程的Looper
6103 Looper.prepareMainLooper();
2、创建一个ActivityThread实例
//(2)创建一个ActivityThread对象
6105 ActivityThread thread = new ActivityThread();
3、调用ActivityThread.attach()方法
//(3)调用attach()方法
6106 thread.attach(false);
4、Loop.loop() 消息无限循环。
其中 主要的操作在 thread.attach(false);中。
private void attach(boolean system) {
//初始化sCurrentActivityThread 实例
5931 sCurrentActivityThread = this;
5932 mSystemThread = system;
5933 if (!system) {
5934
5940
5943 final IActivityManager mgr = ActivityManagerNative.getDefault();
5944 try {
5945 mgr.attachApplication(mAppThread);
5946 } catch (RemoteException ex) {
5947 throw ex.rethrowFromSystemServer();
5948 }
5949
5971 } else {
5972
5976 try {
5977 mInstrumentation = new Instrumentation();
5978 ContextImpl context = ContextImpl.createAppContext(
5979 this, getSystemContext().mPackageInfo);
5980 mInitialApplication = context.mPackageInfo.makeApplication(true, null);
5981 mInitialApplication.onCreate();
5982 } catch (Exception e) {
5983 throw new RuntimeException(
5984 "Unable to instantiate Application():" + e.toString(), e);
5985 }
5986 }
5987
6019
6020 }
6021
attach中的主要操作为绑定Application的操作
final IActivityManager mgr = ActivityManagerNative.getDefault();
5944 try {
5945 mgr.attachApplication(mAppThread);
5946 } catch (RemoteException ex) {
5947 throw ex.rethrowFromSystemServer();
5948 }
通过Bind 机制,最终会调用ActivityManagerService(AMS)中的
attachApplication()方法
public final class ActivityManagerService extends ActivityManagerNative{
@Override
6667 public final void attachApplication(IApplicationThread thread) {
6668 synchronized (this) {
6669 int callingPid = Binder.getCallingPid();
6670 final long origId = Binder.clearCallingIdentity();
6671 attachApplicationLocked(thread, callingPid);
6672 Binder.restoreCallingIdentity(origId);
6673 }
6674 }
}
attachApplication()方法中 主要调用了attachApplicationLocked()方法。我们接着看AMS中attachApplicationLocked 做了哪些事情:
6431 private final boolean attachApplicationLocked(IApplicationThread thread,
6432 int pid) {
6433
6434 // Find the application record that is being attached... either via
6435 // the pid if we are running in multiple processes, or just pull the
6436 // next app record if we are emulating process with anonymous threads.
6437 ProcessRecord app;
6438 if (pid != MY_PID && pid >= 0) {
6439 synchronized (mPidsSelfLocked) {
6440 app = mPidsSelfLocked.get(pid);
6441 }
6442 } else {
6443 app = null;
6444 }
6445
6446 if (app == null) {
6447 Slog.w(TAG, "No pending application record for pid " + pid
6448 + " (IApplicationThread " + thread + "); dropping process");
6449 EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
6450 if (pid > 0 && pid != MY_PID) {
6451 Process.killProcessQuiet(pid);
6452 //TODO: killProcessGroup(app.info.uid, pid);
6453 } else {
6454 try {
6455 thread.scheduleExit();
6456 } catch (Exception e) {
6457 // Ignore exceptions.
6458 }
6459 }
6460 return false;
6461 }
6462
6463 // If this application record is still attached to a previous
6464 // process, clean it up now.
6465 if (app.thread != null) {
6466 handleAppDiedLocked(app, true, true);
6467 }
6468
6469 // Tell the process all about itself.
6470
6471 if (DEBUG_ALL) Slog.v(
6472 TAG, "Binding process pid " + pid + " to record " + app);
6473
6474 final String processName = app.processName;
6475 try {
6476 AppDeathRecipient adr = new AppDeathRecipient(
6477 app, pid, thread);
6478 thread.asBinder().linkToDeath(adr, 0);
6479 app.deathRecipient = adr;
6480 } catch (RemoteException e) {
6481 app.resetPackageList(mProcessStats);
6482 startProcessLocked(app, "link fail", processName);
6483 return false;
6484 }
6485
6486 EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);
6487
6488 app.makeActive(thread, mProcessStats);
6489 app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;
6490 app.curSchedGroup = app.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
6491 app.forcingToForeground = null;
6492 updateProcessForegroundLocked(app, false, false);
6493 app.hasShownUi = false;
6494 app.debugging = false;
6495 app.cached = false;
6496 app.killedByAm = false;
6497
6498 // We carefully use the same state that PackageManager uses for
6499 // filtering, since we use this flag to decide if we need to install
6500 // providers when user is unlocked later
6501 app.unlocked = StorageManager.isUserKeyUnlocked(app.userId);
6502
6503 mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
6504
6505 boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
6506 List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
6507
6508 if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
6509 Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
6510 msg.obj = app;
6511 mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
6512 }
6513
6514 if (!normalMode) {
6515 Slog.i(TAG, "Launching preboot mode app: " + app);
6516 }
6517
6518 if (DEBUG_ALL) Slog.v(
6519 TAG, "New app record " + app
6520 + " thread=" + thread.asBinder() + " pid=" + pid);
6521 try {
6522 int testMode = IApplicationThread.DEBUG_OFF;
6523 if (mDebugApp != null && mDebugApp.equals(processName)) {
6524 testMode = mWaitForDebugger
6525 ? IApplicationThread.DEBUG_WAIT
6526 : IApplicationThread.DEBUG_ON;
6527 app.debugging = true;
6528 if (mDebugTransient) {
6529 mDebugApp = mOrigDebugApp;
6530 mWaitForDebugger = mOrigWaitForDebugger;
6531 }
6532 }
6533 String profileFile = app.instrumentationProfileFile;
6534 ParcelFileDescriptor profileFd = null;
6535 int samplingInterval = 0;
6536 boolean profileAutoStop = false;
6537 if (mProfileApp != null && mProfileApp.equals(processName)) {
6538 mProfileProc = app;
6539 profileFile = mProfileFile;
6540 profileFd = mProfileFd;
6541 samplingInterval = mSamplingInterval;
6542 profileAutoStop = mAutoStopProfiler;
6543 }
6544 boolean enableTrackAllocation = false;
6545 if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
6546 enableTrackAllocation = true;
6547 mTrackAllocationApp = null;
6548 }
6549
6550 // If the app is being launched for restore or full backup, set it up specially
6551 boolean isRestrictedBackupMode = false;
6552 if (mBackupTarget != null && mBackupAppName.equals(processName)) {
6553 isRestrictedBackupMode = mBackupTarget.appInfo.uid >= Process.FIRST_APPLICATION_UID
6554 && ((mBackupTarget.backupMode == BackupRecord.RESTORE)
6555 || (mBackupTarget.backupMode == BackupRecord.RESTORE_FULL)
6556 || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL));
6557 }
6558
6559 if (app.instrumentationClass != null) {
6560 notifyPackageUse(app.instrumentationClass.getPackageName(),
6561 PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION);
6562 }
6563 if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc "
6564 + processName + " with config " + mConfiguration);
6565 ApplicationInfo appInfo = app.instrumentationInfo != null
6566 ? app.instrumentationInfo : app.info;
6567 app.compat = compatibilityInfoForPackageLocked(appInfo);
6568 if (profileFd != null) {
6569 profileFd = profileFd.dup();
6570 }
6571 ProfilerInfo profilerInfo = profileFile == null ? null
6572 : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
6573 thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
6574 profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
6575 app.instrumentationUiAutomationConnection, testMode,
6576 mBinderTransactionTrackingEnabled, enableTrackAllocation,
6577 isRestrictedBackupMode || !normalMode, app.persistent,
6578 new Configuration(mConfiguration), app.compat,
6579 getCommonServicesLocked(app.isolated),
6580 mCoreSettingsObserver.getCoreSettingsLocked());
6581 updateLruProcessLocked(app, false, null);
6582 app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
6583 } catch (Exception e) {
6584 // todo: Yikes! What should we do? For now we will try to
6585 // start another process, but that could easily get us in
6586 // an infinite loop of restarting processes...
6587 Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
6588
6589 app.resetPackageList(mProcessStats);
6590 app.unlinkDeathRecipient();
6591 startProcessLocked(app, "bind fail", processName);
6592 return false;
6593 }
6594
6595 // Remove this record from the list of starting applications.
6596 mPersistentStartingProcesses.remove(app);
6597 if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
6598 "Attach application locked removing on hold: " + app);
6599 mProcessesOnHold.remove(app);
6600
6601 boolean badApp = false;
6602 boolean didSomething = false;
6603
6604 // See if the top visible activity is waiting to run in this process...
6605 if (normalMode) {
6606 try {
6607 if (mStackSupervisor.attachApplicationLocked(app)) {
6608 didSomething = true;
6609 }
6610 } catch (Exception e) {
6611 Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
6612 badApp = true;
6613 }
6614 }
6615
6616 // Find any services that should be running in this process...
6617 if (!badApp) {
6618 try {
6619 didSomething |= mServices.attachApplicationLocked(app, processName);
6620 } catch (Exception e) {
6621 Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
6622 badApp = true;
6623 }
6624 }
6625
6626 // Check if a next-broadcast receiver is in this process...
6627 if (!badApp && isPendingBroadcastProcessLocked(pid)) {
6628 try {
6629 didSomething |= sendPendingBroadcastsLocked(app);
6630 } catch (Exception e) {
6631 // If the app died trying to launch the receiver we declare it 'bad'
6632 Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
6633 badApp = true;
6634 }
6635 }
6636
6637 // Check whether the next backup agent is in this process...
6638 if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.uid) {
6639 if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
6640 "New app is backup target, launching agent for " + app);
6641 notifyPackageUse(mBackupTarget.appInfo.packageName,
6642 PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
6643 try {
6644 thread.scheduleCreateBackupAgent(mBackupTarget.appInfo,
6645 compatibilityInfoForPackageLocked(mBackupTarget.appInfo),
6646 mBackupTarget.backupMode);
6647 } catch (Exception e) {
6648 Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
6649 badApp = true;
6650 }
6651 }
6652
6653 if (badApp) {
6654 app.kill("error during init", true);
6655 handleAppDiedLocked(app, false, true);
6656 return false;
6657 }
6658
6659 if (!didSomething) {
6660 updateOomAdjLocked();
6661 }
6662
6663 return true;
6664 }
核心方法是ApplicationThread中的bindApplication()方法,如下:
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
6574 profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
6575 app.instrumentationUiAutomationConnection, testMode,
6576 mBinderTransactionTrackingEnabled, enableTrackAllocation,
6577 isRestrictedBackupMode || !normalMode, app.persistent,
6578 new Configuration(mConfiguration), app.compat,
6579 getCommonServicesLocked(app.isolated),
6580 mCoreSettingsObserver.getCoreSettingsLocked());
6581 updateLruProcessLocked(app, false, null);
我们知道ApplicationThread是ActivityThread的一个内部类
647 private class ApplicationThread extends ApplicationThreadNative {
648 private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
649
650 private int mLastProcessState = -1;
651
public final void bindApplication(String processName, ApplicationInfo appInfo,
853 List<ProviderInfo> providers, ComponentName instrumentationName,
854 ProfilerInfo profilerInfo, Bundle instrumentationArgs,
855 IInstrumentationWatcher instrumentationWatcher,
856 IUiAutomationConnection instrumentationUiConnection, int debugMode,
857 boolean enableBinderTracking, boolean trackAllocation,
858 boolean isRestrictedBackupMode, boolean persistent, Configuration config,
859 CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {
860
861 if (services != null) {
862 // Setup the service cache in the ServiceManager
863 ServiceManager.initServiceCache(services);
864 }
865
866 setCoreSettings(coreSettings);
867
868 AppBindData data = new AppBindData();
869 data.processName = processName;
870 data.appInfo = appInfo;
871 data.providers = providers;
872 data.instrumentationName = instrumentationName;
873 data.instrumentationArgs = instrumentationArgs;
874 data.instrumentationWatcher = instrumentationWatcher;
875 data.instrumentationUiAutomationConnection = instrumentationUiConnection;
876 data.debugMode = debugMode;
877 data.enableBinderTracking = enableBinderTracking;
878 data.trackAllocation = trackAllocation;
879 data.restrictedBackupMode = isRestrictedBackupMode;
880 data.persistent = persistent;
881 data.config = config;
882 data.compatInfo = compatInfo;
883 data.initProfilerInfo = profilerInfo;
884 sendMessage(H.BIND_APPLICATION, data);
885 }
709}
bindApplication中 做了一些参数的复制操作到AppBindData参数,最终要的是最后一句话 sendMessage(H.BIND_APPLICATION, data); 最终向ActivityThread中的H Handler发送了一个消息。
private class H extends Handler {
public void handleMessage(Message msg) {
case BIND_APPLICATION:
1543 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
1544 AppBindData data = (AppBindData)msg.obj;
1545 handleBindApplication(data);
1546 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
1547 break;
}
}
我们来看一下handleBindApplication 做了哪些事情
private void handleBindApplication(AppBindData data) {
// Instrumentation info affects the class loader, so load it before
5272 // setting up the app context.
5273 final InstrumentationInfo ii;
5274
5293
5294 final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
5
5320
5327//通过反射初始化一个Instrumentation仪表。
5328 // Continue loading instrumentation.
5329
5330 final ApplicationInfo instrApp = new ApplicationInfo();
5331 ii.copyTo(instrApp);
5332 instrApp.initForUser(UserHandle.myUserId());
5333 final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
5334 appContext.getClassLoader(), false, true, false);
5335 final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
5336
5337 try {
5338 final ClassLoader cl = instrContext.getClassLoader();
5339 mInstrumentation = (Instrumentation)
5340 cl.loadClass(data.instrumentationName.getClassName()).newInstance();
5341 } catch (Exception e) {
5342 throw new RuntimeException(
5343 "Unable to instantiate instrumentation "
5344 + data.instrumentationName + ": " + e.toString(), e);
5345 }
5346
5347 final ComponentName component = new ComponentName(ii.packageName, ii.name);
5348 mInstrumentation.init(this, instrContext, appContext, component,
5349 data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
5350
5351 //通过LoadedApp命令创建Application实例
5361 // If the app is being launched for full backup or restore, bring it up in
5376 // a restricted environment with the base application class.
5377 Application app = data.info.makeApplication(data.restrictedBackupMode, null);
5378 mInitialApplication = app;
5379
5380 // don't bring up providers in restricted mode; they may depend on the
5381
5390
5391 // Do this after providers, since instrumentation tests generally start their
5392 // test thread at this point, and we don't want that racing.
5402 try {
5403 //让仪器调用Application的onCreate()方法
mInstrumentation.callApplicationOnCreate(app);
5404 } catch (Exception e) {
5405 if (!mInstrumentation.onException(app, e)) {
5406 throw new RuntimeException(
5407 "Unable to create application " + app.getClass().getName()
5408 + ": " + e.toString(), e);
5409 }
5410 }
5411 }
5414 }
- 根据loadedApk 调用 ContextImpl.createAppContext 创建了ContextImpl 对象,资源文件的读取操作都依赖于该ContextImpl 对象。
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
- 根据ContextImpl 的classLoader,加载Instrumentation 仪表盘类,利用返回实例化一个Instrumentation对象。
final ApplicationInfo instrApp = new ApplicationInfo();
5331 ii.copyTo(instrApp);
5332 instrApp.initForUser(UserHandle.myUserId());
5333 final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
5334 appContext.getClassLoader(), false, true, false);
5335 final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
5336
5337 try {
5338 final ClassLoader cl = instrContext.getClassLoader();
5339 mInstrumentation = (Instrumentation)
5340 cl.loadClass(data.instrumentationName.getClassName()).newInstance();
5341 } catch (Exception e) {
5342 throw new RuntimeException(
5343 "Unable to instantiate instrumentation "
5344 + data.instrumentationName + ": " + e.toString(), e);
5345 }
5346
5347 final ComponentName component = new ComponentName(ii.packageName, ii.name);
5348 mInstrumentation.init(this, instrContext, appContext, component,
5349 data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
Android instrumentation是Android系统里面的一套控制方法或者”钩子“。这些钩子可以在正常的生命周期(正常是由操作系统控制的)之外控制Android控件的运行。其实指的就是Instrumentation类提供的各种流程控制方法.
然后我们继续发现callApplicationOnCreate要一个app参数,
这个app是从哪里来的?继续像上看代码发现app产自这么一个方法makeApplication()
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
}
发现是由一个叫LoadedApk的类去执行的,这个类是维护这本地已经加载的apk。如果有这个app就直接返回,没有就用反射创建一个,创建过程又交给了仪表盘mInstrumentation。
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
简单的利用反射创建了application类,并调用了Application的attach()方法。
Application类中attach()方法 会触发 attachBaseContext(context);的调用
/**
186 * @hide
187 */
188 /* package */ final void attach(Context context) {
189 attachBaseContext(context);
190 mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
191 }
192
这就是我们经常见到的attachBaseContext()
public class ContextWrapper extends Context
protected void attachBaseContext(Context base) {
66 if (mBase != null) {
67 throw new IllegalStateException("Base context already set");
68 }
69 mBase = base;
70 }
}
71
至此Application的attachBaseContext()方法已经别正确调用。
我们接着看H Handler中 handleBindApplication中的
mInstrumentation.callApplicationOnCreate(app);方法
public class Instrumentation {
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
}
直接调用了 Application的onCreate()方法
二、知识点小结
1、 Applicatio的启动过程
从ActivityThread 的main()方法,
->到LoadedApk调用makeApplication()创建Application对象(实际上是Instrumentation创建的)
->再到Application类调用attach(),
->再到attachBaseContext()回调,
->最后调用Application类的onCreate()方法。
一个应用程序Application就被启动起来了。
2、ActivityThread中主要的成员和信息
ActivityThread 是android程序的主入口。它内部包含了几个主要的内部类
- final Looper mLooper = Looper.myLooper(); 主线程Looper
- final H mH = new H(); 主线程消息循环的Handler,负责处理所有的消息事件。
- final ApplicationThread mAppThread = new ApplicationThread(); ApplicationThread 类实现了以schedule***开头的操作Activity生命周期的回调函数。而这些回调函数中 大多会以 sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);的形式 传递到H handler中来处理。
网友评论