一个星期使用三种不同的开发模式完成资讯类App——《听风资讯》

文章目录

  • 1.引言
  • 2.App开发模式的主要区别
  • 3.App开发模式在开发项目时所使用到的技术栈
  • 4.App开发时的感想
    • 4.1 Native App(原生App)
      • 4.1.1 Material Design的设计
        • 4.1.1.1 BottomNavigationView
        • 4.1.1.2 Toolbar
        • 4.1.1.3 SwipeRefreshLayout
        • 4.1.1.4 RecyclerView
        • 4.1.1.5 TabLayout
      • 4.1.2 Gson的解析
        • 4.1.2.1 Gson在遇到类型错误时的处理
        • 4.1.2.2 Gson和缓存
      • 4.1.3 ViewPager的懒加载
      • 4.1.4 Glide的占位图
    • 4.2 Web App(WebApp)
    • 4.3 HyBird App(混合App)
      • 4.3.1 使用setState()刷新界面
      • 4.3.2 使用FlutterJsonBeanFactory来解析Json数据
      • 4.3.3 使用Future完成异步操作
      • 4.3.4 使用compute()完成线程隔离
  • 5.总结

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)


一个星期使用三种不同的开发模式完成资讯类App——《听风资讯》_第1张图片

  • Web App(WebApp)

    一个星期使用三种不同的开发模式完成资讯类App——《听风资讯》_第2张图片

  • HyBird App(混合App)

    一个星期使用三种不同的开发模式完成资讯类App——《听风资讯》_第3张图片
    该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,如图所示:
一个星期使用三种不同的开发模式完成资讯类App——《听风资讯》_第4张图片
最开始做项目时,要实现底部标签需要使用RadioGroup + RadioButton来实现这部分的功能,RadioButton还需要编写一个Selector来满足图标在点击时显示不同的图样,而使用BottomNavigationView似乎就能很好地解决这块的问题。

当然,使用BottomNavigationView时,需要注意几个要点:

  1. 在初始化BottomNavigationView管理着的Fragment时,如果你的项目中使用到了Toolbar,则需要先绑定Toolbar,否则会报空指针异常,代码如下:

    // 初始化ToolBar,注意要在Fragment初始化之前调用,不然会报空指针异常,这里踩过坑!
    setSupportActionBar(tb_title);
    // 初始化Fragment
    initFragment();
    
  2. id的对应。

    标签和标签中控件的id要对应,不然会不显示内容。

  3. 另外,若使用BottomNavigationView,布局则推荐使用ConstraintLayout,即约束布局,这样会比较好控制控件的摆放(这个控件作者本人用的也不是很熟练,在使用时遇到了BottomNavigationView遮挡RecyclerView的情况,导致内容显示不全,最后作者用了很笨的方法才调整成功,希望有比较了解这块内容的读者能够在评论区不吝赐教,作者将感激不尽

4.1.1.2 Toolbar

Toolbar是Google官方提供的一种实现状态栏切换的控件,作为替换Actionbar的状态栏,功能要更为强大。

使用Toolbar时,需要注意几个要点:

  1. Android应用默认使用的是Actionbar,要使用Toolbar,记得在values/style.xml中声明Android应用的样式,即NoActionBar代码如下:

    
    	<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
       	 
        	"colorPrimary">@color/colorRed
        	"colorPrimaryDark">@color/colorRed
        	"colorAccent">@color/colorAccent
    	style>
    
  2. 要通过Toolbar实现沉浸式状态栏,只需要修改values/style.xml中的配置颜色,并且让Toolbar的颜色也对应即可,代码如下:

        <item name="colorPrimary">@color/colorReditem>
        <item name="colorPrimaryDark">@color/colorReditem>
    
    <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,可以实现默认的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的开发相当熟悉之后再去尝试,不然会对其中的许多概念感到模糊。

你可能感兴趣的:(Android)