1. 程式人生 > >Android原始碼解析(二十七)-->HOME事件流程

Android原始碼解析(二十七)-->HOME事件流程

上一篇文章中我們介紹了android系統的截圖事件,由於截圖事件是一種系統全域性處理事件,所以事件的處理邏輯不是在App中執行,而是在PhoneWindowManager中執行。而本文我們現在主要講解android系統中HOME按鍵的事件處理,和截圖事件類似,這裡的HOME按鍵也是系統級別的按鍵事件監聽,所以其處理事件的邏輯也應該和截圖事件處理流程類似,從上一篇文章的分析過沖中我們不難發現,系統級別的按鍵處理邏輯其實都是在PhoneWindowManager中,所以HOME按鍵的處理邏輯也是在PhoneWindowManager的dispatchUnhandledKey方法中執行,那麼我們就從dispatchUnhandleKey方法開始分析HOME按鍵的處理流程。

好吧我們看一下PhoneWindowManager的dispatchUnhandleKey方法的實現:

@Override
    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
        ...
        KeyEvent fallbackEvent = null;
        if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
            final KeyCharacterMap kcm = event
.getKeyCharacterMap(); final int keyCode = event.getKeyCode(); final int metaState = event.getMetaState(); final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0; // Check for fallback actions specified by the key character map.
final FallbackAction fallbackAction; if (initialDown) { fallbackAction = kcm.getFallbackAction(keyCode, metaState); } else { fallbackAction = mFallbackActions.get(keyCode); } if (fallbackAction != null) { if (DEBUG_INPUT) { Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode + " metaState=" + Integer.toHexString(fallbackAction.metaState)); } final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK; fallbackEvent = KeyEvent.obtain( event.getDownTime(), event.getEventTime(), event.getAction(), fallbackAction.keyCode, event.getRepeatCount(), fallbackAction.metaState, event.getDeviceId(), event.getScanCode(), flags, event.getSource(), null); if (!interceptFallback(win, fallbackEvent, policyFlags)) { fallbackEvent.recycle(); fallbackEvent = null; } if (initialDown) { mFallbackActions.put(keyCode, fallbackAction); } else if (event.getAction() == KeyEvent.ACTION_UP) { mFallbackActions.remove(keyCode); fallbackAction.recycle(); } } } if (DEBUG_INPUT) { if (fallbackEvent == null) { Slog.d(TAG, "No fallback."); } else { Slog.d(TAG, "Performing fallback: " + fallbackEvent); } } return fallbackEvent; }

通過檢視原始碼,我們重點看一下dispatchUnhandledKey方法中呼叫的interceptFallback方法,關於HOME按鍵的處理邏輯也是在這個方法體中的,所以繼續看一下interceptFallback方法的實現:

private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
        int actions = interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
        if ((actions & ACTION_PASS_TO_USER) != 0) {
            long delayMillis = interceptKeyBeforeDispatching(
                    win, fallbackEvent, policyFlags);
            if (delayMillis == 0) {
                return true;
            }
        }
        return false;
    }

通過分析原始碼我們知道關於HOME按鍵的處理邏輯主要是在interceptKeyBeforeDispatching方法的實現的,既然這樣,我們看一下interceptKeyBeforeDispatching方法的實現:

