首先作为一名刚毕业从业不久的Android宝宝来说,第一次独立开发完成后我觉得很有必要梳理一下,一方面为了在以后的开发路上如果遇到踩过的坑有个地方可以参考。另一方面为了以后矫情怀念的时候拿出来看一看。哈哈~废话不多说,进入正题!
这篇文章我总结了在开发中运用到的技术和设计思想,并且归纳了在实际开发中遇到的一些问题和解决方法,如果大家有遇到此类问题也可作参考,文章有不对的地方也欢迎大家指正。谢谢!
一、设计模式
在本次开发中我结合了databinding 实现了M-V-VM的架构,如图:
关于M-V-VM模式,我目前的理解是:model层更像一个“仓库”,它负责存放各种数据(例如:bean和网络请求后获取到的JSON数据等),而ViewModel层(业务逻辑的处理等)更像是一个“原材料加工厂”,它负责的是把"仓库"里面的东西拿出来经过一定的加工后然后投放给“市场”(View)展示给消费者(用户)。反过来,当“市场”(View)有新的需要时它将会通知“加工厂”(ViewModel),“加工厂”会去“仓库”(Model)拿相应的数据加工后返回给“市场”(View),这样就达到了界面——业务——数据分离的效果。并且是符合面向对象的SRP(Single responsibility principle)原则 。
这样的例子不知道大家能不能明白,我当时看了很多关于设计模式的资料,产生了一个疑惑:“M-V-VM模式虽然让View和Model层解耦,互不可知,但是View和ViewModel不是也耦合了吗?”带着这个疑惑,下面要开始介绍databinding啦,它的出现可以解决我们这个疑惑,如果没了解过databinding的同学可以点链接去看这篇文章,这也是我当时学习的文章,入门不错哟~
二、在项目中使用的技术
1.databinding
databinding将界面和ViewModel在不耦合的情况下绑定起来,原因是为什么呢?因为界面引用的过程是布局文件中进行的,并且绑定操作也是发生在View层,ViewModel中没有关于View中的任何引用。那么有的童鞋这时候就有疑问了,没有引用,那我从model层拿到数据之后如果要动态显示在界面上呢?怎么办?例如弹出Toast框,没有context的话,我怎么弹啊?我可是上过小学的人!!小编不要骗我好吗!!这位同学问得好,差一点就让小编我无言以对。不过这时候我就要引出databinding的另一个牛逼的属性啦,那就是@BindingAdapter,它的作用是什么呢?大家都知道databinding可以使数据和视图进行绑定,当数据发生改变时,可以使用notifyPropertyChanged()来刷新被@Bindable标记的变量。这是databinding的一大功能。但是databingding还有另外一个很强大的功能就是@BindingAdapter,它到底强大在哪呢?请看VCR!!
@BindingAdapter({"erroInfo"})
public static voidtoastErroInfo(View view,String erroInfo) {
if(erroInfo !=null) {
CustomToast.makeText(view.getContext(),erroInfo,Toast.LENGTH_SHORT).show();
}
}
这是一段我封装的关于弹出错误信息提示的代码,如果你看过我上面介绍的databinding使用文章的话,你应该懂得他的意思。在我用@BindingAdapter这个方法后,我只需要在布局文件控件的属性里面加上一句:app:erroInfo=“{viewModel.erroInfo}”,这样我就可以轻易的弹出Toast框而又不会造成和viewModel耦合的问题了,因为我执行的操作都是在共有类里面的一个公有方法,无论是哪个View和ViewModel绑定,只要你在所在布局文件的根目录下设置了自定义属性它把你要Toast的信息传过来,他就能帮你显示出来,就是辣么神奇。
上面的例子只是一个小小的部分,databinding还支持大量的操作符,这一类问题有一些也可以用操作符就可以解决的。在这里我就不一一举例了,大家可以先去看文章。不懂得地方可以留言我们一起探讨一起进步~
2.RxJava
对于RxJava想必大家也不陌生吧,从一开始只有小众知道慢慢的到现在的主流。如果有不了解的童鞋可以去看这篇文章 。本人对RxJava的接触不太久,很多方面并没有深入了解,所以只是表达一下到目前为止使用过RxJava的感受:”它的写法从上到下,一条链式的调用,逻辑清晰,可读性非常强, 并且还可以自由的切换线程。” 在这个项目中我只是把他运用在Model层里面的网络请求模块中,但RxJava的强大远不止这些,还有非常多非常多东西得等我们去学,具体也不一一介绍了,大家可以看看推荐的那篇文章。
3.Retrofit 2.0
对于配合RxJava,我相信Retrofit2.0是最好的选择了。推荐大家可以去看这篇文章《Retrofit 2.0:有史以来最大的改进》 ,这也是我当时学习Retrofit看的一篇文章。
4.Picasso
支持二级缓存、加载错误占位图、支持裁剪等等,仅仅一行代码就可实现图片异步加载。关于学习文章的话网上数不胜数,这里我推荐我自己当时学习的一篇《picasso-强大的Android图片下载缓存库》。
5.其他常用的第三方库(百度地图、科大讯飞、zxing、百度推送等)
这些第三方库我就不一一介绍了,大家可以去相应的官方网站观看官方文档,非常详细,没有什么难度。
三、项目中遇上的“坑”
做这个项目的时候遇上了不少坑,也查阅了很多资料。最后总结出下面的一些问题及当时的解决办法,一方面给我以后开发提供参考,另一方面大家如果也遇上了这类问题也可以给大家一些解决思路。好了,不说了,先点菜吧!!
(1)百度推送绑定成功但是无推送(大意篇)
问题描述:这个问题说起来有点搞笑,当时我们后台给出的文档中,要求登录时要上传一个设备号的参数,我当时的代码获取的是手机本身的唯一设备号而不是百度推送绑定成功后返回的channelId,后面发现百度推送回调方法onBind()中有一个channelId的参数,才猛然发现原来是这个问题,后面把这个参数传给后台问题就解决了,这个显然是我大意造成的,希望大家以此为戒哈哈!
解决办法:将onBind()方法中的channelId作为参数上传给后台
(2)RecycleView中item的点击事件
问题描述: 因为以前使用的都是ListView,但看了一些RecycleView的介绍,感觉它比ListView更方便也更灵活。所以作为一个有探索精神的Android宝宝,所以我也决定使用一下RecycleView到项目中,一开始用的很顺利,后面涉及到item点击事件的时候,我想当然的跟ListView一样想setOnItemClickListener()把子条目的点击事件给设置出来,可是找了半天发现竟然没有!!!然后上网一查资料发现这是需要自己定义的,所以自己动手实现了一次。
解决办法:首先在adapter中设置一个onClickItem接口和一个setOnclickItem(ItemOnclickListener listener)方法,然后在ViewHolder中继承点击事件监听,实现接口即可。下面我贴出关键代码:
public interface MyItemClickListener {
public voidonItemClick(View v, intpostion);
}
//设置item监听
public void setOnItemClickListener(MyItemClickListener listener) {
this.mlistener= listener;
}
这段代码是在adapter中定义的,接下来贴出内部类ViewHolder中的关键代码:
public void onClick(View view) {
if(mlistener!=null) {
mlistener.onItemClick(view,getLayoutPosition());
}
}
实现之后调用方法的方法为:
adapter.setOnItemClickListener(new MyItemClickListener({
public void onItemClick(View v, intpostion) {
// 想实现的Item功能
});
(3)关于PullToRefreshListView中的onRefreshComplete()方法无效
问题描述:因为我在这个项目中其他页面也用到了onRefreshComplete()方法,但是发现并没有出现这个问题,但是唯独其中一个页面使用这个方法无效,后面在我对比跟其他页面的不同之处,找到了一点蛛丝马迹(柯南背景音乐响起~):首先因为这个页面的数据是已经获取好并且存储在本地的数据,之前那些页面的数据是临时从网络获取的。这样的话就会产生一个问题“加载过快,数据来不及显示”。那么怎么办呢?
解决办法:
我们可以延迟一秒左右,在调用onRefreshComplete()方法。代码如下:
mBinding.lvCardDetail.postDelayed(newRunnable() {
@Override
public void run() {
mBinding.lvCardDetail.onRefreshComplete();
}},1000);
注意:要在setAdapter后面执行,不然是无效的!
(4)app在Android 5.0以下的手机崩溃
问题描述:在项目终于要上线的时候,进入测试阶段,发现app在Android5.0以下手机运行直接崩溃。而且报的异常也是奇怪(当时报的是xml解析异常和找不到类的异常),关键我的那些代码都好好的呀!在我查阅了很多资料依然没有解决问题的时候,万念俱灰下的我最后抱着死马当活马医的心态点开了最后搜索的一篇文章,最后也就是这篇文章让我恍然大悟。
解决办法:
因为我项目的总方法数超过了65536这个限制,所以Android 5.0之前的版本使用的Dalvik运行时执行应用程序代码。默认情况下,Dalvik的限制的应用程序,每APK一个classes.dex字节码文件。为了解决这个限制,可以使用multidexsupportlibrary,成为您的应用程序的主DEX文件的一部分,然后设法获得了额外的DEX文件和它们所包含的代码。
而在Android 5.0以及更高版本使用的是ARTruntime,可以支持原生从APK文件中加载多个dex文件。ART进行预编译的应用程序安装时它会扫描类(..N).dex文件,并通过Android设备编译成一个单一的.oat文件执行,所以可以正常运行。
(5)部分新款Android手机打开地图崩溃
问题描述:测试时发现,部分新款Android手机打开地图直接崩溃,但是大部分手机正常。这让我很苦恼,当时拿着问题手机和正常运行手机对比后发现一个共性,那就是问题手机都是比较新款的手机。在网上一查资料发现,这是由于新款手机读取不到我项目中lib文件夹中的so文件。原因是:没有找到对应的cpu架构文件,读取失败。
解决办法:将百度地图sdk更换为最新版,然后在project文件下的lib文件添加:arm64-v8a、x86、x86_64文件并把百度地图sdk对应的so文件也放入相应的文件夹中。重新运行,正常~
(6)Android 6.0及更高版本系统拍照崩溃
问题描述:这个问题暴露的比较明显,Android 6.0以上的机型均出现此问题,查资料后发现:在Android 6.0 以后除了在Manifests上声明权限,有部分危险权限还需要在代码中动态添加权限。
解决办法:下面我举的是关于动态添加拍照权限的核心代码,其它权限也是大同小异。
<1>调用拍照前,检查权限
//检查权限
if(ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
//如果没有授权,则请求授权
ActivityCompat.requestPermissions(this, newString[]{Manifest.permission.CAMERA},MY_PERMISSIONS_REQUEST_CALL_CAMERA);
}else{
//有授权,直接开启摄像头
callCamera();
}
<2>在Activity中调用onRequestPermissionsResult(int requestCode,@NonNullString[] permissions,@NonNullint[] grantResults)方法。
//判断请求码
if(requestCode ==MY_PERMISSIONS_REQUEST_CALL_CAMERA) {
//grantResults授权结果
if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//成功,开启摄像头
callCamera();
}else{
//授权失败
Toast.makeText(this,"Permission Denied",Toast.LENGTH_SHORT).show();
}
return;
这是其中的一个示例代码,大家可以去参考这篇文章 ,里面具体的介绍了此类问题的解决办法。
四、总结
这个项目中,许多的错误本是可以避免的,从而造成效率不升反降。下面我将会列举在下个项目中自己要改进的点,大家也可作参考:
1.布局文件中不要出现中文,统一放到res/values/strings下管理(重要指数:★★★)
其实为什么要这样做呢,因为如果后期如果要改动文字说明或者复用文字说明可以直接调用,而不用一遍又一遍的写。当然,一开始直接写在xml布局文件可能觉得更加方便。可大家想一想,如果有20条文字说明是一样的并且分布在不同的页面,大家一遍又一遍的敲即使不累,可哪天产品过来说要全部修改文字说明,这时候是不是要一个页面一个页面的修复了?(哈哈~这时候蛋碎了吧!知道错了吧?)
2.代码中如果出现数字请统一声明全局变量来管理。(重要指数:★★★★)
大家如果不懂什么意思我贴出代码来大家瞬间就明白了:
(1)设想一下如果我想在一个集合里面删除某一下标的数据,首先我可以这么做:
String[] arr=new String[“a”,“b”,“c”];
List<String> mList=new ArrayList<>();
//将数组赋值到集合中
for(int i=0;i<arr.length();i++){
mList.add(arr[i]);
}
//点击按钮删除第二个下标
deleteButton.setOnclickListenner(new onClickListenner(){
public void onClick(View v){
mList.remove(1);
}
});
或者我也可以这么做
String[] arr=new String["a","b","c"];
List mList=new ArrayList<>();
int delete_position=1;
//将数组赋值到集合中
for(int i=0;i<arr.length();i++){
mList.add(arr[i]);
}
//点击按钮删除第二个下标
deleteButton.setOnclickListenner(new OnclickListenner(){
public void onClick(View v){
mList.remove(delete_position);
}
});
可能从上面代码看大家觉得两种其实差不多,但是如果一个类里面一旦有很多个地方都需要删除这个下标数据的时候,两种代码的差别就出来了。第一种方式你需要在每个地方都修改下标,如果是你刚开发不久可能还记得哪些地方有牵连,一旦久了的话如果有一个地方忘了修改,那bug君就要出来搞事了。第二种方式只需要轻轻松松在定义好的delete_position上修改那就轻松多了!
3.开发前务必把页面逻辑梳理好,画好完整的流程图在开始开发。(重要指数:★★★★★)
(开发前务必把页面逻辑梳理好,画好完整的流程图在开始开发!开发前务必把页面逻辑梳理好,画好完整的流程图在开始开发!开发前务必把页面逻辑梳理好,画好完整的流程图在开始开发!重要的事情说三遍!!!)小编深受其害。因为刚开始开发,很多时候了解业务逻辑之后大概梳理一下就开始哒~哒~哒~哒~的敲代码,可是敲到一半发现哎哟~不对,有个地方出现了问题,当时没考虑到。然后就开始删代码,删到一些地方的时候又发现原来的代码好像可以,只需要加一点点条件就行了,然后又开始敲~效率就在这里变得越来越低了!
所以这里大家一定要注意,了解业务之后首先在脑子大概的梳理一下然后画出流程图,画好之后然后开始完善流程图,尽可能的把各种情况想清楚,画完之后在开始开发,这样子开发起来才有方向感,可以避免走很多弯路!
4.开发前,把整个app业务了解透彻,抽取共性。然后开始开发(重要指数:★★★★★)
这条和上条类似,不过这条可能是更加宏观一点,贯穿了整个app。在开发前如果把app的业务了解透彻,抽取出共性,建立基类,开发时候就可以直接在基类里面拓展,这样可以省去十分多不必要的麻烦和少敲许多冗余的代码。不过在前期规划上可能要下一番功夫,但是为了代码的简洁和优雅,何乐而不为呢?
5.尝试使用(MVP+Databinding+Dagger2+Retrofit 2.0+RxJava)(重要指数:★★★★)
因为当时项目比较赶,所以没有学习Dagger2,目前正在学习并准备将这套模式运用在下个项目上。这套架构也是目前Android的主流,作为一个有求知精神的宝宝,怎么可能不学习呢?
6.尝试使用Kotlin语言开发(重要指数:★★)
如果没有了解Kotlin语言的朋友可以去看一下这篇文章 。因为Android是基于Java语言开发的,Kotlin这门语言支持一键java代码转换成Kotlin,有Java基础的朋友上手并不难。目前本人也准备上手这门语言并运用到下个项目中,有兴趣的童鞋可以了解了解~
7.拥有一个机械键盘(重要指数:★★★★★★★★★★)
这条其实是搞笑的~大家可以忽略!!不过拥有一个机械键盘敲代码还是十分有feel的!有条件的童鞋还是可以考虑的哈哈。