Android技术精髓-理解Context [源码]
来源:互联网 发布:网络聊天软件有哪些 编辑:程序博客网 时间:2024/05/14 05:31
Android技术精髓-理解Context [源码]
Context:
Context类是一个抽象类,定义应用程序环境的全局信息,它允许访问应用程序特定的资源和类,以及最新的电话应用程序级别的操作,例如: launching activities, broadcasting and receiving intents 等操作!
图1:Context定义的抽象方法以及全局变量
举个例子:
Context抽象类定义了StartActivity的方法,而并非是我们所想的在Activity中定义。
Launch multiple new activities.
Same as
startActivities(Intent[], Bundle)
with no options specified.Same as
startActivity(Intent, Bundle)
with no options specified.Launch a new activity.
在Android源代码中此抽象方法定义如下:
public abstract void startActivity(Intent intent);
public abstract boolean stopService(Intent service); public abstract boolean bindService(Intent service, ServiceConnection conn, int flags); public abstract void unbindService(ServiceConnection conn);
总结下:
1、它描述的是一个应用程序环境的信息,即上下文。
2、该类是一个抽象(abstract class)类,Android提供了该抽象类的具体实现类(后面我们会讲到是ContextIml类)。
3、通过它我们可以获取应用程序的资源和类,也包括一些应用级别操作,例如:启动一个Activity,发送广播,接受Intent信息 等。
Context相关类的继承关系
ContextIml.java类:
该Context类的实现类为ContextIml,该类实现了Context类的功能。请注意,该函数的大部分功能都是直接调用其属性mPackageInfo去完成
源码:
/** * Common implementation of Context API, which provides the base * context object for Activity and other application components. */class ContextImpl extends Context { private final static String TAG = "ApplicationContext"; private final static boolean DEBUG = false; private final static boolean DEBUG_ICONS = false;
//以下选择部分function实现源码贴出
@Override public PackageManager getPackageManager() { if (mPackageManager != null) { return mPackageManager; } IPackageManager pm = ActivityThread.getPackageManager(); if (pm != null) { // Doesn't matter if we make more than one instance. return (mPackageManager = new ApplicationPackageManager(this, pm)); } return null; }
@Override public void setTheme(int resid) { mThemeResource = resid; } @Override public Resources.Theme getTheme() { if (mTheme == null) { if (mThemeResource == 0) { mThemeResource = com.android.internal.R.style.Theme; } mTheme = mResources.newTheme(); mTheme.applyStyle(mThemeResource, true); } return mTheme; }
private static File makeBackupFile(File prefsFile) { return new File(prefsFile.getPath() + ".bak"); } public File getSharedPrefsFile(String name) { return makeFilename(getPreferencesDir(), name + ".xml"); } @Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; File prefsFile; boolean needInitialLoad = false; synchronized (sSharedPrefs) { sp = sSharedPrefs.get(name); if (sp != null && !sp.hasFileChangedUnexpectedly()) { return sp; } prefsFile = getSharedPrefsFile(name); if (sp == null) { sp = new SharedPreferencesImpl(prefsFile, mode, null); sSharedPrefs.put(name, sp); needInitialLoad = true; } } synchronized (sp) { if (needInitialLoad && sp.isLoaded()) { // lost the race to load; another thread handled it return sp; } File backup = makeBackupFile(prefsFile); if (backup.exists()) { prefsFile.delete(); backup.renameTo(prefsFile); } // Debugging if (prefsFile.exists() && !prefsFile.canRead()) { Log.w(TAG, "Attempt to read preferences file " + prefsFile + " without permission"); } Map map = null; FileStatus stat = new FileStatus(); if (FileUtils.getFileStatus(prefsFile.getPath(), stat) && prefsFile.canRead()) { try { FileInputStream str = new FileInputStream(prefsFile); map = XmlUtils.readMapXml(str); str.close(); } catch (org.xmlpull.v1.XmlPullParserException e) { Log.w(TAG, "getSharedPreferences", e); } catch (FileNotFoundException e) { Log.w(TAG, "getSharedPreferences", e); } catch (IOException e) { Log.w(TAG, "getSharedPreferences", e); } } sp.replace(map, stat); } return sp; } private File getPreferencesDir() { synchronized (mSync) { if (mPreferencesDir == null) { mPreferencesDir = new File(getDataDirFile(), "shared_prefs"); } return mPreferencesDir; } } @Override public FileInputStream openFileInput(String name) throws FileNotFoundException { File f = makeFilename(getFilesDir(), name); return new FileInputStream(f); } @Override public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { final boolean append = (mode&MODE_APPEND) != 0; File f = makeFilename(getFilesDir(), name); try { FileOutputStream fos = new FileOutputStream(f, append); setFilePermissionsFromMode(f.getPath(), mode, 0); return fos; } catch (FileNotFoundException e) { } File parent = f.getParentFile(); parent.mkdir(); FileUtils.setPermissions( parent.getPath(), FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, -1, -1); FileOutputStream fos = new FileOutputStream(f, append); setFilePermissionsFromMode(f.getPath(), mode, 0); return fos; } @Override public boolean deleteFile(String name) { File f = makeFilename(getFilesDir(), name); return f.delete(); } @Override public File getFilesDir() { synchronized (mSync) { if (mFilesDir == null) { mFilesDir = new File(getDataDirFile(), "files"); } if (!mFilesDir.exists()) { if(!mFilesDir.mkdirs()) { Log.w(TAG, "Unable to create files directory"); return null; } FileUtils.setPermissions( mFilesDir.getPath(), FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, -1, -1); } return mFilesDir; } }
@Override public void startActivity(Intent intent) { if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?"); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1); }
@Override public void sendBroadcast(Intent intent, String receiverPermission) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, receiverPermission, false, false); } catch (RemoteException e) { } }
@Override public ComponentName startService(Intent service) { try { ComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver())); if (cn != null && cn.getPackageName().equals("!")) { throw new SecurityException( "Not allowed to start service " + service + " without permission " + cn.getClassName()); } return cn; } catch (RemoteException e) { return null; } } @Override public boolean stopService(Intent service) { try { int res = ActivityManagerNative.getDefault().stopService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver())); if (res < 0) { throw new SecurityException( "Not allowed to stop service " + service); } return res != 0; } catch (RemoteException e) { return false; } } @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { IServiceConnection sd; if (mPackageInfo != null) { sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), mMainThread.getHandler(), flags); } else { throw new RuntimeException("Not supported in system context"); } try { int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags); if (res < 0) { throw new SecurityException( "Not allowed to bind to service " + service); } return res != 0; } catch (RemoteException e) { return false; } } @Override public void unbindService(ServiceConnection conn) { if (mPackageInfo != null) { IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( getOuterContext(), conn); try { ActivityManagerNative.getDefault().unbindService(sd); } catch (RemoteException e) { } } else { throw new RuntimeException("Not supported in system context"); } } @Override public Object getSystemService(String name) { if (WINDOW_SERVICE.equals(name)) { return WindowManagerImpl.getDefault(); } else if (LAYOUT_INFLATER_SERVICE.equals(name)) { synchronized (mSync) { LayoutInflater inflater = mLayoutInflater; if (inflater != null) { return inflater; } mLayoutInflater = inflater = PolicyManager.makeNewLayoutInflater(getOuterContext()); return inflater; } } else if (ACTIVITY_SERVICE.equals(name)) { return getActivityManager(); } else if (INPUT_METHOD_SERVICE.equals(name)) { return InputMethodManager.getInstance(this); } else if (ALARM_SERVICE.equals(name)) { return getAlarmManager(); } else if (ACCOUNT_SERVICE.equals(name)) { return getAccountManager(); } else if (POWER_SERVICE.equals(name)) { return getPowerManager(); } else if (CONNECTIVITY_SERVICE.equals(name)) { return getConnectivityManager(); } else if (THROTTLE_SERVICE.equals(name)) { return getThrottleManager(); } else if (WIFI_SERVICE.equals(name)) { return getWifiManager(); } else if (NOTIFICATION_SERVICE.equals(name)) { return getNotificationManager(); } else if (KEYGUARD_SERVICE.equals(name)) { return new KeyguardManager(); } else if (ACCESSIBILITY_SERVICE.equals(name)) { return AccessibilityManager.getInstance(this); } else if (LOCATION_SERVICE.equals(name)) { return getLocationManager(); } else if (SEARCH_SERVICE.equals(name)) { return getSearchManager(); } else if (SENSOR_SERVICE.equals(name)) { return getSensorManager(); } else if (STORAGE_SERVICE.equals(name)) { return getStorageManager(); } else if (VIBRATOR_SERVICE.equals(name)) { return getVibrator(); } else if (STATUS_BAR_SERVICE.equals(name)) { synchronized (mSync) { if (mStatusBarManager == null) { mStatusBarManager = new StatusBarManager(getOuterContext()); } return mStatusBarManager; } } else if (AUDIO_SERVICE.equals(name)) { return getAudioManager(); } else if (TELEPHONY_SERVICE.equals(name)) { return getTelephonyManager(); } else if (CLIPBOARD_SERVICE.equals(name)) { return getClipboardManager(); } else if (WALLPAPER_SERVICE.equals(name)) { return getWallpaperManager(); } else if (DROPBOX_SERVICE.equals(name)) { return getDropBoxManager(); } else if (DEVICE_POLICY_SERVICE.equals(name)) { return getDevicePolicyManager(); } else if (UI_MODE_SERVICE.equals(name)) { return getUiModeManager(); } else if (DOWNLOAD_SERVICE.equals(name)) { return getDownloadManager(); } else if (NFC_SERVICE.equals(name)) { return getNfcManager(); } return null; }
ContextWrapper类 :
该类只是对Context类的一种包装,该类的构造函数包含了一个真正的Context引用,即ContextIml对象
源码:
public class ContextWrapper extends Context { Context mBase; public ContextWrapper(Context base) { mBase = base; } /** * Set the base context for this ContextWrapper. All calls will then be * delegated to the base context. Throws * IllegalStateException if a base context has already been set. * * @param base The new base context for this wrapper. */ protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; } /** * @return the base context as set by the constructor or setBaseContext */ public Context getBaseContext() { return mBase; } @Override public AssetManager getAssets() { return mBase.getAssets(); } @Override public Resources getResources() { return mBase.getResources(); } @Override public PackageManager getPackageManager() { return mBase.getPackageManager(); } @Override public ContentResolver getContentResolver() { return mBase.getContentResolver(); } @Override public Looper getMainLooper() { return mBase.getMainLooper(); } @Override public Context getApplicationContext() { return mBase.getApplicationContext(); } @Override public void setTheme(int resid) { mBase.setTheme(resid); } @Override public Resources.Theme getTheme() { return mBase.getTheme(); } @Override public ClassLoader getClassLoader() { return mBase.getClassLoader(); } @Override public String getPackageName() { return mBase.getPackageName(); } @Override public ApplicationInfo getApplicationInfo() { return mBase.getApplicationInfo(); } @Override public String getPackageResourcePath() { return mBase.getPackageResourcePath(); } @Override public String getPackageCodePath() { return mBase.getPackageCodePath(); } /** @hide */ @Override public File getSharedPrefsFile(String name) { return mBase.getSharedPrefsFile(name); } @Override public SharedPreferences getSharedPreferences(String name, int mode) { return mBase.getSharedPreferences(name, mode); } @Override public FileInputStream openFileInput(String name) throws FileNotFoundException { return mBase.openFileInput(name); } @Override public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { return mBase.openFileOutput(name, mode); } @Override public boolean deleteFile(String name) { return mBase.deleteFile(name);
ContextThemeWrapper类 :
该类内部包含了主题(Theme)相关的接口,即android:theme属性指定的。只有Activity需要主题,Service不需要主题, 所以Service直接继承于ContextWrapper类。
源码:
/** * A ContextWrapper that allows you to modify the theme from what is in the * wrapped context. */public class ContextThemeWrapper extends ContextWrapper { private Context mBase; private int mThemeResource; private Resources.Theme mTheme; private LayoutInflater mInflater; public ContextThemeWrapper() { super(null); } public ContextThemeWrapper(Context base, int themeres) { super(base); mBase = base; mThemeResource = themeres; } @Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(newBase); mBase = newBase; } @Override public void setTheme(int resid) { mThemeResource = resid; initializeTheme(); } @Override public Resources.Theme getTheme() { if (mTheme != null) { return mTheme; } if (mThemeResource == 0) { mThemeResource = com.android.internal.R.style.Theme; } initializeTheme(); return mTheme; } @Override public Object getSystemService(String name) { if (LAYOUT_INFLATER_SERVICE.equals(name)) { if (mInflater == null) { mInflater = LayoutInflater.from(mBase).cloneInContext(this); } return mInflater; } return mBase.getSystemService(name); } /** * Called by {@link #setTheme} and {@link #getTheme} to apply a theme * resource to the current Theme object. Can override to change the * default (simple) behavior. This method will not be called in multiple * threads simultaneously. * * @param theme The Theme object being modified. * @param resid The theme style resource being applied to <var>theme</var>. * @param first Set to true if this is the first time a style is being * applied to <var>theme</var>. */ protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) { theme.applyStyle(resid, true); } private void initializeTheme() { final boolean first = mTheme == null; if (first) { mTheme = getResources().newTheme(); Resources.Theme theme = mBase.getTheme(); if (theme != null) { mTheme.setTo(theme); } } onApplyThemeResource(mTheme, mThemeResource, first); }}
Activity类源码(部分):
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { private static final String TAG = "Activity";
public static long getInstanceCount() { return sInstanceCount; } /** Return the intent that started this activity. */ public Intent getIntent() { return mIntent; } /** * Change the intent returned by {@link #getIntent}. This holds a * reference to the given intent; it does not copy it. Often used in * conjunction with {@link #onNewIntent}. * * @param newIntent The new Intent object to return from getIntent * * @see #getIntent * @see #onNewIntent */ public void setIntent(Intent newIntent) { mIntent = newIntent; } /** Return the application that owns this activity. */ public final Application getApplication() { return mApplication; } /** Is this activity embedded inside of another activity? */ public final boolean isChild() { return mParent != null; } /** Return the parent activity if this view is an embedded child. */ public final Activity getParent() { return mParent; } /** Retrieve the window manager for showing custom windows. */ public WindowManager getWindowManager() { return mWindowManager; } /** * Called when the activity is starting. This is where most initialization * should go: calling {@link #setContentView(int)} to inflate the * activity's UI, using {@link #findViewById} to programmatically interact * with widgets in the UI, calling * {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve * cursors for data being displayed, etc. * * <p>You can call {@link #finish} from within this function, in * which case onDestroy() will be immediately called without any of the rest * of the activity lifecycle ({@link #onStart}, {@link #onResume}, * {@link #onPause}, etc) executing. * * <p><em>Derived classes must call through to the super class's * implementation of this method. If they do not, an exception will be * thrown.</em></p> * * @param savedInstanceState If the activity is being re-initialized after * previously being shut down then this Bundle contains the data it most * recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b> * * @see #onStart * @see #onSaveInstanceState * @see #onRestoreInstanceState * @see #onPostCreate */ protected void onCreate(Bundle savedInstanceState) { mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); mCalled = true; }
private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) { final Dialog dialog = onCreateDialog(dialogId, args); if (dialog == null) { return null; } dialog.dispatchOnCreate(state); return dialog; }
protected void onDestroy() { mCalled = true; // dismiss any dialogs we are managing. if (mManagedDialogs != null) { final int numDialogs = mManagedDialogs.size(); for (int i = 0; i < numDialogs; i++) { final ManagedDialog md = mManagedDialogs.valueAt(i); if (md.mDialog.isShowing()) { md.mDialog.dismiss(); } } mManagedDialogs = null; } // close any cursors we are managing. synchronized (mManagedCursors) { int numCursors = mManagedCursors.size(); for (int i = 0; i < numCursors; i++) { ManagedCursor c = mManagedCursors.get(i); if (c != null) { c.mCursor.close(); } } mManagedCursors.clear(); } // Close any open search dialog if (mSearchManager != null) { mSearchManager.stopSearch(); } }
/** * Called when a key was pressed down and not handled by any of the views * inside of the activity. So, for example, key presses while the cursor * is inside a TextView will not trigger the event (unless it is a navigation * to another object) because TextView handles its own key presses. * * <p>If the focused view didn't want this event, this method is called. * * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK} * by calling {@link #onBackPressed()}, though the behavior varies based * on the application compatibility mode: for * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications, * it will set up the dispatch to call {@link #onKeyUp} where the action * will be performed; for earlier applications, it will perform the * action immediately in on-down, as those versions of the platform * behaved. * * <p>Other additional default key handling may be performed * if configured with {@link #setDefaultKeyMode}. * * @return Return <code>true</code> to prevent this event from being propagated * further, or <code>false</code> to indicate that you have not handled * this event and it should continue to be propagated. * @see #onKeyUp * @see android.view.KeyEvent */ public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) { event.startTracking(); } else { onBackPressed(); } return true; } if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) { return false; } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) { if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) { return true; } return false; } else { // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_* boolean clearSpannable = false; boolean handled; if ((event.getRepeatCount() != 0) || event.isSystem()) { clearSpannable = true; handled = false; } else { handled = TextKeyListener.getInstance().onKeyDown( null, mDefaultKeySsb, keyCode, event); if (handled && mDefaultKeySsb.length() > 0) { // something useable has been typed - dispatch it now. final String str = mDefaultKeySsb.toString(); clearSpannable = true; switch (mDefaultKeyMode) { case DEFAULT_KEYS_DIALER: Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); break; case DEFAULT_KEYS_SEARCH_LOCAL: startSearch(str, false, null, false); break; case DEFAULT_KEYS_SEARCH_GLOBAL: startSearch(str, false, null, true); break; } } } if (clearSpannable) { mDefaultKeySsb.clear(); mDefaultKeySsb.clearSpans(); Selection.setSelection(mDefaultKeySsb,0); } return handled; } } /** * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent) * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle * the event). */ public boolean onKeyLongPress(int keyCode, KeyEvent event) { return false; } public boolean onKeyUp(int keyCode, KeyEvent event) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) { if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) { onBackPressed(); return true; } } return false; } /** * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle * the event). */ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return false; } /** * Called when the activity has detected the user's press of the back * key. The default implementation simply finishes the current activity, * but you can override this to do whatever you want. */ public void onBackPressed() { finish(); }
@Override public void startActivity(Intent intent) { startActivityForResult(intent, -1); }
public void startActivityFromChild(Activity child, Intent intent, int requestCode) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, child, intent, requestCode); if (ar != null) { mMainThread.sendActivityResult( mToken, child.mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } }
public void finish() { if (mParent == null) { int resultCode; Intent resultData; synchronized (this) { resultCode = mResultCode; resultData = mResultData; } if (Config.LOGV) Log.v(TAG, "Finishing self: token=" + mToken); try { if (ActivityManagerNative.getDefault() .finishActivity(mToken, resultCode, resultData)) { mFinished = true; } } catch (RemoteException e) { // Empty } } else { mParent.finishFromChild(this); } }
public void finishFromChild(Activity child) { finish(); } /** * Force finish another activity that you had previously started with * {@link #startActivityForResult}. * * @param requestCode The request code of the activity that you had * given to startActivityForResult(). If there are multiple * activities started with this request code, they * will all be finished. */ public void finishActivity(int requestCode) { if (mParent == null) { try { ActivityManagerNative.getDefault() .finishSubActivity(mToken, mEmbeddedID, requestCode); } catch (RemoteException e) { // Empty } } else { mParent.finishActivityFromChild(this, requestCode); } } {
1 0
- Android技术精髓-理解Context [源码]
- Android Context 源码的理解
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context (转)
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- Android源码分析-全面理解Context
- LeetCode 148 — Sort List(C++ Java Python)
- mysql数据库的安装
- Java容器类List、ArrayList、Vector及map、HashTable、HashMap的区别与用法
- 胖子伤不起
- XML与HTML的区别
- Android技术精髓-理解Context [源码]
- PAT 1034. Head of a Gang (30)
- 程序员应该去的网站
- MySql数据库 快速入门
- 修改WINDOWS远程控制的默认端口
- 转-如何看懂源代码(上)
- 关于mysql中无法显示中文的完美解决方案
- 递归思想(一)
- 转-如何读懂源码(下)