@Override
    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
        ...
        // First we always handle the home key here, so applications
        // can never break it, although if keyguard is on, we do let
        // it handle it, because that gives us the correct 5 second
        // timeout.
        if (keyCode == KeyEvent.KEYCODE_HOME) {

            // If we have released the home key, and didn't do anything else
            // while it was pressed, then it is time to go home!
            if (!down) {
                cancelPreloadRecentApps();

                mHomePressed = false;
                if (mHomeConsumed) {
                    mHomeConsumed = false;
                    return -1;
                }

                if (canceled) {
                    Log.i(TAG, "Ignoring HOME; event canceled.");
                    return -1;
                }

                // If an incoming call is ringing, HOME is totally disabled.
                // (The user is already on the InCallUI at this point,
                // and his ONLY options are to answer or reject the call.)
                TelecomManager telecomManager = getTelecommService();
                if (telecomManager != null && telecomManager.isRinging()) {
                    Log.i(TAG, "Ignoring HOME; there's a ringing incoming call.");
                    return -1;
                }

                // Delay handling home if a double-tap is possible.
                if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {
                    mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case
                    mHomeDoubleTapPending = true;
                    mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,
                            ViewConfiguration.getDoubleTapTimeout());
                    return -1;
                }

                handleShortPressOnHome();
                return -1;
            }

            // If a system window has focus, then it doesn't make sense
            // right now to interact with applications.
            WindowManager.LayoutParams attrs = win != null ? win.getAttrs() : null;
            if (attrs != null) {
                final int type = attrs.type;
                if (type == WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM
                        || type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
                        || (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                    // the "app" is keyguard, so give it the key
                    return 0;
                }
                final int typeCount = WINDOW_TYPES_WHERE_HOME_DOESNT_WORK.length;
                for (int i=0; i<typeCount; i++) {
                    if (type == WINDOW_TYPES_WHERE_HOME_DOESNT_WORK[i]) {
                        // don't do anything, but also don't pass it to the app
                        return -1;
                    }
                }
            }

            // Remember that home is pressed and handle special actions.
            if (repeatCount == 0) {
                mHomePressed = true;
                if (mHomeDoubleTapPending) {
                    mHomeDoubleTapPending = false;
                    mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable);
                    handleDoubleTapOnHome();
                } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI
                        || mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {
                    preloadRecentApps();
                }
            } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
                if (!keyguardOn) {
                    handleLongPressOnHome(event.getDeviceId());
                }
            }
            return -1;
        }

        // Let the application handle the key.
        return 0;
    }

這裡我們主要看一下對android系統HOME按鍵的處理邏輯,通過分析原始碼我們知道HOME按鍵進入launcher介面的主要邏輯是在handleShortPressOnHome();方法中執行的,所以我們繼續看一下handleShortPressOnHome方法的實現。

private void handleShortPressOnHome() {
        // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
        getHdmiControl().turnOnTv();

        // If there's a dream running then use home to escape the dream
        // but don't actually go home.
        if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
            mDreamManagerInternal.stopDream(false /*immediate*/);
            return;
        }

        // Go home!
        launchHomeFromHotKey();
    }

可以看到在handleShortPressOnHome方法中呼叫了launchHomeFromHotKey方法,該方法的註釋用於go home,所以繼續看一下該方法的實現:

void launchHomeFromHotKey() {
        launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/);
    }

可以看到在launchHomeFromHotKey方法中我們又呼叫了launchHomeFromHotkey的重構方法,這樣我們看一下這個重構方法的實現。

void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
        if (respectKeyguard) {
            if (isKeyguardShowingAndNotOccluded()) {
                // don't launch home if keyguard showing
                return;
            }

            if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
                // when in keyguard restricted mode, must first verify unlock
                // before launching home
                mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
                    @Override
                    public void onKeyguardExitResult(boolean success) {
                        if (success) {
                            try {
                                ActivityManagerNative.getDefault().stopAppSwitches();
                            } catch (RemoteException e) {
                            }
                            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
                            startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
                        }
                    }
                });
                return;
            }
        }

        // no keyguard stuff to worry about, just launch home!
        try {
            ActivityManagerNative.getDefault().stopAppSwitches();
        } catch (RemoteException e) {
        }
        if (mRecentsVisible) {
            // Hide Recents and notify it to launch Home
            if (awakenFromDreams) {
                awakenDreams();
            }
            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
            hideRecentApps(false, true);
        } else {
            // Otherwise, just launch Home
            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
            startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
        }
    }

可以發現在方法中我們首先呼叫了ActivityManagerNative.getDefault().stopAppSwitches();該方法主要用於暫停後臺的開啟Activity的操作,避免打擾使用者的操作。比如這時候我們在後臺開啟一個新的App,那麼這時候由於要回到home頁面,所以需要先延時開啟。方法執行這個方法之後然後執行了sendCloseSystemWindows方法,該方法主要實現了對當前系統App頁面的關閉操作,下面我們先看一下ActivityManagerNative.getDefault().stopAppSwitches();方法的實現,這裡的ActivityManagerNative.getDefault我們在前面已經多次說過了其是一個Binder物件,是應用程序Binder客戶端用於與ActivityManagerService之間通訊,所以這裡最終呼叫的是ActivityManagerService的stopAppsSwitches方法,這樣我們就繼續看一下ActivityManagerService的stopAppsSwitches方法的實現。

