底部弹出控件 - Fragment 实现
1 | import android.os.Bundle |
1 | import android.os.Bundle |
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架Avro就使用了Netty作为底层通信框架,其他如Strom还有业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。
通过对Netty的分析,我们将它的优点总结如下。
◎ API使用简单,开发门槛低;
◎ 功能强大,预置了多种编解码功能,支持多种主流协议;
◎ 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
◎ 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
◎ 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
◎ 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
◎ 经历了大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。
正是因为这些优点,Netty逐渐成为了Java NIO编程的首选框架。
那Netty到底是什么?官方解释:Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。Netty就是一个对Jdk的Nio进行封装的一个框架。
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.
在开始了解 Netty 是什么之前,我们先来回顾一下,如果我们需要实现一个客户端与服务端通信的程序,使用传统的 IO 编程,应该如何来实现?
我们简化下场景:客户端每隔两秒发送一个带有时间戳的 “hello world” 给服务端,服务端收到之后打印。
为了方便演示,下面例子中,服务端和客户端各一个类,把这两个类拷贝到你的 IDE 中,先后运行 IOServer.java
和IOClient.java
可看到效果。
下面是传统的 IO 编程中服务端实现
IOServer.java
1 | /** |
Server 端首先创建了一个serverSocket
来监听 8000 端口,然后创建一个线程,线程里面不断调用阻塞方法 serversocket.accept();
获取新的连接,见(1),当获取到新的连接之后,给每条连接创建一个新的线程,这个线程负责从该连接中读取数据,见(2),然后读取数据是以字节流的方式,见(3)。
下面是传统的IO编程中客户端实现
IOClient.java
1 | /** |
客户端的代码相对简单,连接上服务端 8000 端口之后,每隔 2 秒,我们向服务端写一个带有时间戳的 “hello world”。
IO 编程模型在客户端较少的情况下运行良好,但是对于客户端比较多的业务来说,单机服务端可能需要支撑成千上万的连接,IO 模型可能就不太合适了,我们来分析一下原因。
上面的 demo,从服务端代码中我们可以看到,在传统的 IO 模型中,每个连接创建成功之后都需要一个线程来维护,每个线程包含一个 while 死循环,那么 1w 个连接对应 1w 个线程,继而 1w 个 while 死循环,这就带来如下几个问题:
为了解决这三个问题,JDK 在 1.4 之后提出了 NIO。
关于 NIO 相关的文章网上也有很多,这里不打算详细深入分析,下面简单描述一下 NIO 是如何解决以上三个问题的。
NIO 编程模型中,新来一个连接不再创建一个新的线程,而是可以把这条连接直接绑定到某个固定的线程,然后这条连接所有的读写都由这个线程来负责,那么他是怎么做到的?我们用一幅图来对比一下 IO 与 NIO
如上图所示,IO 模型中,一个连接来了,会创建一个线程,对应一个 while 死循环,死循环的目的就是不断监测这条连接上是否有数据可以读,大多数情况下,1w 个连接里面同一时刻只有少量的连接有数据可读,因此,很多个 while 死循环都白白浪费掉了,因为读不出啥数据。
而在 NIO 模型中,他把这么多 while 死循环变成一个死循环,这个死循环由一个线程控制,那么他又是如何做到一个线程,一个 while 死循环就能监测1w个连接是否有数据可读的呢? 这就是 NIO 模型中 selector 的作用,一条连接来了之后,现在不创建一个 while 死循环去监听是否有数据可读了,而是直接把这条连接注册到 selector 上,然后,通过检查这个 selector,就可以批量监测出有数据可读的连接,进而读取数据,下面我再举个非常简单的生活中的例子说明 IO 与 NIO 的区别。
在一家幼儿园里,小朋友有上厕所的需求,小朋友都太小以至于你要问他要不要上厕所,他才会告诉你。幼儿园一共有 100 个小朋友,有两种方案可以解决小朋友上厕所的问题:
这就是 NIO 模型解决线程资源受限的方案,实际开发过程中,我们会开多个线程,每个线程都管理着一批连接,相对于 IO 模型中一个线程管理一条连接,消耗的线程资源大幅减少
由于 NIO 模型中线程数量大大降低,线程切换效率因此也大幅度提高
IO 读写是面向流的,一次性只能从流中读取一个或者多个字节,并且读完之后流无法再读取,你需要自己缓存数据。 而 NIO 的读写是面向 Buffer 的,你可以随意读取里面任何一个字节数据,不需要你自己缓存数据,这一切只需要移动读写指针即可。
简单讲完了 JDK NIO 的解决方案之后,我们接下来使用 NIO 的方案替换掉 IO 的方案,我们先来看看,如果用 JDK 原生的 NIO 来实现服务端,该怎么做
前方高能预警:以下代码可能会让你感觉极度不适,如有不适,请跳过
NIOServer.java
1 | /** |
相信大部分没有接触过 NIO 的同学应该会直接跳过代码来到这一行:原来使用 JDK 原生 NIO 的 API 实现一个简单的服务端通信程序是如此复杂!
复杂得我都没耐心解释这一坨代码的执行逻辑(开个玩笑),我们还是先对照 NIO 来解释一下几个核心思路
serverSelector
负责轮询是否有新的连接,clientSelector
负责轮询连接是否有数据可读clientSelector
上,这样就不用 IO 模型中 1w 个 while 循环在死等,参见(1)clientSelector
被一个 while 死循环包裹着,如果在某一时刻有多条连接有数据可读,那么通过 clientSelector.select(1)
方法可以轮询出来,进而批量处理,参见(2)其他的细节部分,我不愿意多讲,因为实在是太复杂,你也不用对代码的细节深究到底。总之,强烈不建议直接基于JDK原生NIO来进行网络开发,下面是我总结的原因
正因为如此,我客户端代码都懒得写给你看了==!,你可以直接使用IOClient.java
与NIOServer.java
通信
JDK 的 NIO 犹如带刺的玫瑰,虽然美好,让人向往,但是使用不当会让你抓耳挠腮,痛不欲生,正因为如此,Netty 横空出世!
那么 Netty 到底是何方神圣? 用一句简单的话来说就是:Netty 封装了 JDK 的 NIO,让你用得更爽,你不用再写一大堆复杂的代码了。 用官方正式的话来说就是:Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。
下面是我总结的使用 Netty 不使用 JDK 原生 NIO 的原因
看不懂没有关系,这些我们在后续的课程中我们都可以学到,接下来我们用 Netty 的版本来重新实现一下本文开篇的功能吧
首先,引入 Maven 依赖,本文后续 Netty 都是基于 4.1.6.Final 版本
1 | <dependency> |
然后,下面是服务端实现部分
NettyServer.java
1 | /** |
这么一小段代码就实现了我们前面 NIO 编程中的所有的功能,包括服务端启动,接受新连接,打印客户端传来的数据,怎么样,是不是比 JDK 原生的 NIO 编程优雅许多?
初学 Netty 的时候,由于大部分人对 NIO 编程缺乏经验,因此,将 Netty 里面的概念与 IO 模型结合起来可能更好理解
boss
对应 IOServer.java
中的接受新连接线程,主要负责创建新连接worker
对应 IOServer.java
中的负责读取数据的线程,主要用于读取数据以及业务逻辑处理然后剩下的逻辑我在后面的系列文章中会详细分析,你可以先把这段代码拷贝到你的 IDE 里面,然后运行 main 函数
然后下面是客户端 NIO 的实现部分
NettyClient.java
1 | /** |
在客户端程序中,group
对应了我们IOClient.java
中 main 函数起的线程,剩下的逻辑我在后面的文章中会详细分析,现在你要做的事情就是把这段代码拷贝到你的 IDE 里面,然后运行 main 函数,最后回到 NettyServer.java
的控制台,你会看到效果。
使用 Netty 之后是不是觉得整个世界都美好了,一方面 Netty 对 NIO 封装得如此完美,写出来的代码非常优雅,另外一方面,使用 Netty 之后,网络通信这块的性能问题几乎不用操心,尽情地让 Netty 榨干你的 CPU 吧。
资料:
1、选择Netty作为基础通信框架 :
https://www.cnblogs.com/mxyhws/p/5500425.html
2、Nginx/Netty/ZeroMQ网络模型:
1 | package com.dench.webviewlib |
情景一:如果访问的页面中要与Javascript交互,则webView必须设置支持Javascript
1 | val webSettings = webView.settings |
情景二:在安卓5.0之后,默认不允许加载http与https混合内容,需要手动设置
1 | // 在安卓5.0之后,默认不允许加载http与https混合内容,需要手动设置 |
其他情景: 设置domStorageEnabled
和背景色需要验证,暂时没遇到。
由于设置了 android:layerType="software"
导致的 webview 卡顿。
关于三个layerType属性介绍:https://blog.csdn.net/a345017062/article/details/7478667
解决:
开启 Activity 硬件加速 ``android:hardwareAccelerated=”true”, 并且设置 webview 的
android:layerType=”none”`。
可以按如下方式声明特定的 Maven 或 Ivy 代码库:
1 | allprojects { |
随着项目采用模块化,组件化开发,moudle 的个数也会随着增加,统一管理配置gradle就显得比较重要了。
1、在 project 根目录创建一个 config.gradle
文件
1 | ext { |
2、在 Project 根目录下的 build.gradle
添加apply
1 | apply from: 'config.gradle' |
3、在相应Moudle中调用
1 | android { |
创建产品变种与创建构建类型类似:将其添加到构建配置中的 productFlavors
代码块并添加所需的设置。产品变种支持与 defaultConfig
相同的属性,这是因为,defaultConfig
实际上属于 ProductFlavor
类。这意味着,您可以在 defaultConfig
代码块中提供所有变种的基本配置,每个变种均可更改其中任何默认值,如 applicationId
。
1 | android { |
1、Gradle 要求:
在所有构建变体之间共享的所有内容创建 main/
源代码集和目录。
将“debug”构建类型特有的 Java 类文件放在 src/debug/java/
目录中。
1 | debug |
依次转到 MyApplication > Tasks > android,然后双击 sourceSets。Gradle 执行该任务后,系统应该会打开 Run 窗口以显示输出。
2、更改默认源代码集配置
1 | android { |
1 | dependencies { |
1、在项目的根目录下创建一个名为 keystore.properties
的文件,并使其包含以下信息:
1 | storePassword=myStorePassword |
2、在 build.gradle
文件中,按如下方式加载 keystore.properties
文件(必须在 android 代码块前面):
1 |
|
3、输入存储在 keystoreProperties
对象中的签名信息:
1 | android { |
如需从环境变量获取这些密码,请添加以下代码:
1 | storePassword System.getenv("KSTOREPWD") |
1 | android { |
or
1 | outputFileName = "app_v${versionName}.${buildTime}_${flavorName}_${buildType.name}.apk" |
1、如果您需要将变量插入在 build.gradle
文件中定义的 AndroidManifest.xml
文件,可以使用 manifestPlaceholders
属性执行此操作。此属性采用键值对的映射,如下所示:
1 | android { |
2、您可以将某个占位符作为属性值插入清单文件,如下所示:
1 | <intent-filter ... > |
在构建时,Gradle 将生成 BuildConfig
类,以便应用代码可以检查与当前构建有关的信息。您也可以从 Gradle 构建配置文件中使用 buildConfigField()
方法将自定义字段添加到 BuildConfig
类中,然后在应用的运行时代码中访问这些值。同样,您也可以使用 resValue()
添加应用资源值。
1 | def buildTime = new Data().format("yyyyMMddHHmm", TimeZone.getTimeZone("GTM+08:00")) |
在应用代码中,您可以按如下方式访问属性:
1 | Log.i(TAG, BuildConfig.BUILD_TIME); |
1 | allprojects { |
1.使用 View Binding 先要在Module 的 build.gradle
文件注册
1 | android { |
2.会根据布局文件,编译之后自动生成对应的Binding class,可以在Activity 和 Fragment 直接调用 inflate 使用
1 | private var _binding: ResultProfileBinding? = null |
1.在 build.gradle
文件中开启lib
1 | android { |
2.布局文件start with a root tag of layout
followed by a data
element
1 | <layout xmlns:android="http://schemas.android.com/apk/res/android" |
3.在Activity中使用
1 | override fun onCreate(savedInstanceState: Bundle?) { |
在 Fragment
, ListView
, or RecyclerView
adapter, you may prefer to use the inflate()
1 | val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) |
View binding and data binding both generate binding classes that you can use to reference views directly. However, view binding is intended to handle simpler use cases and provides the following benefits over data binding:
Conversely, view binding has the following limitations compared to data binding:
Because of these considerations, it is best in some cases to use both view binding and data binding in a project. You can use data binding in layouts that require advanced features and use view binding in layouts that do not.
自定义Notification 实现:
1 | // RemoteViews for notification |
1.使用 NotificationCompat
兼容各个版本差异性
2.RemoteViews
布局文件不支持 constraintlayout
,切记
3.在SDK 26之后必须要绑定Channel,所以通知要先创建Channel
1 | fun checkAndCreateChannel( |
Update your browser to view this website correctly.&npsb;Update my browser now