Android进阶课学习收获(31~33)

17 篇文章 1 订阅
10 篇文章 1 订阅

第31讲:Android屏幕适配技巧有哪些?

近几年屏幕适配方案的诞生,比如头条适配方案、AutoSize库,以及Android 各种适配控件的退出,是的屏幕适配变得越来越容易。本节总结下屏幕适配的那些技巧。

ContraintLayout

      很多人觉得这个控件要设置各种上下左右的约束条件,但是请相信我,前期在代码里付出的越多,后期需要解决的bug就越少。并且它还能实现其他View容器比较难实现的效果。比如有两个Button分别是Button1和Button2,需求是将Button1位于屏幕中间,并且始终覆盖Button2的坐上半角,UI效果和实现代码如下:

ConstraintLayout还提供了水平和垂直方向的bias属性,这个属性的取值范围是0~1。主要作用是确立View在水平方向或者垂直方向的位置百分比。如下:

<TextView
    ...
    app:layout_constraintHorizontal_bias="0.3"
    app:layout_constraintVertival_bias="0.5"
/>

//这样Textview 会显示在水平方向30%和垂直方向50%的位置。

多dimens基于dp的适配方案

在ConstraintLayout的基础上,我们还可以在res文件夹中创建多套values文件夹,如下所示:

图中value-后的sw指的是small width,也就是最小宽度。Android系统在运行时会自动识别屏幕可用的最小宽度,然后根据识别的结果去资源文件中查找相对应的资源文件中的属性值。手写每个values文件很麻烦,可以借助工具一键生成。具体可以参考这个链接:Android屏幕适配,自动生成不同的dimens.xml

通过ConstraintLayout + 多dimens适配方案,基本上能够将UI布局适配到所有的机型。在此基础上再针对个别UI控件进行适配就基本上上完美了。比如对TextView尽量使用wrap_content,降低在部分手机上文字显示不全的概率,而ImageView则尽量设置固定的宽高,因为我们从服务器上加载的图片宽高一般不固定,或者动态设置ImageView的宽高。

总结:使用ConstraintLayout +  多dimens适配 + 特殊UI的针对性适配,基本满足适配需求。

第32讲:解析动态权限适配遇到的问题

       Android 6.0之前,APP需要的权限会在安装阶段向用户展示,APP运行期间不需要动态判断权限是否已申请。从6.0之后的版本开始,对于部分权限,APP需要在代码中动态申请相应的权限。

     Android权限分2种,分为普通权限和危险权限,这两种权限都需要在AndroidManifest清单文件中声明。普通权限在程序运行期间自动获取,危险权限比如通讯录、图库、位置等这类操作,Android系统要求App主动向用户展示操作所需要的权限,只有用户授权之后才可以进行下一步操作,这样就保证了用户的隐私信息不被轻易窃取。

权限动态申请流程

一次完整的权限申请流程如下图所示:

上述流程中,在调用requestPermission方法申请权限之前,还有一步比较重要的操作:判断是否需要展示shouldShowRequestPermissionRationale。shouldShowRequestPermissionRationale方法会返回以下两种情况:

  • 返回true:用户之前在申请权限操作时,点击了”拒绝“按钮,但是没有选中”Never ask again“选项。因此,我们只需要再次调用requestPermission方法申请权限即可,系统会自动弹出申请权限的对话框。
  • 返回false:有2中情况会返回false,其一是用户重来没有申请过此权限;其二是用户之前选中拒绝,并勾选了”Never ask again“选项。我们可以通过SP来记录是否申请过权限,如果申请过,方法还返回false,则说明用户之前勾选了”Never ask again“,这样直接显示自定义弹窗,告知用户必须通过权限后才能继续进行操作,并给用户提供权限设置页面的入口,如果么申请过,直接掉requestPermission即可。

需要注意的一点是shouldShowRequestPermissionRationale返回true的情况在很多国内厂商的手机中设置了自动屏蔽,也就是没有返回true的情况,比如华为、小米等手机。

权限申请操作封装

APP中会存在很多调用危险权限的代码,如果每一次执行这些代码都复制粘贴申请代码,会显得代码很冗余。因此我们可以将动态申请的操作封装到工程中的某个Util类中,并提供给调用者相应的回调接口。部分核心代码如下:

// PermissionUtils.java

