Hexo


  • 首页

  • 归档

  • 标签

MVC、MVP、MVVM

发表于 2018-06-13 | | 阅读次数

MVC :
Model View Controller
Model 负责数据交互 android 中的 View 和 Controller 都是指 Activity,Activity 负责了
ui 显示和 业务逻辑代码。

MVC 缺点:当业务规模比较大时,Controller 代码比较臃肿。一个 Activity 代码成千上万行的时候
非常不便于阅读、维护。
适用场景:规模比较小的项目

MVP :
Model View Presenter
Model 负责数据交互 android 中的 View 指 Activity,Activity 负责了
ui 显示,Presenter 是指跟 android api 无关的业务逻辑代码。
Presenter 在中间,通过 View 接口和 View 进行交互

MVP 优点:View 层只负责 ui 显示,减少了耦合度。
MVP 缺点:View 和 Presenter 互相引用并互相回调,代码不美观。当 View 发生变化时,Presenter
可能做相应的变化(改变更新 ui 的方法,改变回调的方法等),view 和 Presenter 之间还是存
在一定的耦合度,导致代码更改量比较大。

MVVM:
View ViewModel Model
ViewModel 不再持有 View 的引用,通过绑定关系自动更新 ui ,ui的变化也自动反馈到
ViewModel

MVVM 优点:
低耦合度
MVVM模式中,数据是独立于UI的,ViewModel只负责处理和提供数据,UI想怎么处理数据都由UI自己决定,ViewModel 不涉及任何和UI相关的事也不持有UI控件的引用,即使控件改变(TextView
换成 EditText)ViewModel 几乎不需要更改任何代码,专注自己的数据处理就可以了。

TCP与UDP相关

发表于 2018-06-03 | | 阅读次数

TCP 协议和 UDP 协议都属于传输层的协议。传输层是网络分层的概念。按照TCP/IP五层模型的协
议,可分为以下 5 层:应用层、传输层、网络层、数据链路层、物理层。从一台主机的应用程序
A 发送数据到另一台的主机上的应用程序 B 需要经历各层协议。每层协议完成不同的功能:
物理层简单的理解是跟物理设备打交道的。它规定了网络连接时所需接插件的规格尺寸、引脚数量
和排列情况;
数据链路层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。
网络层的作用是找到目标主机的地址。
传输层的作用是把数据发送到目标主机的目标应用程序。
应用层的作用是为操作系统或网络应用程序提供访问网络服务的接口。

UDP 和 TCP 区别:

  1. TCP协议可靠;UDP协议不可靠
  2. TCP协议是面向连接;UDP协议采用无连接
  3. TCP协议负载较高,采用虚电路;UDP采用无连接
  4. TCP协议的发送方要确认接收方是否收到数据段(3次握手协议)
  5. TCP协议采用窗口技术和流控制
    UDP 和 TCP 需要根据实际需要来判断需要哪种协议,并不一定 TCP 就是好的。UDP协议的优点是比较简单,缺点是可靠性较差,一旦数据包发出,无法知道对方是否收到。一旦数据包发出,无法知道对方是否收到,对于音频、在线游戏、视频 ,UDP是最好的选择。TCP协议能够确保数据不会遗失。它的缺点是过程复杂、实现困难、消耗较多的资源。

    TCP 建立连接需要三次握手(每次握手实际是发送报文):
    第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
    完成三次握手,客户端与服务器开始传送数据。
    但是为什么一定要进行三次握手呢,一次不行么?两次不行么?知乎上有人通俗地解释了:

三次握手:“喂,你听得到吗?”
“我听得到呀,你听得到我吗?”
“我能听到你,今天balabala……”

两次握手:“喂,你听得到吗?”
“我听得到呀”
“喂喂,你听得到吗?”
“草,我听得到呀!!!!”
“你TM能不能听到我讲话啊!!喂!”
“……”

四次握手:
“喂,你听得到吗?”
“我听得到呀,你听得到我吗?”
“我能听到你,你能听到我吗?”
“……不想跟傻逼说话”

以上部分内容参考网络并结合我本人的理解。如有错误,欢迎指正,谢谢

“进程保活

发表于 2018-05-06 | | 阅读次数

一、进程回收机制

