一个星期使用三种不同的开发模式完成资讯类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)
技术栈AndroidUniApp、Ionic、CordovaReact Native、Flutter
语言Java、KotlinHtml、Css、JsReactJs、Dart
对应平台AndroidAndroid、IOS、微信小程序等多个平台Android、IOS
兼容性差,不支持本地数据库读写和驱动调用一般
性能差,大部分内容需要联网才可使用一般
开发成本低,大部分逻辑仅需要实现前端页面即可一般

3.App开发模式在开发项目时所使用到的技术栈

在资讯类App《听风资讯》中,针对三种App开发模式的特点,分别选用了如下所示的技术栈来进行设计:

性质/App类型Native App(原生App)Web App(WebApp)HyBird App(混合App)
技术栈AndroidUniAppFlutter
语言JavaVue.jsDart
网络访问OkHttpPromiseDio
图片加载GlideImageImage
数据解析Gson/FlutterJsonBeanFactory
侧拉栏DrawerLayout、NavigationViewDrawerDrawer
列表RecycleView、ViewPagerV-forListView、ListTitle
状态栏ToolbarTitleNViewAppBar
轮播图BannerSwiperSwiper
底部标签BottomNavigationViewTabBarBottomNavigationBar
导航栏TabHost/TabBarView
下拉刷新SwiperRefreshLayoutPullDownRefreshRefreshIndicator
异步模型Handler、AsyncTaskAwait、AsyncFuture、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的开发相当熟悉之后再去尝试,不然会对其中的许多概念感到模糊。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值