一個星期使用三種不同的開發模式完成資訊類App——《聽風資訊》

1.引言

最近一段時間由於畢設以及答辯等一系列的事情,已經很久沒有更新博客了。在月初立下的Flag——每天學習一個Android中的常用框架,也沒能堅持下去。當然,作者並不是一個半途而廢的人。等到最近的事情完成得差不多了,還是會繼續更新這個系列的博文。
事實上,在處理事情的同時,我研究了App的幾種開發模式,並且嘗試學習並運用其中的一些主流的技術棧。目前來說,App的開發模式主要分爲Native App(原生App)、Web App(WebApp)、HyBird App(混合App)。這三種App的開發模式在網上都有具體的介紹,感興趣的讀者可以查找一下相關的資料。
通過一段時間內對這三種開發模式的學習,爲了加深自己的印象,本着實踐出真知的想法,作者產生了使用這三種開發模式分別開發一個App的想法。一來是可以鞏固自己的基礎,二來也是提升自己的實踐運用能力。
經過一週的時間,作者成功根據三種App的開發模式開發出了文章名所說的資訊類App——《聽風資訊》。由於作者本人的不熟練,項目裏還存在相當多的可優化處。也因爲項目的不成熟,該項目的源碼就不公開放在碼雲上了,對項目感興趣的讀者可以私下聯繫我,QQ:545646733。

無圖無真相,接下來把分別通過這三種App開發模式的App運行效果及其目錄結構展示出來:

  • Native App(原生App)

在這裏插入圖片描述
在這裏插入圖片描述

  • Web App(WebApp)
    在這裏插入圖片描述
    在這裏插入圖片描述

  • HyBird App(混合App)
    在這裏插入圖片描述
    在這裏插入圖片描述
    該App的功能比較簡單,基本上就是仿照世面上常見的資訊類App。通過成果展示也可以看出,它們之間的共同功能有:

  • 網絡訪問接口數據(Json格式);

  • 解析Json格式的數據,並且渲染到列表上;

  • 對圖片加載的優化;

  • 下拉刷新;

  • 底部的導航欄;

  • 點擊某條新聞時,進入該新聞對應的網頁;

  • 循環展示的輪播圖;

  • 頁面中的導航欄;

  • 均實現了異步調用(即數據的獲取和UI的渲染是分開的)

  • 沉浸式狀態欄

接下來會介紹項目裏這三種App開發模式的異同,以及作者開發過程中的一些感想。

2.App開發模式的主要區別

根據網絡上查詢的資料,三種App開發模式的主要區別如下:

性質/App類型 Native App(原生App) Web App(WebApp) HyBird App(混合App)
技術棧 Android UniApp、Ionic、Cordova React Native、Flutter
語言 Java、Kotlin Html、Css、Js ReactJs、Dart
對應平臺 Android Android、IOS、微信小程序等多個平臺 Android、IOS
兼容性 差,不支持本地數據庫讀寫和驅動調用 一般
性能 差,大部分內容需要聯網纔可使用 一般
開發成本 低,大部分邏輯僅需要實現前端頁面即可 一般

3.App開發模式在開發項目時所使用到的技術棧

在資訊類App《聽風資訊》中,針對三種App開發模式的特點,分別選用瞭如下所示的技術棧來進行設計:

性質/App類型 Native App(原生App) Web App(WebApp) HyBird App(混合App)
技術棧 Android UniApp Flutter
語言 Java Vue.js Dart
網絡訪問 OkHttp Promise Dio
圖片加載 Glide Image Image
數據解析 Gson / FlutterJsonBeanFactory
側拉欄 DrawerLayout、NavigationView Drawer Drawer
列表 RecycleView、ViewPager V-for ListView、ListTitle
狀態欄 Toolbar TitleNView AppBar
輪播圖 Banner Swiper Swiper
底部標籤 BottomNavigationView TabBar BottomNavigationBar
導航欄 TabHost / TabBarView
下拉刷新 SwiperRefreshLayout PullDownRefresh RefreshIndicator
異步模型 Handler、AsyncTask Await、Async Future、Isolate