当系统内存资源不足时,android 系统会杀掉不重要的进程来回收内存。根据网上的资料,android 系统中进程可分为以下几种:
1、前台进程(Foreground process)
场景:
某个进程持有一个正在与用户交互的Activity并且该Activity正处于resume的状态。
某个进程持有一个Service,并且该Service与用户正在交互的Activity绑定。
某个进程持有一个Service,并且该Service调用startForeground()方法使之位于前台运行。
某个进程持有一个Service,并且该Service正在执行它的某个生命周期回调方法,比如onCreate()、 onStart()或onDestroy()。
某个进程持有一个BroadcastReceiver,并且该BroadcastReceiver正在执行其onReceive()方法。
用户正在使用的程序,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等极端情况会杀死。

2、可见进程(Visible process)
场景:
拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
拥有绑定到可见(或前台)Activity 的 Service
用户正在使用,看得到,但是摸不着,没有覆盖到整个屏幕,只有屏幕的一部分可见。进程不包含任何前台组件,一般系统也是不会杀死可见进程的,除非要在资源吃紧的情况下,要保持某个或多个前台进程存活

3、服务进程(Service process)
场景
某个进程中运行着一个Service且该Service是通过startService()启动的,与用户看见的界面没有直接关联。
在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死

4、后台进程(Background process)
场景:
在用户按了”back”或者”home”后,程序本身看不到了,但是其实还在运行的程序,比如Activity调用了onPause方法
系统可能随时终止它们,回收内存

5、空进程(Empty process)
场景:
某个进程不包含任何活跃的组件时该进程就会被置为空进程,完全没用,杀了它只有好处没坏处,第一个干它!

假设现在内存不足,空进程都被杀光了,现在要杀后台进程,但是手机中后台进程很多,难道要一次性全部都清理掉?当然不是的,进程是有它的优先级的,这个优先级通过进程的adj值来反映,它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。可以通过cat /proc/进程id/oom_adj可以看到当前进程的adj值

二、进程保活方案,进程保活的基本原理是提高进程的优先级,即如何降低oom_adj的值:

1、开启一个像素的Activity
据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉。这种方案的实现原理就是将进程的优化级提高到了最高等级的前台进程。
2、利用 Notification 提升权限
2.1 方案设计思想
Android 中 Service 的优先级为4,通过 setForeground 接口可以将后台 Service 设置为前台 Service,使进程的优先级由4提升为2,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。

2.2 方案实现挑战
从 Android2.3 开始调用 setForeground 将后台 Service 设置为前台 Service 时,必须在系统的通知栏发送一条通知,也就是前台 Service 与一条可见的通知时绑定在一起的。
对于不需要常驻通知栏的应用来说,该方案虽好,但却是用户感知的,无法直接使用。

2.3 方案挑战应对措施
通过实现一个内部 Service,在 LiveService 和其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。
据说这个微信也用过的进程保活方案,该方案实际利用了Android前台service的漏洞。

三、进程死后拉活的方案

1、JobSheduler
JobSheduler是作为进程死后复活的一种手段,这种方式即使用户强制关闭,也能被拉起来。
2、将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活:
如下两种情况无法拉活:
Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起;
进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。

okhttp-源码解析

发表于 2018-04-30 | | 阅读次数

如何导入 okhttp 源码?

okhttp 源码工程是一个 maven 构建的 java 工程,不支持 android studio 导入(自己折腾了使用了 android studio 好久发现根本编译不过去,网上有的文章说支持 android studio 导入,误人子弟。。比如.
okhttp 源码工程需要使用 IntelliJ IDEA 导入。具体可参考:IntelliJ IDEA导入 OkHttp。
按照该链接编译成功并运行:

源码分析

现在就可以打断点进行调试了。
以 GetExample.java 文件里发送 get 请求为例,来分析一下 okhttp 的执行流程。
GetExample.java:

1
2
3
4
5
6
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}

代码按照如下顺序依次执行:execute() -> getResponseWithInterceptorChain(),重点分析 getResponseWithInterceptorChain():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}