public void requestPermission(Context context, String permission, 
   PermissionRequestListener listener){
   if(shouldAskPermission(context, permission)){
        
 if(ActivityCompat.shouldShowRequestPermissionRationale((AppcompatActivity)context,permission)){
            listener.onPermissionPreviouslyDenied();
  }else{
      if(isFirstTimeAsking(permission)){
        firstTimeAsking(permission,false);
        listener.onFirstRequestPermission();
      } else{
        listener.onPermissionPreviouslyDeniedWithNeverAskAgain();
      }      
  }
    }else{
        listener.onPermissionGranted();
    }     
}


/**
*请求动态权限回调接口
*/
public interface PermissionRequestListener{
    //第一次申请权限
    void onFirstRequestPermission();
    //用户拒绝权限申请,但是并没有选中”不再提示“选项
    void onPermissionPreviouslyDenied();
    //用户拒绝权限申请,并选中”不再提示“选项
    void onPermissionPreviouslyDeniedWithNeverAskAgain();
    //权限已获取
    void onPermissionGranted();
}

最后只需要在BaseActivity中,调用此方法时传入具体实现的PermissionRequestListener即可,如下所示:

public class BaseActivity extends AppCompatActivity{
    ...
    public void requestContactPermission(View view){
        PermissionUtils pu = new PermissionUtils(this);
        pu.requestPermission(this,Manifest.permission.WRITE_CONTACTS, 
          new PermissionUtils.PermissionRequestListener(){
            @Override
            public void onFirstRequestPermission(){}

            @Override
            public void onPermissionPreviouslyDenied(){}

            @Override
            public void 
                onPermissionPreviouslyDeniedWithNeverAskAgagin();
            
            @Override
            public void onPermissionGranted(){}
        } );
    }
}

三方库使用

      对于Permission的动态申请也可以借助开源的三方库来加快开发速度,目前对于Permission动态申请支持比较好的开源库有Dexter、easypermissions、PermissionDispatcher。

      但是使用三方库也具有一定的隐患,因为不同Android版本对Permission的处理并不完全一致,新版本的系统中对权限申请有了更加严格的请求策略。比如在Android 10中,Android系统就增加对外置存储访问的限制,正常情况下我们可以通过如下代码获取外置存储的根路径:

Environment.getExternalStorageDirectory().getAbsolutePath

然后在此目录下创建APP相应的文件夹缓存数据,但是从Android10(API29)开始,APP层已经没有此路径的访问权,无论你在清单文件里加了对应权限还是使用ActivityCompat.requestPermissions动态申请权限都无法实现访问。目前官方提供的临时解决方案就是在AndroidManifest清单文件中添加如下设置:

<application android:requestLegacyExternalStorage="true">
...
</application>

当我们使用三方库处理动态申请权限的话,如果三方库并没有做版本适配,或者做了适配,而我们并没有升级三方库版本,都会造成在Android 10中的设备上处理文件发生的异常。

总结

动态申请权限常用的三个方法:

  • checkSelfPermission  检查某权限是否已申请;
  • requestPermissions    主动发送申请权限的请求;
  • shouldShowRequestPermissionRationale  判断用户之前对申请权限做成的相应动作;

第33讲:对于网络编程,你做过哪些优化?

使用OkHttp框架后,我们可以通过EventListener来查看一次网络请求的详细情况,一次完整的网络请求会包含以下几个步骤:

0,000 callStart
0,027 dnsStart
5,189 dnsEnd
5,359 secureConnectStart
5,907 secureConnectEnd
5,910 connectEnd
5,921 connectionAcquired
5,925 requestHeadersStart
5,930 requestHeadersEnd
5,938 responseHeadersStart
6,181 responseHeadersEnd
6,189 responseBodyEnd
6,233 connectionReleased 

也就是说,一次网络请求的操作时从DNS解析开始,然后建立连接并发送数据到服务器,随后读取从服务器返回的数据,最后将连接释放,一次网络请求操作也就结束了。接下来我们就从DNS解析开始,分析有哪些方面可以做进一步的优化

DNS解析优化

