1. 程式人生 > >Hybrid App移動應用開發初探

Hybrid App移動應用開發初探

一、移動App型別及其優缺點

1.1 Native App

  Native App(原生App)是用原生語言(Object-C/Java/C#/....)開發,使用者需要下載安裝的手機應用。

  優點是 可以完全利用系統的 API 和平臺特性,在效能上也是最好的。

  缺點是 由於開發技術不同,如果你要覆蓋多個平臺,則要針對每個平臺獨立開發,無跨平臺特性。

1.2 Web App

  Web App主要是採用統一的標準的HTML、JavaScript與CSS 等 Web 技術開發。

  優點是 使用者無需下載,通過不同平臺的瀏覽器訪問即可實現跨平臺,同時可以通過瀏覽器支援充分使用 HTML5 特性。

  缺點是 這些基於瀏覽器的應用無法呼叫系統 API 來實現一些高階功能(例如拍照、GPS、儲存等),也不適合高效能要求的場合。

1.3 Hybrid App

  Hybrid App(混合式App)中和了Native App和Web App各自的優勢。 我們可以用 HTML + CSS + JS 開發,相容多個平臺。使用者也要下載安裝,並能呼叫手機的攝像頭、通訊錄等功能, Hybrid App的靜態資源也在手機本地。

  優點是 相同的程式碼只需針對不同平臺進行編譯就能實現在多平臺的分發,大大提高了多平臺開發的效率;而相較於 Web App,開發者可以通過包裝好的介面,呼叫大 部分常用的系統 API。

二、Hybird App開發平臺介紹

2.1 PhoneGap

  PhoneGap是一個用基於HTML,CSS和JavaScript的,建立移動跨平臺移動應用程式的快速開發平臺。它使開發者能夠利用iPhone,Android,Palm,Symbian,WP7,WP8,Bada和Blackberry智慧手機的核心功能——包括地理定位,加速器,聯絡人,聲音和振動等,此外PhoneGap擁有豐富的外掛,可以呼叫。

  業界很多主流的移動開發框架均源於PhoneGap。較著名的有Worklight、appMobi、WeX5等;其中WeX5為國內打造,完全Apache開源,在融合Phonegap的基礎上,做了深度優化,具備接近Native app的效能,同時開發便捷性也較好。

2.2 Cordova

  Cordova是貢獻給Apache後的開源專案,是從PhoneGap中抽出的核心程式碼,是驅動PhoneGap的核心引擎。你可以把它們的關係想象成類似於Webkit和Google Chrome的關係。

  Cordova提供了一組裝置相關的API,通過這組API,移動應用能夠以JavaScript訪問原生的裝置功能,如攝像頭、麥克風等。

  Cordova還提供了一組統一的JavaScript類庫,以及為這些類庫所用的裝置相關的原生後臺程式碼。

  Cordova支援如下移動作業系統:iOS, Android,ubuntu phone os, Blackberry, Windows Phone, Palm WebOS, Bada 和 Symbian。

2.3 Hybrid App程式結構

  為了瞭解清楚Hybrid App的程式結構,我們先來複習一下普通的ASP.NET Web網站應用的結構:

  最底層當然是CLR提供的執行時環境,這是所有.NET應用程式都必須賴以生存的條件。在CLR之上是.NET Framework提供的一些基類庫BCL,包括了IO、String、Thread等常用的型別。在BCL之上是一些常用的Framework,例如B/S模式的ASP.NET WebForm和ASP.NET MVC,C/S模式的Windows Form或WPF等。最上層才是我們得應用程式,它是基於下面的基礎環境來構建的,一層接一層,每一層都對下層有依賴。

  現在我們再來看下面一張圖,它展示了一個Hybird App的結構:

  與Web網站結構圖相對應,Hybrid App結構圖的最底層是Native Code(原生代碼),這裡列舉了三種主要作業系統iOS、Android以及Windows Phone的對應開發語言Object-C、Java和C#,在Native App的開發中我們直接使用這幾種語言開發對應作業系統的App。在Native Code之上的是Cordova/PhoneGap這樣的平臺,這些平臺提供了JavaScript執行平臺和Native API,上層通過傳遞JS程式碼,由JS執行平臺進行解釋,再呼叫對應的Native API,由Native API去呼叫對應作業系統的Native Code。換句話說,Cordova/PhoneGap這一層所做的就是對Native Code層面的包裝。在平臺層之上是一些Plugins(外掛),它是一堆手機的硬體元件介面,可以方便地使用JS程式碼呼叫相機、檔案、網路等硬體資源。最上層是一些Web前端的展示層框架,藉助這些框架可以方便地開發出適合Hybird App的Web頁面。

  一個典型的Hybrid App的呼叫手機硬體的Camera相機功能的層次順序如下圖所示:

三、Cordova平臺環境配置

3.1 配置JDK環境

  安裝jdk-8u71-windows-x64.exe,配置環境變數:

JAVA_HOME = C:\Program Files\Java\jdk1.8.0_71

Path += %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;

CLASSPATH += %JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;

  驗證:java -version

  

3.2 配置Android-SDK環境

  解壓android-sdk.rarD:\Develop\Android\sdk,配置環境變數:

ADT_HOME = D:\Develop\Android\sdk

Path += %ADT_HOME%\platform-tools;%ADT_HOME%\tools;

  驗證:adb -version

  

3.3 配置Apache-Ant環境

  解壓apache-ant-1.9.6.rar配置環境變數:

ANT_HOME = D:\Develop\ApacheAnt

Path += %ANT_HOME%\bin;

  驗證:ant -versioin

  

3.4 配置GIT環境

  安裝Git_V2.5.1_64_bit_setup.1441791170.exe配置環境變數:

GIT_HOME = C:\Program Files\Git

Path += %GIT_HOME%\bin;

  驗證:git --version

  

3.5 配置Node環境

  安裝node-v0.10.29-x64.msi,配置環境變數:

NODE_HOME = C:\Program Files\Nodejs\

Path += %NODE_HOME%;

  驗證:node -v,npm -v

  

3.6 配置Cordova環境

  在cmd中執行npm install -g cordova(線上安裝)

  或者將cordova.rar解壓到C:\Users\YourName\AppData\Roaming\npm\node_modules

PS:cordova最新版本匹配android 6.0,因此你的Android SDK也要下載6.0的包,如果你只有5.x的,那麼可以指定cordova的版本進行安裝,例如安裝cordova 5.1.1 :npm install -g [email protected]  

四、第一個移動App:簡單登入Demo

4.1 開發流程概述

  首先,使用Visual Studio或Sublime Text等IDE開發Web網頁,然後使用Cordova平臺進行打包生成Android專案檔案,最後調整配置檔案和釋出成apk。

4.2 使用Visual Studio開發Web網站

  這裡只開發一個簡單的login頁面,因此只有一個HTML檔案:login.html,藉助於bootstrap和zeptojs。login.html的程式碼如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>MyHybirdApp</title>
    <link href="assets/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <link href="assets/css/style.css" rel="stylesheet" />
</head>
<body>
    <!-- 表單區域 -->
    <form id="form_login" class="form-main" method="post" autocomplete="off">
        <h2>Sign in</h2>
        <hr />
        <div class="avator">
            <img id="myavator" src="assets/img/avatar.png" />
        </div>
        <div class="message-box"></div>
        <label for="username" class="sr-only">Username</label>
        <input id="username" type="text" class="form-control input-lg input-group-top" placeholder="Input your Username" required autofocus>
        <label for="password" class="sr-only">Password</label>
        <input id="password" type="password" class="form-control input-lg input-group-bottom" placeholder="Input your Password" required>
        <div class="checkbox">
            <input id="remember" type="checkbox" value="remember-me" checked>
            <label for="remember">Remember me</label>
            <a class="link" href="register.html">Sign up</a>
        </div>
        <input type="hidden" name="redirect" value="/" />
        <button id="btn_login" class="btn btn-lg btn-info btn-block">Sign in</button>
    </form>
    <!-- 指令碼區域 -->
    <script type="text/javascript" src="assets/lib/zepto/zepto.min.js"></script>
    <script type="text/javascript" src="cordova.js"></script>
    <script type="text/javascript">
        $(function () {
            // 呼叫手機硬體拍照
            $('#myavator').on('click', function () {
                navigator.camera.getPicture(onSuccess, onFail, {
                    quality: 50,
                    destinationType: Camera.DestinationType.DATA_URL
                });

                function onSuccess(imageData) {
                    var image = document.getElementById('myavator');
                    image.src = "data:image/jpeg;base64," + imageData;
                }

                function onFail(message) {
                    alert('Failed because: ' + message);
                }
            });
            // 訪問服務端服務
            $('#btn_login').on('click', function () {
                var userName = $('#username').val();
                var password = $('#password').val();
                if (userName == "") {
                    alert('Please input your user name!');
                }
                else if (password == "") {
                    alert('Please input your password!');
                }
                else {
                    // 在PC瀏覽器端的話下面的ajax請求就涉及到跨域,而在Cordova中我們不需要考慮麼麼噠
                    $.get('http://www.edisonchou.cn/AccountHandler.ashx?action=Login&username=' + userName + '&password=' + password + '', {
                    }, function (data) {
                        alert(data.Message);
                    });
                }

                return false;
            });
        });
    </script>
</body>
</html>
View Code

  這裡需要注意的有以下幾點:

  (1)訪問服務端的服務

    // 訪問服務端服務
    $('#btn_login').on('click', function () {
        var userName = $('#username').val();
        var password = $('#password').val();
        if (userName == "") {
            alert('Please input your user name!');
        }
        else if (password == "") {
            alert('Please input your password!');
        }
        else {
            // 在PC瀏覽器端的話下面的ajax請求就涉及到跨域,而在Cordova中我們不需要考慮麼麼噠
            $.get('http://www.edisonchou.cn/AccountHandler.ashx?action=Login&username=' + userName + '&password=' + password + '', {
            }, function (data) {
                alert(data.Message);
            });
        }

        return false;
    });

  我們知道在傳統PC 瀏覽器端中,ajax請求受限於XMLHttpRequest無法進行跨域請求,我們可能需要藉助JSONP一類的幫手幫我們解決,而在Cordova生成的Hybird App中不需要考慮這個問題。在上面的程式碼中,get請求訪問的是一個位於遠端伺服器中的一個服務(可以是ashx一般處理程式,也可以是一個MVC應用的action)。

  (2)訪問Android手機的硬體

    // 呼叫手機硬體拍照
    $('#myavator').on('click', function () {
        navigator.camera.getPicture(onSuccess, onFail, {
            quality: 50,
            destinationType: Camera.DestinationType.DATA_URL
        });

        function onSuccess(imageData) {
            var image = document.getElementById('myavator');
            image.src = "data:image/jpeg;base64," + imageData;
        }

        function onFail(message) {
            alert('Failed because: ' + message);
        }
    });

  參考Cordova的API文件,我們可以通過如上所示的JS程式碼訪問Camera相機,並呼叫相機進行拍照。兩個事件onSuccess和OnFail則是拍照成功或失敗後的處理邏輯。這裡成功後,我們將新拍的照片放到頭像Image位置。

  另外,我們還需要一個服務端,提供登入驗證的介面供App客戶端呼叫,這裡我們簡單地做一個ashx一般處理程式來進行處理,並將其釋出到阿里雲的虛擬機器中以便手機可以隨時訪問,其處理邏輯程式碼如下:

    public class AccountHandler : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            if (string.IsNullOrEmpty(context.Request["action"]))
            {
                return;
            }

            string action = context.Request["action"];
            JsonObject jsonObject = new JsonObject();
            switch (action)
            {
                case "Login":
                    string userName = context.Request["username"];
                    string password = context.Request["password"];
                    jsonObject.UserId = userName;

                    bool result = CheckAccount(userName, password);
                    if (result)
                    {
                        jsonObject.Message = "Success";
                    }
                    else
                    {
                        jsonObject.Message = "Failed";
                    }

                    JavaScriptSerializer serializer = new JavaScriptSerializer();
                    string jsonResult = serializer.Serialize(jsonObject);
                    context.Response.ContentType = "application/json";
                    context.Response.Write(jsonResult);
                    break;
            }
        }

        private bool CheckAccount(string userName, string password)
        {
            bool result = false;
            List<Account> accountList = GetAccountList();
            foreach (var account in accountList)
            {
                if (account.UserName.Equals(userName) && account.Password.Equals(password))
                {
                    result = true;
                }
            }

            return result;
        }

        private List<Account> GetAccountList()
        {
            return new List<Account>()
            {
                new Account() { UserName="edisonchou",Password="123456" },
                new Account() { UserName="zhouxulong",Password="654321"}
            };
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
View Code

  這裡沒有進行資料庫的操作,而是簡單得模擬了一個含有兩條使用者資訊的List集合。

4.3 使用Cordova打包apk檔案

  1.新建一個專案資料夾

  有了Web網站,我們可以進行App的準備工作了,首先新建一個Cordova專案資料夾:

  

  這裡我們給app取名為約嗎,PS:今天情人節,你約了嗎?

  然後將www檔案裡面的內容全部刪除,將我們開發的web網頁以及依賴的資源(圖片、css、js等)拷貝到此目錄下:

  

  2.增加android platform支援

  有了一個Cordova的專案資料夾,我們需要增加一個android的platform,因為我們要做的是一個基於android的app。進入yuema資料夾,然後輸入以下命令:

  

  3.增加android plugin支援

  由於我們的app使用了硬體,所以需要增加針對相對應硬體的plugin支援,前面提到我們需要呼叫對應的plugin,由plugin去呼叫JS直譯器生成對應Native API。這裡我們增加camera的plugin:

  

4.4 調整配置檔案和釋出應用

  在cordova生成的專案資料夾中,最頂層有一個config.xml,這個就是我們需要編輯的配置檔案。

  1.設定app的起始頁面

    <!-- 應用程式入口 -->
    <content src="login.html" />

  2.設定app的Icon以及SplashScreen

    <!-- 針對不同平臺單獨的設定選項 -->
    <platform name="android">
        <allow-intent href="market:*" />
        <!-- app icon image -->
        <icon src="www/assets/img/avatar.png" density="ldpi" />
        <icon src="www/assets/img/avatar.png" density="mdpi" />
        <icon src="www/assets/img/avatar.png" density="hdpi" />
        <icon src="www/assets/img/avatar.png" density="xdpi" />
        <!-- splash screen image -->
        <splash src="www/assets/img/splashscreen.png" density="land-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-xdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-xdpi" />
    </platform>

  完整的config.xml如下:

<?xml version='1.0' encoding='utf-8'?>
<widget id="cn.edisonchou.app" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>約嗎</name>
    <description>
        Edison Chou的第一個Cordova應用.
    </description>
    <author email="[email protected]" href="http://www.edisonchou.cn">
        Edison Chou
    </author>
    <!-- 應用程式入口 -->
    <content src="login.html" />
    <!-- 外掛依賴項 -->
    <plugin name="cordova-plugin-whitelist" version="1" />
    <!-- 網路型別配置 *代表所有地址均可訪問-->
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <!-- 針對不同平臺單獨的設定選項 -->
    <platform name="android">
        <allow-intent href="market:*" />
        <!-- app icon image -->
        <icon src="www/assets/img/avatar.png" density="ldpi" />
        <icon src="www/assets/img/avatar.png" density="mdpi" />
        <icon src="www/assets/img/avatar.png" density="hdpi" />
        <icon src="www/assets/img/avatar.png" density="xdpi" />
        <!-- splash screen image -->
        <splash src="www/assets/img/splashscreen.png" density="land-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="land-xdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-hdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-ldpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-mdpi" />
        <splash src="www/assets/img/splashscreen.png" density="port-xdpi" />
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform>
</widget>
View Code

  3.編譯釋出app成apk檔案

  通過命令:cordova build android 來生成最後的apk檔案

  

  

  生成的apk檔案位於:YourDirectory\yuema\platforms\android\build\outputs\apk 中

  

4.5 預覽我們的app

  將生成的apk放到我們得android手機中並進行安裝,然後點選進入,下面是演示圖片(演示手機:Smartisan T1)。

  (1)安裝app

     

  (2)點選進入登入頁面

     

  以下兩個gif圖片受限於gif製作軟體,效果較差,但是功能已經演示了出來:

  (3)呼叫服務端進行驗證

        

  (4)呼叫相機進行拍照

    

附件下載

參考資料

作者:周旭龍

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。

相關推薦

Hybrid App移動應用開發初探

一、移動App型別及其優缺點 1.1 Native App   Native App(原生App)是用原生語言(Object-C/Java/C#/....)開發,使用者需要下載安裝的手機應用。   優點是 可以完全利用系統的 API 和平臺特性,在效能上也是最好的。   缺點是 由於開發技術不同,

Hybrid App(混合開發移動開發除錯

1、下載專案,npm install安裝依賴 本地執行 npm run dev(根據具體packjson配 置而定) 2、區域網訪問:http://172.20.9.35:8080/ 3、手機端訪問:在我的、設定裡,點選關於vv裡的logo 5下 出現隱藏設定 除錯地址裡填  區域網訪

Qt移動應用開發(四):應用粒子特效

ons -i 遊戲 direct mit class png pop 狀態 Qt移動應用開發(四):應用粒子特效 上一篇文章介紹了Qt Quick是如何對幀動畫進行支持的。幀動畫的實現離不開狀態機、而狀態機、動畫和狀態切換(transition

使用Xamarin實現跨平臺移動應用開發(轉載)

def acs catch live make asset book -a 開發語言 剛在朋友圈看到張善友,轉發的一條分享“使用Xamarin實現跨平臺移動應用開發”,寫的確實很詳細得體,從收費到開源,這段時間xamarin受到不少質疑,如此文h

APP移動開發html模板

table sof :link get render ubi rom zoom detection 移動端開發模板: 750的稿子除以75: <!DOCTYPE html> <html> <head> <meta char

【Mono for Android】應用開發初探(2)

這是效果圖。但是新建 OpenGL 遊戲(Android)      Visual C#   工程時,遇到了問題。 即使我在AndroidManifest.xml中添加了網路許可權 <uses-perm

【Mono for Android】應用開發初探(1)

1.安裝VS2017 -1- -2- -3- -4- 第一次裝的時候如果只裝 Xamarin Workbooks \ Android Nd

《HTML5移動應用開發入門經典》(美)凱瑞恩.掃描版.pdf

書籍簡介: HTML5是關注度的前沿Web技術,而移動網際網路則是近兩年炙手可熱的Web領域。《HTML5移動應用開發入門經典》將這兩者巧妙結合起來,詳細講解了如何利用HTML5進行移動應用開發。    《HTML5移動應用開發入門經典》總共分為24章,以示例的方式對如何使用HT

安卓移動應用開發學習日記(一)

首先,自己安好網上的教程安裝好了AndroidStudio,花了n多時間,接下來就開始進行安卓開發學習啦!PS:根據老師上課 + 安卓程式設計權威指南(資源已上傳https://download.csdn.net/download/xingchen007/10645607)來

移動應用開發中AppID、AppKey、AppSecret到底是什麼?

AppID:應用的唯一標識 AppKey:公匙(相當於賬號) AppSecret:私匙(相當於密碼) token:令牌(過期失效) 使用方法 1. 向第三方伺服器請求授權時,帶上AppKey和AppSecret(需存在伺服器端) 2. 第三方伺服器驗證AppKey

(熱更新技術)高效率Hybird移動應用開發過程解決方案

前言 作為一名移動應用開發者而言快速高效進行版本測試,是至關重要的,所以一直在探索一個解決方案,可以隨時更新我們的邏輯程式碼,今天我們就來看一下,我是如何在專案中進行應用的。 熱更新 這個名詞很早就聽說過,只不過一直都沒有一個明確的定義,也沒有

Qt移動應用開發(三):使用精靈圖片實現幀動畫

       上一篇博文講到了Qt Quick對於動畫的一般支援,動畫的形式多樣,配合不同的插值函式,可以幾乎實現所有想要的動畫效果,而對於遊戲的一些特殊的效果比如說幀動畫,Qt更是有專門的類來實現。下面我們就來看看Qt Quick中究竟是對幀動畫是如何實現的吧。 原

React Native跨平臺移動應用開發框架介紹

好久沒有來更新部落格了,給大家說聲抱歉,人一旦懶惰起來連自己都害怕。可能是因為一個人生活,少了很多動力吧。這都是在給自己找理由。我在不偷懶了。 最近我要入坑了,公司安排我要開始搞React

基於mint-ui的移動應用開發案例三(首頁)

本節主要包含以下內容:首頁大致佈局vuex進行底部tabbar的顯示與隱藏控制和tab選中控制mint-cell-swipe元件的使用1.vuex的引入與使用首先在state資料夾中新建一個mutation_types.js用於存放要提交的動作,和一個index.js主要配置

HTML5移動應用開發最容易踩到的幾個坑

01/移動應用中HTML5的新特性   工欲善其事,必先利其器。我比較推崇的學習技術的方式,是先整體瞭解,然後結合實際需求,再做針對性的學習。整體瞭解的方式,比較建議是直接看官網的API文件,這裡可以推薦幾個網站: http://www.w3school.com.cn/

Delphi XE8移動應用開發中Andr​​oid許可權設定

本文摘自《Delphi XE8 iOS與Android移動應用開發教程[完整中文版]》,該書是一本介紹使用delphi xe8開發iOS與Android移動應用的電子書(開發教程與開發手冊)。 本節內容主要介紹使用Delphi XE系列工具開發Android應用時需要使

Python移動應用開發

這一章卡了好久好久……本來是安裝書中所說的先安裝Android sdk,再建立模擬器,但是模擬器不僅啟動慢,而且問題很多,所以最終換了使用VirtualBox-4.3.10-93012-Win.exe,genymotion-2.6.0.exe來代替之前的andr

移動應用開發產業的現狀分析

如今的移動應用開發產業著實讓人眼花繚亂,如果能有一份報告幫我們預知這個產業的未來走向,肯定是大有用處。所以,我在此邀請各位對移動應用開發產業感興趣的朋友和我來一起研究VisionMobile的一份調研報告(和Telefónica開發聯盟合作完成),因為這份報告是我目前讀到的

深信服科技2019年校園招聘 移動應用開發 一面

1、spring, aop, ioc 2、springmvc前端控制器,怎樣找到對應的處理處理器 設計這個查詢模組。。答的map 怎樣用別的方式快速查詢。。包括查詢帶有正則匹配的 3、一個公交站在1分鐘內有車經過概率是p,問3分鐘

H5+混合移動app應用開發——開篇

分享圖片 前三 mvvm 入門 混合應用 接口 blank alt jquery 前言 經過2個多月的艱苦奮鬥,app的第一個版本已經快完工了,期間遇到了太多的坑,作為一個喜歡分享的人,我當然不會吝嗇分享這爬坑歷程。不要問我有多坑,我會告訴你很多,很多..... 過去一