上面的代码展示了 okhttp 核心流程:不同层次的拦截器通过 责任链模式 对 Request 和 Response 进行处理。
如果不清楚责任链模式,可参考责任链模式。按照我的理解,责任链模式是一个事件传递过来以后,会有相应的对象进行处理,具体是哪个或者哪些对象处理需要根据实际情况进行判断。okhttp 里的责任链模式可见如下伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Response getResponseWithInterceptorChain() throws IOException {
// 第一次传入的是 0 代表执行第一个拦截器
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,...);
return chain.proceed(originalRequest);
}
chain.proceed()
{
//递增执行后面拦截器的代码
RealInterceptorChain next = new RealInterceptorChain(index + 1);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
}
interceptor.intercept(Chain chain)
{
chain.proceed(originalRequest);
}

拦截器是 OkHttp 的精髓。
client.interceptors() ,首先加入 interceptors 的是用户自定义的拦截器,比如修改请求头的拦截器等;
RetryAndFollowUpInterceptor 是用来重试和重定向的拦截器,在下面我们会讲到;
BridgeInterceptor 是用来将用户友好的请求转化为向服务器的请求,之后又把服务器的响应转化为对用户友好的响应;
CacheInterceptor 是缓存拦截器,若存在缓存并且可用就直接返回该缓存,否则会向服务器请求;
ConnectInterceptor 用来建立连接的拦截器;
client.networkInterceptors() 加入用户自定义的 networkInterceptors ;
CallServerInterceptor 是真正向服务器发出请求且得到响应的拦截器;
最后在聚合了这些拦截器后,利用 RealInterceptorChain 来链式调用这些拦截器。

上面只是展示了大致的流程,具体还有很多细节值得研究。

camera tip

发表于 2018-04-08 | | 阅读次数

有一个打开手机手电筒的需求,在二维码扫描框下面加个按钮,让用户可以打开手电筒,类似摩拜这样:

想着蛮简单:直接调用系统 Api 就可以了:

1
2
3
4
5
6
7
8
9
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
String[] camerList = manager.getCameraIdList();
manager.setTorchMode("0", true);
} catch (CameraAccessException e) {
Log.e("error", e.getMessage());
}
}

可是运行程序的时候程序报错:

1
2
error: CAMERA_IN_USE (4): setTorchMode:1405: Torch for camera "0" is not
available due to an existing camera user

在网上搜索好久,没有找到什么有用的解决办法,阅读 android 系统源码也没有好的思路。
报错信息提示相机已经被占用了,自己重新看了看需求,猜想可能是二维码扫描框占用了相机.
查看二维码扫描框的代码发现它调用的是老版本的相机 Api : android.hardware.Camera
此 Api 从 android 5.0 开始被废弃了(Deprecated since API level 21)。
终于找到问题了,原来是二维码扫描框用的是老 Api,而开启手电筒使用的是新 Api:
android.hardware.camera2.CameraManager
.将二维码扫描框的代码修改后,手电筒的功能就
正常了

rotateTextView

发表于 2017-09-24 | | 阅读次数

最近碰到一个签名区的需求:要求文字横屏显示,大致效果如下:

一开始想着这不是很简单:只要把系统自带 TextView 旋转 90 度就可以了。自己试了试,效果是实现了,可是碰到了一个问题:旋转后 View 在布局中所占据的宽度并没有改变,导致旋转后 view 上方出现了一段空白。在 github 上面搜了几个项目发现都没有考虑这个问题。那就索性自己撸一个出来。

思路如下:在测量方法 onMeasure() 中改变控件宽高:
setMeasuredDimension(mHeight,mWidth);
在绘制方法 onDraw() 中改变Canvas坐标系,使文字在竖直方向绘制,绘制文字时借助 StaticLayout 使文字能够自动换行。

demo 工程代码地址:demo地址

Android 缩小客户端体积之 so 方向

发表于 2017-06-17 | | 阅读次数

公司的 android 客户端大小已经达到了 60 多 MB,减小客户端体积的大小已经迫在眉睫。研究一下项目代码发现 android 工程 libs 下的 so 文件已经达到了 30 多 MB (压缩前的大小)。于是决定从 so 文件方面进行体积的优化。最终的结果是客户端大小减小了 8.7 MB。下面是详细的过程。