沒有列出具體選項(即“/”)的格子即表示實現該功能還沒有較好的技術手段或者本身就支持了,其餘基本上都是當下較爲流行的框架,版本號也是各代碼倉庫(GitHub、DCloud、Pub等)裏最新(2020.6.22)的。

接下來,將會介紹作者在進行App開發時遇到的幾個難點。

4.App開發時的感想

4.1 Native App(原生App)

原生App,即使用Java或者Kotlin語言進行實現的Android應用。最早入坑Android時,接觸的基本上都是原生App。由於技術棧的原因,可以讓熟悉Java/Kotlin語言的人很快就學會Android的很多特性,從而開始Android應用的研發。
當然,作爲高度可定製並且兼容性最佳的開發模式,原生App基本上作爲當下Android應用的主流。但與此同時,也產生了諸如屏幕適配,大圖加載,資源裝載等許多細節問題。幸而目前原生App已經發展很成熟了,有許多好用的工具可以解決這些問題。
作爲Android工程師,最需要熟悉的App開發模式就是原生App了。記錄完這篇博客後,作者將會研究一個更爲成熟、好用的原生App腳手架,並通過另一篇博客進行記錄(立下flag)。

接下來,談談作者在進行原生App開發時遇到的一些主要難點:

4.1.1 Material Design的設計

4.1.1.1 BottomNavigationView

BottomNavigationView是Google官方提供的一種實現底部標籤切換的控件,在Android Studio 3.0之後就可以通過創建Activity中的Bottom Navigation Activity,如圖所示:
在這裏插入圖片描述
最開始做項目時,要實現底部標籤需要使用RadioGroup + RadioButton來實現這部分的功能,RadioButton還需要編寫一個Selector來滿足圖標在點擊時顯示不同的圖樣,而使用BottomNavigationView似乎就能很好地解決這塊的問題。