@Override
    public void stopAppSwitches() {
        if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Requires permission "
                    + android.Manifest.permission.STOP_APP_SWITCHES);
        }

        synchronized(this) {
            mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
                    + APP_SWITCH_DELAY_TIME;
            mDidAppSwitch = false;
            mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
            Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
            mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
        }
    }

可以發現這裡主要是傳送了一個非同步訊息,並且msg.what為DO_PENDING_ACTIVITY_LAUNCHES_MSG,即跳轉Activity,然後我們繼續我們看一下mHandler的handleMessage方法當msg.what為DO_PENDING_ACTIVITY_LAUNCHES_MSG時的操作。而且我們可以發現這裡的非同步訊息是一個延時的非同步訊息,延時的時間為APP_SWITCH_DELAY_TIME,我們可以看一下該變數的定義:

// Amount of time after a call to stopAppSwitches() during which we will
    // prevent further untrusted switches from happening.
    static final long APP_SWITCH_DELAY_TIME = 5*1000;

然後我們可以看一下mHander的handleMessage方法的具體實現:

case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
                synchronized (ActivityManagerService.this) {
                    mStackSupervisor.doPendingActivityLaunchesLocked(true);
                }
            } break;

可以發現這裡直接呼叫了mStackSupervisor.doPendingActivityLaunchesLocked方法,好吧,繼續看一下doPendingActivityLaunchesLocked方法的實現。

final void doPendingActivityLaunchesLocked(boolean doResume) {
        while (!mPendingActivityLaunches.isEmpty()) {
            PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
            startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
                    doResume && mPendingActivityLaunches.isEmpty(), null, null);
        }
    }

可以發現這裡就是呼叫了startActivity的操作了,看過Activity啟動流程的同學應該知道:android原始碼解析之(十四)–>Activity啟動流程 這裡就是開始啟動Activity了,所以當我們按下HOME按鍵的時候,後臺的startActivity都會延時5秒鐘執行…

然後回到我們的launchHomeFromHotKey方法,看一下launchHomeFromHotKey方法的實現。

void sendCloseSystemWindows(String reason) {
        PhoneWindow.sendCloseSystemWindows(mContext, reason);
    }

可以發現這裡呼叫了PhoneWindow的靜態方法sendCloseSystemWindow,繼續看一下該方法的實現邏輯。

public static void sendCloseSystemWindows(Context context, String reason) {
        if (ActivityManagerNative.isSystemReady()) {
            try {
                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
            } catch (RemoteException e) {
            }
        }
    }

看到這裡,很明顯了又是呼叫了Binder的程序間通訊,最終ActivityManagerService的closeSystemDialogs方法會被執行,所以我們繼續看一下ActivityManagerService的closeSystemDialogs方法的實現。

@Override
    public void closeSystemDialogs(String reason) {
        enforceNotIsolatedCaller("closeSystemDialogs");

        final int pid = Binder.getCallingPid();
        final int uid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        try {
            synchronized (this) {
                // Only allow this from foreground processes, so that background
                // applications can't abuse it to prevent system UI from being shown.
                if (uid >= Process.FIRST_APPLICATION_UID) {
                    ProcessRecord proc;
                    synchronized (mPidsSelfLocked) {
                        proc = mPidsSelfLocked.get(pid);
                    }
                    if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                        Slog.w(TAG, "Ignoring closeSystemDialogs " + reason
                                + " from background process " + proc);
                        return;
                    }
                }
                closeSystemDialogsLocked(reason);
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

可以發現其實在方法體中將關閉視窗的邏輯下發到了closeSystemDialogsLocked中,所以我們繼續看一下closeSystemDialogsLocked方法的實現。

void closeSystemDialogsLocked(String reason) {
        Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                | Intent.FLAG_RECEIVER_FOREGROUND);
        if (reason != null) {
            intent.putExtra("reason", reason);
        }
        mWindowManager.closeSystemDialogs(reason);

        mStackSupervisor.closeSystemDialogsLocked();

        broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
                AppOpsManager.OP_NONE, null, false, false,
                -1, Process.SYSTEM_UID, UserHandle.USER_ALL);
    }

可以發現在方法體中首先呼叫了mWindowManager.closeSystemDialogs方法,該方法就是關閉當前頁面中存在的系統視窗,比如輸入法,桌布等:

@Override
    public void closeSystemDialogs(String reason) {
        synchronized(mWindowMap) {
            final int numDisplays = mDisplayContents.size();
            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
                final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
                final int numWindows = windows.size();
                for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
                    final WindowState w = windows.get(winNdx);
                    if (w.mHasSurface) {
                        try {
                            w.mClient.closeSystemDialogs(reason);
                        } catch (RemoteException e) {
                        }
                    }
                }
            }
        }
    }

講過這樣一層操作之後,我們就關閉了當前中存在的系統視窗。然後還是回到我們的launchHomeFromHotKey方法,我們發現在方法體的最後我們呼叫了startDockOrHome方法,這個方法就是實際的跳轉HOME頁面的方法了,我們可以具體看一下該方法的實現。

void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {
        if (awakenFromDreams) {
            awakenDreams();
        }

        Intent dock = createHomeDockIntent();
        if (dock != null) {
            try {
                if (fromHomeKey) {
                    dock.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
                }
                startActivityAsUser(dock, UserHandle.CURRENT);
                return;
            } catch (ActivityNotFoundException e) {
            }
        }

        Intent intent;

        if (fromHomeKey) {
            intent = new Intent(mHomeIntent);
            intent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, fromHomeKey);
        } else {
            intent = mHomeIntent;
        }

        startActivityAsUser(intent, UserHandle.CURRENT);
    }

可以發現我們在方法體中呼叫了createHomeDockIntent,這個方法的作用就是建立到達HOME頁面的Intent物件,然後我們呼叫了startActivityAsUser方法,這樣經過一系列的呼叫之後就調起了home頁面的Activity,所以這時候系統就返回到了HOME頁面。

總結:

  • 系統也是在PhoneWindowManager中監聽HOME按鍵的點選並進行處理;

  • 系統監聽到HOME按鍵之後會首先關閉相應的系統彈窗;

  • 通過建立Intent物件,並呼叫startActivity方法使系統跳轉到HOME頁面;

相關推薦

Android原始碼解析-->HOME事件流程

上一篇文章中我們介紹了android系統的截圖事件,由於截圖事件是一種系統全域性處理事件,所以事件的處理邏輯不是在App中執行,而是在PhoneWindowManager中執行。而本文我們現在主要講解android系統中HOME按鍵的事件處理,和截圖事件類似

Android原始碼解析-->應用程式返回按鍵執行流程

從這篇文章中我們開始分析android系統的事件分發流程,其實網上已經有了很多關於android系統的事件分發流程的文章,奈何看了很多但是印象還不是很深,所以這裡總結一番。 android系統的事件分發流程分為很多部分: Native層 –> V

Android原始碼解析-->電源開關機按鍵事件流程

前面我們講解了系統截圖按鍵處理流程,HOME按鍵處理流程,今天再來講解一下電源開關機按鍵事件流程,當然這也是系統按鍵處理流程方面的最後一篇部落格了。 和截圖按鍵、HOME按鍵的處理流程類似,電源按鍵由於也是系統級別的按鍵,所以對其的事件處理邏輯是和截圖按鍵

Android原始碼解析-->PopupWindow載入繪製流程

在前面的幾篇文章中我們分析了Activity與Dialog的載入繪製流程,取消繪製流程,相信大家對Android系統的視窗繪製機制有了一個感性的認識了,這篇文章我們將繼續分析一下PopupWindow載入繪製流程。 在分析PopupWindow之前,我們將

Android原始碼解析-->截圖事件流程

今天這篇文章我們主要講一下Android系統中的截圖事件處理流程。用過android系統手機的同學應該都知道,一般的android手機按下音量減少鍵和電源按鍵就會觸發截圖事件(國內定製機做個修改的這裡就不做考慮了)。那麼這裡的截圖事件是如何觸發的呢?觸發之後

three.js 原始碼註釋Core/BufferGeometry.js