android 工程下有 armeabi 和 armeabi-v7a 两个文件夹,两个文件夹下各自有 10 多个 so 文件。比较两个文件夹下的 so 文件的 hash 值,一比较吓一跳,两个文件夹下有 13 个 so 文件的 hash 值是相同的。也就是说有 13 个 so 文件是重复的。(13 个 so 文件大小总共 15 MB )。

到这里可能有人会想直接把 armeabi 或者 armeabi-v7a 下面重复的 13 个 so 文件删除了。不过这是不可行的。首先看一张图:

图片

该图是说不同的 ABI 文件夹对应不同指令集的 cpu 。v5 架构 cpu 会加载 armeabi 文件夹下面的 so 文件,v7 架构的 cpu 会加载 armeabi-7a 下面的 so 文件夹。假如说 v7 架构的手机需要加载一个 a.so 文件,首先 系统会判断 armeabi-v7a 文件夹是否存在。如果存在直接加载 armeabi-v7a 下面的 a.so ,如果 armeabi-v7a 下面没有 a.so 文件,app 会直接闪退。如果工程下面没有 armeabi-v7a 文件夹,系统自动加载 armeabi 下面对应的 a.so 文件,假如 armeabi 下面也不存在 a.so 的话,程序也会闪退。

从 armeabi 的角度来看,直接删除 armeabi 下面重复的 13 个 so 文件是不可以的,目前公司项目需要支持低配的手机,所以 armeabi 文件夹下面的 so 文件是不能被删除的。
从 armeabi-v7a 的角度来看,直接删除 armeabi-v7a 下面重复的 13 个 so 文件会导致应用闪退。把整个 armeabi-v7a 删除掉也不好, 因为项目中 armeabi-v7a 有两个 so 文件是 支持 v7 架构的,它们与 armeabi 下面对应的 so 文件 hash 值是不一样的,直接删除掉整个 armeabi-v7a 文件夹会影响 app 的性能。

那有什么方案能既能支持老旧的手机设备又不会有性能的损耗呢?手淘和微信客户端的做法为我们提供了一种思路,如下图:




由上图可见微信 、手淘的客户端只保留了 armeabi 文件夹(微信和手淘 armeabi 文件夹下保留了几个 -v7a.so 文件,微信、淘宝客户端是在 app 运行的时候判断手机处理器类型决定是否加载 -v7a.so 文件)。

所以最终的方案是删除掉整个 armeabi-v7a 文件夹,将对性能有影响的 v7a 架构的 so 文件放到 armeabi 文件夹下。假如有个 a.so 文件对性能有较大影响,在 armeabi 文件夹放 a.so 和 a_v7a.so 两个 so 文件。代码这样写:

String abi = Build.CPU_ABI;
if ("arm64-v8a".equals(abi) || "armeabi-v7a".equals(abi)) {
        System.loadLibrary("a_v7a");
    } else {
        System.loadLibrary("a");
    }

按照上面这种方案实施后,公司 android 客户端大小缩减了 8.7 MB,心里真是很有成就感呢^-^。

浏览器网页启动 Android App

发表于 2017-04-05 | | 阅读次数

开发的时候碰到这样一个需求:需要从手机浏览器网页中启动自己的 App ,这样的代码网上一搜就是。于是我就模仿着写:

<intent-filter>
   <data android:scheme="Test"/>
   <action android:name="android.intent.action.VIEW"/>

   <category android:name="android.intent.category.BROWSABLE"/>
   <category android:name="android.intent.category.DEFAULT"/>
 </intent-filter>  

手机本地测试网页 test.html 的代码:

<a href="Test://param">Click</a>

代码这样写应该没有问题了,可是运行发现启动不了自己的 App ,在网上查了好久。观察发现网上定义的 scheme 都是小写字母的形式,感觉问题可能出在 android:scheme=”Test” 。测试把 android:scheme=”Test” 改为 android:scheme=”test” 后,果然成功了。
上网查阅发现,Andoroid 官方要求 scheme 是小写字母的形式:

Note: Scheme matching in the Android framework is case-sensitive, unlike the RFC. As a result, you should always specify schemes using lowercase letters.

123
DaQiang

DaQiang

享受生命的精彩

28 日志
7 标签
© 2021 DaQiang
主题 - NexT.Pisces