當然,使用BottomNavigationView時,需要注意幾個要點:

  1. 在初始化BottomNavigationView管理着的Fragment時,如果你的項目中使用到了Toolbar,則需要先綁定Toolbar,否則會報空指針異常,代碼如下:

    // 初始化ToolBar,注意要在Fragment初始化之前調用,不然會報空指針異常,這裏踩過坑!
    setSupportActionBar(tb_title);
    // 初始化Fragment
    initFragment();
    
  2. id的對應。<menu>標籤和<item>標籤中控件的id要對應,不然會不顯示內容。

  3. 另外,若使用BottomNavigationView,佈局則推薦使用ConstraintLayout,即約束佈局,這樣會比較好控制控件的擺放(這個控件作者本人用的也不是很熟練,在使用時遇到了BottomNavigationView遮擋RecyclerView的情況,導致內容顯示不全,最後作者用了很笨的方法才調整成功,希望有比較瞭解這塊內容的讀者能夠在評論區不吝賜教,作者將感激不盡

4.1.1.2 Toolbar

Toolbar是Google官方提供的一種實現狀態欄切換的控件,作爲替換Actionbar的狀態欄,功能要更爲強大。

使用Toolbar時,需要注意幾個要點:

  1. Android應用默認使用的是Actionbar,要使用Toolbar,記得在values/style.xml中聲明Android應用的樣式,即NoActionBar代碼如下:

    <!-- Base application theme. -->
    	<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
       	 <!-- Customize your theme here. -->
        	<item name="colorPrimary">@color/colorRed</item>
        	<item name="colorPrimaryDark">@color/colorRed</item>
        	<item name="colorAccent">@color/colorAccent</item>
    	</style>
    
  2. 要通過Toolbar實現沉浸式狀態欄,只需要修改values/style.xml中的配置顏色,並且讓Toolbar的顏色也對應即可,代碼如下:

        <item name="colorPrimary">@color/colorRed</item>
        <item name="colorPrimaryDark">@color/colorRed</item>
    
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tb_title"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
    </androidx.appcompat.widget.Toolbar>
    
  3. Toolbar的標題默認是顯示在左邊的,要想顯示在正中,常見的做法是在Toolbar中嵌套一個居中顯示的TextView,這裏也可以使用一個工具類來調整Toolbar中標題的擺放,代碼如下:

    public class ToolBarUtils {
    
    public static void setTitleCenter(Toolbar toolbar) {
        String title = "title";
        final CharSequence originalTitle = toolbar.getTitle();
        toolbar.setTitle(title);
        for (int i = 0; i < toolbar.getChildCount(); i++) {
            View view = toolbar.getChildAt(i);
            if (view instanceof TextView) {
                TextView textView = (TextView) view;
                if (title.equals(textView.getText())) {
                    textView.setGravity(Gravity.CENTER);
                    Toolbar.LayoutParams params = new Toolbar.LayoutParams(Toolbar.LayoutParams.WRAP_CONTENT, Toolbar.LayoutParams.MATCH_PARENT);
                    params.gravity = Gravity.CENTER;
                    textView.setLayoutParams(params);
                }
            }
            toolbar.setTitle(originalTitle);
        }
    	}
    }
    
  4. Toolbar默認是沒有返回按鈕的,若想開啓需要先調用getSupportActionBar().setDisplayHomeAsUpEnabled(true);,然後實現其點擊方法,代碼如下:

    /**
     * 點擊Toolbar上的“回退”按鈕時觸發的邏輯
     * @param item
     * @return
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(item.getItemId() == android.R.id.home)
        {
            finish();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
    
  5. 可以在設定Toolbar時用?attr/actionBarSize來界定其高度,代表之前應用還擁有Actionbr控件時的高度,控件整體代碼如下:

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tb_title"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
    </androidx.appcompat.widget.Toolbar>
    

4.1.1.3 SwipeRefreshLayout

SwipeRefreshLayout是Google官方提供的一種實現下拉刷新的控件,作爲替換pullToRefresh的下拉刷新控件,使用和集成要相對簡單一些。

使用SwipeRefreshLayout時,需要注意幾個要點:

  1. 在使用SwipeRefreshLayout時,建議只包裹一個List類型的控件即可,代碼如下:

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/srl_head"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rl_head"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    
  2. 在實現SwipeRefreshLayout的監聽器onRefresh()方法後,下拉刷新時會循環展示刷新的動畫,需要在數據顯示完畢後手動調用setRefreshing(false)來關閉動畫

4.1.1.4 RecyclerView

RecyclerView是Google官方提供的一種實現數據列表的控件,作爲替換ListView的數據列表控件,樣式和使用都要相對好一些。(可能是作者使用ListView比較久了,覺得RecyclerView的佈局適配比較難實現)

使用RecyclerView時,需要注意幾個要點:

  1. RecyclerView的適配器需要繼承RecyclerView.Adapter<NewsDetailAdapter.ViewHolder>,可以實現默認的ViewHolder優化;
  2. RecyclerView的適配器的構造方法所接受的數據集合只有發生變動了,RecyclerView的數據刷新方法纔會生效,因此若單獨寫了其適配器類,則需要在獲取到數據的時候配置RecyclerView以及其適配器;

4.1.1.5 TabLayout

TabLayout是Google官方提供的一種實現導航欄的控件,作爲替換ViewPagerIndicator的導航欄,樣式和使用都要相對好一些。一般使用TabLayout都是需要搭配ViewPager的,因此可以使用官方提供的setupWithViewPager來綁定TabLayout和ViewPager,並且在ViewPager註冊適配器時重寫getPageTitle方法,以此來獲取由ViewPager所管理着的Fragment所對應的碎片(如果想要保證順序一致則需要在創建集合時調整插入的數據)

4.1.2 Gson的解析

4.1.2.1 Gson在遇到類型錯誤時的處理

Gson是Google官網提供用來解析Json數據的工具,只需要將Json數據轉化成實體類,就可以通過Gson將其轉化成對象的形式。
然而,有一種情況——平常解析的Json數據中的某個字段平時是一個對象類型,而在網絡不佳的情況下則會傳回一個空字符串(""),也就是說Json數據同一個字段同時出現了兩種類型的情況。在這樣的情形下,Gson會解析失敗,並且會直接拋出異常,導致App閃退。
爲了解決這個問題,Gson提供了JsonDeserializer接口來讓某個類自定義其反序列化的過程,具體操作可參照此篇博客:

JsonDeserializer——Gson自定義解析類型錯誤的字段

這篇博客很好地總結了Gson在解析Json同字段不同類型時的對策,事實上Gson還有許多使用方法,等待我們去學習。

4.1.2.2 Gson和緩存

爲了避免Gson在解析失敗等問題上拋出異常導致整個應用崩潰,可以在每次使用Gson解析數據之後將數據寫入緩存(文件、Sp和數據庫均可),這樣可以保證在異常情況下(沒有網絡,Gson解析失效,網絡傳輸慢)依然可以獲得之前解析好的數據。作者使用Sp作爲讀寫緩存,實現了一個簡單的緩存工具類jsonCache,代碼如下:

public class jsonCache {

    // 設置緩存
    public static void setCache(Context context, String key,String value) {
        SharedPreferencesUtils.putString(context,key,value);
    }

    // 讀取緩存
    public static String getCache(Context context, String key,String defvalue) {
        String string = SharedPreferencesUtils.getString(context, key, defvalue);
        return string;
    }
}

4.1.3 ViewPager的懶加載

使用ViewPager來管理Fragment,會同時導致這些Fragment執行onCreateView,即提前加載好所有的數據,這會讓應用接收龐大的數據導致卡頓。爲了解決這個問題,需要實現ViewPager的懶加載,即切換到這個Fragment時再獲取其數據,保證應用的流暢度。說來慚愧,這項優化其實作者還沒有進行落實,還在研究當中,力求尋找最優的方法,感興趣的讀者也可以查詢相應資料。

4.1.4 Glide的佔位圖

Glide是比較常用的圖片加載工具,底部封裝了三級緩存等大量圖片加載優化。Glide還提供了大量的工具方法,其中包括有佔位圖的設置,即在圖片還未加載出來時先放置佔位圖,這樣可以提高用戶的體驗度,提高了應用的可用性。

4.2 Web App(WebApp)

WebbApp,即使用Html、Css、JavaScript等前端語言進行實現的Android應用。中間因爲學習了一段時間的Java服務器的開發,自然而然也會接觸到這些前端語言的學習。由於技術棧的原因,可以讓熟悉前端的人在不熟悉後端語言的基礎上,開始Android應用的研發。
WebApp的開發相對其他兩種App開發模式要更爲迅速,因爲所有代碼基本上都是基於前端代碼來實現,並且WebApp對應的並非只有Android一個平臺,還支持IOS、Web等平臺。但與此同時,WebApp對於Android系統底層驅動的調用略顯乏力,尤其是不支持訪問本地數據庫的特性使其不支持作爲主流App的開發方向。另外WebApp的大部分功能都需要依據網絡,若失去網絡的支持,WebApp可能只形如空殼,很多開發者會戲稱WebApp爲“手機上的PPT”。
當然,作爲能夠快速開發並且UI設計較佳的開發模式,WebApp適合作爲資訊類等App的開發模式。WebApp仰仗於前端編程語言,具有控件豐富的組件市場,這也算是WebApp相較於其他開發模式較爲優勢的地方。

在開發WebApp時,使用的主要技術棧爲Uniapp,其主體語言爲Vue.js,若熟悉此技術棧會很快上手其開發。除了一些ES6語法之外,Uniapp也支持Scss等樣式,生態圈也較爲成熟,基本上可以做到大部分組件“拿來即用”,所以大致上沒有遇到什麼難點,就暫且略過這部分了。

4.3 HyBird App(混合App)

混合App,即混合使用幾種語言進行實現的Android應用,最典型的混合App技術棧就是React Native和Flutter。在學習了一段時間的原生App後,爲了讓App能夠同時支持Android和IOS端,避免一種App因爲平臺的不同而需要開發兩套項目的成本花銷,混合App應運而生。混合App擁有原生App的性能快和兼容性強等特性,還擁有WebApp的跨平臺運行和界面優美的特點,更像是這兩者App取長補短之後的產物。
當然,作爲Android開發方向的嶄新產物,混合App的生態圈還尚未完備,一些細節性的東西可能還是沒有原生App的實現要好,而兼容性方面或許還是WebApp要更勝一籌。由於React Native的配置需要使用npm,WebPack等前端工具來配置,步驟較爲繁瑣,所以這裏還是採用較新的Flutter來完成混合App的開發。

Flutter主要採用了Dart語言進行開發,Dart語言有點類似於Java語言,如果對Java比較熟悉的話會很快上手Dart語言。Flutter提供了比較方便的MaterialApp佈局,可以快速實現一個標準App的大概樣式。

接下來,談談作者在進行混合App開發時遇到的一些主要難點:

4.3.1 使用setState()刷新界面

在進行操作時,若界面上沒有顯示出來對應的數據,多半是沒有調用setState()來進行刷新,這是由於Flutter的特性所致。例如在點擊底部導航欄時,由於數據沒有發生變化,界面同樣也不會發生變化,就不會產生界面切換的效果,這時候就需要調用動態調用setState(),來通知這個Widget狀態已經發生了改變,需要重繪界面。

4.3.2 使用FlutterJsonBeanFactory來解析Json數據

在Flutter中,由於沒有FastJson、JackSon、Gson等Json解析工具,解析Json變得異常麻煩。使用Flutter原生的Convert雖然也可以解析Json,但是遇到格式複雜的Json數據時,實現龐大的Json實體類是很痛苦的事情。這時候就可以使用FlutterJsonBeanFactory來進行Json數據的解析,並自動生成實體類。在獲取數據時,只需要像使用Json解析工具時一樣即可,代碼如下:

	// 使用FlutterJsonBeanFactory進行解析
      Map jsonMap = json.decode(response.toString());
      NewsEntity newsEntity = newsEntityFromJson(new NewsEntity(),jsonMap);
      NewsResult result = newsEntity.result;
      _newsDataList = result.xList;

4.3.3 使用Future完成異步操作

由於獲取網絡數據並解析之後放入列表中是耗時操作,需要將該操作放入異步模型中執行,防止阻塞主線程。Flutter的異步操作主要通過Future、Async、Await來實現,在獲取數據時,調用Future.builder(),既可以獲取對應Future方法中的數據,並且可以監聽數據獲得的實時性,在未獲取到數據時播放環形進度條,代碼如下:

	FutureBuilder<List<NewsResultList>>(
              future: fetchNews(newsType),
              builder: (context, snapshot){
                if(snapshot.hasError) print(snapshot.error);
                return snapshot.hasData ? NewsListItem(news: snapshot.data,scrollController: _scrollController) : Center(child: CircularProgressIndicator());
              },

4.3.4 使用compute()完成線程隔離

儘管使用了Future來完成異步操作,在數據讀取的同時還是會導致主界面卡頓,這將會降低應用的使用感,Flutter提供了computer()來實現將操作的線程進行隔離的操作,與之對應的還有isolate。但是isolate要相對重量級一些,這裏使用computer()即可達到目標。

5.總結

經過一週對三種App開發模式的學習並實踐,作爲Android開發人員,作者認爲原生App開發的基礎還是必要的,其中涉及了許多高深的原理需要去理解。如果對前端語言有些基礎的話,可以嘗試WebApp。而對於混合App,由於需要一定的學習成本,建議對原生App的開發相當熟悉之後再去嘗試,不然會對其中的許多概念感到模糊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章