俺也是剛開始學,好多地兒肯定不對還請見諒. 以下程式碼是THREE.JS 原始碼檔案中Core/BufferGeometry.js檔案的註釋. /** * @author alteredq / http://alteredqualia.com/ */ /* //

Redis原始碼剖析和註釋--- Redis 故障轉移流程和原理剖析

Redis 故障轉移流程和原理 1. 故障轉移介紹 Redis叢集自身實現了高可用。高可用首先要解決叢集部分失敗的場景:當叢集內少量節點出現故障時通過自動故障轉移保證叢集可以正常對外提供服務。接下來就介紹故障轉移的細節,分析故障檢測和故障轉移。 故障檢測

Android原始碼解析-->觸控事件分發流程

前面一篇文章中我們分析了App返回按鍵的分發流程,從Native層到ViewRootImpl層到DocorView層到Activity層,以及在Activity中的dispatchKeyEvent方法中分發事件,最終呼叫了Activity的finish方法,

Android開發系列:Notification的功能與使用方法

font _id when ice extends 開發 content androi mark 關於消息的提示有兩種:一種是Toast,一種就是Notification。前者維持的時間比較短暫,後者維持的時間比較長。 並且我們尋常手機的應用比方網易、貼吧等等都有非常多

【轉】JMeter學習Jmeter常見問題

pre 麻煩 continue 而不是 行為 let 方式 prop 右上角 收集工作中JMeter遇到的各種問題 1. JMeter的工作原理是什麽?   向服務器提交請求;從服務器取回請求返回的結果。 2. JMeter的作用?   JMeter可以用於測試

ERP合同管理

客戶 contact null format read effective XML listitem 控件 未審核表單列表顯示: 1.用戶登錄後,根據登錄用戶加載審核流程表中屬於當前登錄用戶的未審核表單。2.點擊選中未審核表單跳轉到指定審核流程頁面 if (Re

Android學習路線運用Fragment構建動態UI——創建一個Fragment

動態 app idt 文檔 部分 roi 現實 調用 android學習 你能夠把fragment看成是activity的模塊化部分。它擁有自己的生命周期,接受它自己的輸入事件,你能夠在activity執行時加入或者刪除它(有點像是一個“子activity”。你

Cocos2dx 3.0 過渡篇C++11多線程std::thread的簡單使用(下)

fonts fun avi 2dx read 來源 cpp break 輸出 本篇接上篇繼續講:上篇傳送門:http://blog.csdn.net/star530/article/details/24186783簡單的東西我都說的幾乎相同了,想挖點深的差點把自己給填進

企業分布式微服務雲SpringCloud SpringBoot mybatis 集成spring cache

state tee comm fin 發現 oid actor lis home 創建一個book數據訪問層 先創建一個實體類 public class Book { private String isbn; private String title; public

Linux學習筆記sed

sedsedsed是一種流編輯器,它是文本處理中非常中的工具,能夠完美的配合正則表達式使用,功能不同凡響。處理時,把當前處理的行存儲在臨時緩沖區中,稱為“模式空間”(pattern space),接著用sed命令處理緩沖區中的內容,處理完成後,把緩沖區的內容送往屏幕。接著處理下一行,這樣不斷重復,直到文件末尾

大數據筆記——Spark Core簡介及安裝配置

sin cli sca follow com clu 同時 graphx 信息 1、Spark Core: 類似MapReduce 核心:RDD 2、Spark SQL: 類似Hive,支持SQL 3、Spark Streaming:類似

C之字符串

C語言 字符串 字符數組 我們今天來講下 C 語言中的字符串。字符串是有序字符的集合,它也是程序中的基本元素之一。在 C 語言中沒有字符串的概念,C 語言中通過特殊的字符數組模擬字符串,並且是以 '\0' 結尾的字符數組。 在 C 語言中,雙引號引用的

python2.7練習小例子

IT 個數字 如果 dia yar gson pri python 實例 27):題目:一個5位數,判斷它是不是回文數。即12321是回文數,個位與萬位相同,十位與千位相同。 #!/usr/bin/python # -*- coding: UTF-8 -

Android開發實戰:淺談android:clipChildren屬性

.cn viewpage port 部分 lap ole 有一個 默認 版本 原文:Android開發實戰(二十一):淺談android:clipChildren屬性實現功能: 1、APP主界面底部模塊欄 2、ViewPager一屏多個界面顯示 3、........

C++ 中的字符串類

C++ 字符串類 循環移動 在 C 語言中是不支持真正意義上的字符串,是用字符數組和一組函數來實現字符串操作的。同樣,在 C 語言中不支持自定義類型,因此無法獲得字符串類型。那麽從 C 到 C++ 的進化過程引入了自定義類型,在 C++ 中可以通過類來完成字符串類型的定義。那麽問題