最近一段时间由于毕设以及答辩等一系列的事情,已经很久没有更新博客了。在月初立下的Flag——每天学习一个Android中的常用框架,也没能坚持下去。当然,作者并不是一个半途而废的人。等到最近的事情完成得差不多了,还是会继续更新这个系列的博文。
事实上,在处理事情的同时,我研究了App的几种开发模式,并且尝试学习并运用其中的一些主流的技术栈。目前来说,App的开发模式主要分为Native App(原生App)、Web App(WebApp)、HyBird App(混合App)。这三种App的开发模式在网上都有具体的介绍,感兴趣的读者可以查找一下相关的资料。
通过一段时间内对这三种开发模式的学习,为了加深自己的印象,本着实践出真知的想法,作者产生了使用这三种开发模式分别开发一个App的想法。一来是可以巩固自己的基础,二来也是提升自己的实践运用能力。
经过一周的时间,作者成功根据三种App的开发模式开发出了文章名所说的资讯类App——《听风资讯》。由于作者本人的不熟练,项目里还存在相当多的可优化处。也因为项目的不成熟,该项目的源码就不公开放在码云上了,对项目感兴趣的读者可以私下联系我,QQ:545646733。
无图无真相,接下来把分别通过这三种App开发模式的App运行效果及其目录结构展示出来:
HyBird App(混合App)
该App的功能比较简单,基本上就是仿照世面上常见的资讯类App。通过成果展示也可以看出,它们之间的共同功能有:
网络访问接口数据(Json格式);
解析Json格式的数据,并且渲染到列表上;
对图片加载的优化;
下拉刷新;
底部的导航栏;
点击某条新闻时,进入该新闻对应的网页;
循环展示的轮播图;
页面中的导航栏;
均实现了异步调用(即数据的获取和UI的渲染是分开的)
沉浸式状态栏
接下来会介绍项目里这三种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 |
兼容性 | 高 | 差,不支持本地数据库读写和驱动调用 | 一般 |
性能 | 高 | 差,大部分内容需要联网才可使用 | 一般 |
开发成本 | 高 | 低,大部分逻辑仅需要实现前端页面即可 | 一般 |
在资讯类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开发时遇到的几个难点。
原生App,即使用Java或者Kotlin语言进行实现的Android应用。最早入坑Android时,接触的基本上都是原生App。由于技术栈的原因,可以让熟悉Java/Kotlin语言的人很快就学会Android的很多特性,从而开始Android应用的研发。
当然,作为高度可定制并且兼容性最佳的开发模式,原生App基本上作为当下Android应用的主流。但与此同时,也产生了诸如屏幕适配,大图加载,资源装载等许多细节问题。幸而目前原生App已经发展很成熟了,有许多好用的工具可以解决这些问题。
作为Android工程师,最需要熟悉的App开发模式就是原生App了。记录完这篇博客后,作者将会研究一个更为成熟、好用的原生App脚手架,并通过另一篇博客进行记录(立下flag)。
接下来,谈谈作者在进行原生App开发时遇到的一些主要难点:
BottomNavigationView是Google官方提供的一种实现底部标签切换的控件,在Android Studio 3.0之后就可以通过创建Activity中的Bottom Navigation Activity
,如图所示:
最开始做项目时,要实现底部标签需要使用RadioGroup + RadioButton来实现这部分的功能,RadioButton还需要编写一个Selector来满足图标在点击时显示不同的图样,而使用BottomNavigationView似乎就能很好地解决这块的问题。
当然,使用BottomNavigationView时,需要注意几个要点:
在初始化BottomNavigationView管理着的Fragment时,如果你的项目中使用到了Toolbar,则需要先绑定Toolbar,否则会报空指针异常,代码如下:
// 初始化ToolBar,注意要在Fragment初始化之前调用,不然会报空指针异常,这里踩过坑!
setSupportActionBar(tb_title);
// 初始化Fragment
initFragment();
id的对应。标签和
标签中控件的id要对应,不然会不显示内容。
另外,若使用BottomNavigationView,布局则推荐使用ConstraintLayout,即约束布局,这样会比较好控制控件的摆放(这个控件作者本人用的也不是很熟练,在使用时遇到了BottomNavigationView遮挡RecyclerView的情况,导致内容显示不全,最后作者用了很笨的方法才调整成功,希望有比较了解这块内容的读者能够在评论区不吝赐教,作者将感激不尽)
Toolbar是Google官方提供的一种实现状态栏切换的控件,作为替换Actionbar的状态栏,功能要更为强大。
使用Toolbar时,需要注意几个要点:
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>
要通过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>
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);
}
}
}
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);
}
可以在设定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>
SwipeRefreshLayout是Google官方提供的一种实现下拉刷新的控件,作为替换pullToRefresh的下拉刷新控件,使用和集成要相对简单一些。
使用SwipeRefreshLayout时,需要注意几个要点:
在使用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>
在实现SwipeRefreshLayout的监听器onRefresh()
方法后,下拉刷新时会循环展示刷新的动画,需要在数据显示完毕后手动调用setRefreshing(false)
来关闭动画
RecyclerView是Google官方提供的一种实现数据列表的控件,作为替换ListView的数据列表控件,样式和使用都要相对好一些。(可能是作者使用ListView比较久了,觉得RecyclerView的布局适配比较难实现)
使用RecyclerView时,需要注意几个要点:
RecyclerView.Adapter
,可以实现默认的ViewHolder优化;TabLayout是Google官方提供的一种实现导航栏的控件,作为替换ViewPagerIndicator的导航栏,样式和使用都要相对好一些。一般使用TabLayout都是需要搭配ViewPager的,因此可以使用官方提供的setupWithViewPager
来绑定TabLayout和ViewPager,并且在ViewPager注册适配器时重写getPageTitle
方法,以此来获取由ViewPager所管理着的Fragment所对应的碎片(如果想要保证顺序一致则需要在创建集合时调整插入的数据)
Gson是Google官网提供用来解析Json数据的工具,只需要将Json数据转化成实体类,就可以通过Gson将其转化成对象的形式。
然而,有一种情况——平常解析的Json数据中的某个字段平时是一个对象类型,而在网络不佳的情况下则会传回一个空字符串(""),也就是说Json数据同一个字段同时出现了两种类型的情况。在这样的情形下,Gson会解析失败,并且会直接抛出异常,导致App闪退。
为了解决这个问题,Gson提供了JsonDeserializer接口来让某个类自定义其反序列化的过程,具体操作可参照此篇博客:
JsonDeserializer——Gson自定义解析类型错误的字段
这篇博客很好地总结了Gson在解析Json同字段不同类型时的对策,事实上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;
}
}
使用ViewPager来管理Fragment,会同时导致这些Fragment执行onCreateView
,即提前加载好所有的数据,这会让应用接收庞大的数据导致卡顿。为了解决这个问题,需要实现ViewPager的懒加载,即切换到这个Fragment时再获取其数据,保证应用的流畅度。说来惭愧,这项优化其实作者还没有进行落实,还在研究当中,力求寻找最优的方法,感兴趣的读者也可以查询相应资料。
Glide是比较常用的图片加载工具,底部封装了三级缓存等大量图片加载优化。Glide还提供了大量的工具方法,其中包括有占位图的设置,即在图片还未加载出来时先放置占位图,这样可以提高用户的体验度,提高了应用的可用性。
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等样式,生态圈也较为成熟,基本上可以做到大部分组件“拿来即用”,所以大致上没有遇到什么难点,就暂且略过这部分了。
混合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开发时遇到的一些主要难点:
在进行操作时,若界面上没有显示出来对应的数据,多半是没有调用setState()来进行刷新,这是由于Flutter的特性所致。例如在点击底部导航栏时,由于数据没有发生变化,界面同样也不会发生变化,就不会产生界面切换的效果,这时候就需要调用动态调用setState(),来通知这个Widget状态已经发生了改变,需要重绘界面。
在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;
由于获取网络数据并解析之后放入列表中是耗时操作,需要将该操作放入异步模型中执行,防止阻塞主线程。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());
},
尽管使用了Future来完成异步操作,在数据读取的同时还是会导致主界面卡顿,这将会降低应用的使用感,Flutter提供了computer()来实现将操作的线程进行隔离的操作,与之对应的还有isolate
。但是isolate要相对重量级一些,这里使用computer()即可达到目标。
经过一周对三种App开发模式的学习并实践,作为Android开发人员,作者认为原生App开发的基础还是必要的,其中涉及了许多高深的原理需要去理解。如果对前端语言有些基础的话,可以尝试WebApp。而对于混合App,由于需要一定的学习成本,建议对原生App的开发相当熟悉之后再去尝试,不然会对其中的许多概念感到模糊。