安全方面

        首先是防劫持,我们可以考虑使用HttpDns。HttpDns只是一个概念,并不是一个现有开源库。它与传统的DNS解析的区别在于HttpDns会绕过运营商的DNS服务器,直接与DNS服务器的80端口进行交互,有效防止了域名劫持。

        目前业内主要有第三方厂商提供实现了HTTPDns的SDK,比较普及的是阿里云和腾讯云的HttpDns Service。但这两者的使用具有一定的成本,开发者需要在他们平台注册并获取开发者key,并且部分服务是收费的。

        对普通开发而言,可以考虑使用七牛云提供的免费的happy-dns。实现也比较简单,因为OkHttp已经预留了设置Dns的接口,如下所示:

       在接口Dns中只有一个方法需要实现 ---- lookup,这个方法返回查找到的服务器地址集合。并且OkHttp已经实现了一个默认的DNS解析器,也就是图中红框标识的SYSTEM,它使用java net 包中的InetAddress获取某域名的IP地址集合。

     我们可以自己实现Dns接口,使用Http请求方式实现自己的域名解析器,具体实现就是使用七牛云提供的happy-dns SDK,先添加依赖,然后自己实现Dns类,如下所示:

这样我们就可以在安全方面做到防劫持的功能了。

速度方面

关于DNS解析的速度方面优化,我们可以从以下几个方面突破:

  • IP直连方式     IP直连方式经常会针对不同的开发环境使用,比如针对在qa、staging测试环境下,可以直接配置IP白名单,跳过DNS解析流程,但这样同样需要实现OkHttp 的Dns接口,如下所示:

    有的一线公司也会在线上采用这种方式,但这种方式开发成本较高。因为IP列表是维护在本地,因此需要建立一套IP地址的更新机制。另外IP直连方式摒弃了HTTPS的安全机制,由于HTTPS要求证书绑定域名,因此客户端需要增加额外的代码改造。

  • DNS解析超时  当我们在做网路请求时,如果网络设备切换路由,访问网络出现长时间无响应,很久之后就会抛出UnknownHostException,并且我们再OkHttp中设置connectTimeout属性对DNS的解析不起作用,这种情况下我们可以在自定义的Dns类中做超时判断,如下所示:


     

网络请求缓存优化

       实际上我们有时在做网络请求数据可达优化的时候,经常会不可避免与本地持久化绑定在一起。比如当一次网络请求失败时,我们需要将这次请求保存在本地,并尝试重新发送;或者请求数据成功,我们需要将数据缓存到本地,当下一次请求数据展示UI之前,先将缓存中的数据展示到页面,只有当新的请求返回数据之后,再次刷新页面。

      一般的做法是创建一个数据库Entity类,并根据自家公司的业务逻辑设置公共参数,通常都会有user_id、更新时间update_time等,如下所示:

上图中key表示缓存的标识,插入请求都是根据key操作;value字段用来保存网络请求的数据,当网络请求成功后,将数据以JSON字符串的格式缓存到数据库中。如下:

后续当我们再次执行相同key的网络请求时,就可以先将本地数据库中数据展示到页面,并进行异步请求操作刷新页面。

幂等性

     Http方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。举个例子:当我们点外卖付款时,服务器端扣款成功后发送给客户端一条扣款成功的消息,但如果此时由于网络问题,客户端并没有收到此消息,用户就有可能认为没有付款成功,甚至是尝试再次付款。

    幂等性就是为了解决这种问题,但是它属于代码层面的技巧,并不是一个实体方法或者开源库。实现幂等性需要客户端和服务端协同合作实现。比如原始的付款方法如下:

boolean pay(user_id, amount);

上述代码代表从账户user_id中扣除amount数量的金额,多次操作就会造成同一个user_id账户被扣款多次,可以通过以下方式将付款方式实现幂等:

int create_pay_ticker()
boolean idempotent_pay(ticket_id, account_id , amount)

create_pay_ticker的语义是获取一个服务端生成的唯一的处理号ticket_id,它将用于标识后续的操作。idempotent_pay和pay的区别在于关联一个ticket_id,一个ticket_id表示的操作至多只会被处理一次,这样扣款的操作就符合幂等性了,客户端就可以放心的多次调用。实际上,很多Http请求方法自身就符合幂等性,GET、PUT请求具有幂等性,POST、DELETE请求不具有幂等性。

总结

本节课主要介绍了项目中关于网络优化的几个方向,主要包含以下几点:

  1. DNS解析优化,分安全性和速度提升两方面。
  2. 网络请求数据缓存,对于请求返回的数据需要缓存到本地数据库中。实际上,在某些场景中对于请求对象Request自身也需要做缓存操作。比如”发送埋点“的请求,这样请求失败就将其保存到本地数据库中,当APP重启或者重新接收到连接网络的时候,重新尝试之前失败的请求。
  3. 最后介绍了幂等性,一个在网络架构设计中比较重要的原则。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值