程序员开发避坑指南(移动端篇)-yb体育官方

yb体育官方-亚博平台app下载 我们 服务 网站建设 移动应用 案例 资讯 联系
业务专线:

期待聆听您的声音

15989169178

不忽悠,不作恶,不欺诈;敬天理,存良知,思利他。
服务网点:广州 深圳 佛山 粤西

与我们一起分享美好

程序员开发避坑指南(移动端篇)

发布时间:2022-07-13 发布作者:睿思设计 查阅次数:225次 标签:

01 nstimer造成的内存泄漏问题?


1.1 什么是内存泄漏?


一个对象在引用计数变为0时,系统会回收内存。如果一个本应该被回收的内存,没有被回收(引用计数>0),那么就会造成内存泄漏。


以下代码将造成内存泄漏:


@interface viewcontroller ()
@property (nonatomic, strong) nstimer *timer;
@end

@implementation viewcontroller

- (void)viewdidload {
   [super viewdidload];
   self.timer = [nstimer scheduledtimerwithtimeinterval:1.0 target:self selector:@selector(timertest) userinfo:nil repeats:yes];
}

- (void)timertest
{
   nslog(@"%s", __func__);
}

// 该viewcontroller将不会释放
- (void)dealloc
{
   nslog(@"%s", __func__);
   [self.timer invalidate];
}

1.2 分析如下:


nstimer的scheduledtimerwith


timeinterval方法会传进去一个target,nstimer内部实现会有一个对象强引用传入的对象例如(伪代码如下,示意图如下):

// 伪代码@interface nstimer ()@property (strong, nonatomic) id target;@end // 强引用该对象self.target = target


viewcontroller和nstimer互相引用,此刻viewcontroller的引用计数为2

当一个对象的引用计数变为0时,系统将回收这块内存。


假设对象a在某一时刻需要从内存中释放,那么理应他引用的viewcontroller也应该释放,但是由于viewcontroller内部的nstimer对其有个强引用,最终导致viewcontroller不能释放,从而导致内存泄漏。如图所示:

对象a释放,viewcontroller的引用计数变为1,原本应该引用计数变为0,从而viewcontroller内存泄漏


1.3 如何解决?


按照分析,那应该打破viewcontroller和nstimer双方的强引用。使用弱引用(弱引用不增加对象的引用计数)。


方案1


使用系统代码block块的方法破除循环引用

- (void)viewdidload {    [super viewdidload];        __weak typedef(self) weakself = self;    self.timer = [nstimer scheduledtimerwithtimeinterval:1.0 repeats:yes block:^(nstimer * _nonnull timer) {        [weakself timertest];    }];}

nstimer弱引用viewcontroller,在viewcontroller释放时,nstimer也获得释放,循环链条断开


方案2


使用中间代理层来解决循环引用

// 代理类@interface proxy : nsobject  (instancetype)proxywithtarget:(id)target;// 弱引用target@property (weak, nonatomic) id target;@end@implementation proxy  (instancetype)proxywithtarget:(id)target {    proxy *proxy = [[mjproxy1 alloc] init];    proxy.target = target;    return proxy;}- (id)forwardingtargetforselector:(sel)aselector {    return self.target;}@end@interface viewcontroller ()@property (nonatomic, strong) nstimer *timer;@end@implementation viewcontroller- (void)viewdidload {    [super viewdidload];    self.timer = [nstimer scheduledtimerwithtimeinterval:1.0 target:[proxy proxywithtarget:self] selector:@selector(timertest) userinfo:nil repeats:yes];}- (void)timertest{    nslog(@"%s", __func__);}// 该viewcontroller将不会释放- (void)dealloc{    nslog(@"%s", __func__);    [self.timer invalidate];}

如下图所示,viewcontroller需要强引用nstimer,nstimer内部需要强引用一个target对象,所以可以创建一个代理类来处理这个问题,所以proxy内部有一个弱引用的target对象,viewcontroller调用proxywithtarget把self传入时不会强持有self。


三方之间没有循环引用,最终可以释放对象


02 浅析android的焦点机制


焦点是一个很宽泛的概念,中文释义是比喻问题的关键所在或争论的集中点,在物理学、数学、生活中都有广泛的使用。那么android中的焦点是什么呢?


2.1 android焦点概念


焦点在android中也就是focus,称为focus机制。focus在英文中的释义是:

"the main or central point of something, especially of attention or interest",和中文语义相同。


回到我们android开发中,我们手机屏幕可以同时显示多种多样的内容,那么你的焦点或者说你的注意力在哪个内容上?系统又该如何判断呢?举个例子,当屏幕界面中同时存在多个edittext(输入框)时,你的键盘输入会显示在哪个输入框内呢?亦或是同时显示在所有输入框中?这显然是不合理的,而这时焦点机制就体现了它的意义。对于edittext控件来说,获取到焦点,则意味着激活了和用户的交互,键盘输入的内容会输入到这个edittext上面。


2.2 焦点处理


焦点的处理包含获取焦点、分发焦点、清除焦点等。


2.2.1 获取焦点


让一个view获取焦点直接调用view#requestfocus方法,最终会调用到view#requestfocusnosearch方法,其通过多个条件判断该view是否允许获取焦点,包括是否可见、是否可获取焦点、是否可用,以及在触屏设备中是否允许获取焦点等。

private boolean requestfocusnosearch(int direction, rect previouslyfocusedrect) {
       // need to be focusable
       if (!cantakefocus()) {
           return false;
       }
       // need to be focusable in touch mode if in touch mode
       if (isintouchmode() &&
           (focusable_in_touch_mode != (mviewflags & focusable_in_touch_mode))) {
              return false;
       }
       // need to not have any parents blocking us
       if (hasancestorthatblocksdescendantfocus()) {
           return false;
       }
       if (!islayoutvalid()) {
           mprivateflags |= pflag_wants_focus;
       } else {
           clearparentswantfocus();
       }
       handlefocusgaininternal(direction, previouslyfocusedrect);
       return true;
   }


2.2.2 获取焦点的模式


获取焦点有两种模式,分别是:

普通模式(focusable):允许有普通获取焦点的能力(比如物理键、电视、手表等非触摸的输入方式)


触摸模式(focusableintouchmode):允许有触摸获取焦点的能力。


需要注意的是,在设置允许触摸模式时会默认开启普通模式,注意同时设置这两个属性时不要冲突。


并且由此我们可以得到一条关于焦点的特性:


  • 并不是所有view都可以获取焦点。获取焦点的前提是视图必需要有获取焦点的资格。


2.2.3 分发焦点


上述view在获取焦点时,需要逐级通知它的父view进行焦点处理,清除旧焦点信息并保存新焦点信息,参见viewgroup#requestchildfocus。


通过viewgroup中mfocused(view类型)这个成员来保存具有焦点的子view,并且一直递归下去,为父view判断是否包含焦点(hasfocus)和查找焦点(findfocus)提供了便利。


举例:某个根view a包含b、c两个子view,c下又包含c1、c2两个子view,且c2具有焦点,则c中mfocused保存的是c2,根view a中mfocused保存的则是c。


另外viewgroup也可以获取焦点,参见viewgroup#requestfocus,与view获取焦点逻辑不同,viewgroup获取焦点受策略控制,如下:

focus_block_descendants:this view will block any of its descendants from getting focus, even if they are focusable.

focus_before_descendants:this view will get focus before any of its descendants.

focus_after_descendants:this view will get focus only if none of its descendants want it.


public boolean requestfocus(int direction, rect previouslyfocusedrect) {
       // ...省略
       int descendantfocusability = getdescendantfocusability();
       boolean result;
       switch (descendantfocusability) {
           case focus_block_descendants:
               result = super.requestfocus(direction, previouslyfocusedrect);
               break;
           case focus_before_descendants: {
               final boolean took = super.requestfocus(direction, previouslyfocusedrect);
               result = took ? took : onrequestfocusindescendants(direction,
                       previouslyfocusedrect);
               break;
           }
           case focus_after_descendants: {
               final boolean took = onrequestfocusindescendants(direction, previouslyfocusedrect);
               result = took ? took : super.requestfocus(direction, previouslyfocusedrect);
               break;
           }
           default:
              // ...省略
       }
       if (result && !islayoutvalid() && ((mprivateflags & pflag_wants_focus) == 0)) {
           mprivateflags |= pflag_wants_focus;
       }
       return result;
   }


由此我们也能得到另一些关于焦点的特性:


  • 一个窗口内最多只有一个view具有焦点,或者无焦点。上述在递归分发焦点时,当有view获取焦点后则会退出递归。

  • 根view没有焦点不能说明子view一定没有焦点。子view具有焦点,根view能够感知。


2.2.4 清除焦点


需要我们主动清除焦点的场景其实较少,我们可以通过clearfocus来清除焦点,view和viewgroup的清除逻辑有细微差异,viewgroup会同时清除上诉分发焦点过程中所记录的状态(需区分当前焦点是自己还是子view),最终都会调用view#clearfocusinternal进行真正的清除操作,后面会继续提到焦点清除的问题。


/**
    * clears focus from the view, optionally propagating the change up through
    * the parent hierarchy and requesting that the root view place new focus.
    *
    * @param propagate whether to propagate the change up through the parent
    *            hierarchy
    * @param refocus when propagate is true, specifies whether to request the
    *            root view place new focus
    */
   void clearfocusinternal(view focused, boolean propagate, boolean refocus) {
       if ((mprivateflags & pflag_focused) != 0) {
           mprivateflags &= ~pflag_focused;
           clearparentswantfocus();
           if (propagate && mparent != null) {
               mparent.clearchildfocus(this);
           }
           onfocuschanged(false, 0, null);
           refreshdrawablestate();
           if (propagate && (!refocus || !rootviewrequestfocus())) {
               notifyglobalfocuscleared(this);
           }
       }
   }


问题1:错误启用获取焦点能力导致点击失效


以edittext为例,我们在点击时即会获取焦点,输入框中会显示光标,弹出输入法等。但像button、textview等控件,默认触摸不会获取焦点,如果对此类控件设置了focusableintouchmode=true,就会发现第一次触摸无法响应点击事件,第二次点击才会响应,这是为什么呢?从事件分发机制中寻找线索,看view#ontouchevent中对motionevent.action_up的处理,可以清晰看到up事件的处理会优先处理焦点获取,只有在无焦点变化时才会如我们所想的开始分发点击事件。所以我们在第一次点击时收到的是onfocuschange事件,第二次点击收到的才是onclick事件。


public boolean ontouchevent(motionevent event) {
       // ...省略
       switch (action) {
           case motionevent.action_up:
               // ...省略
               // take focus if we don't have it already and we should in
               // touch mode.
               boolean focustaken = false;
               if (isfocusable() && isfocusableintouchmode() && !isfocused()) {
                   focustaken = requestfocus();
               }
               // ...省略
               if (!mhasperformedlongpress && !mignorenextupevent) {
                   // only perform take click actions if we were in the pressed state
                   if (!focustaken) {
                       // use a runnable and post this rather than calling
                       // performclick directly. this lets other visual state
                       // of the view update before click actions start.
                       if (mperformclick == null) {
                           mperformclick = new performclick();
                       }
                       if (!post(mperformclick)) {
                           performclickinternal();
                       }
                   }
               }
       }
   }


问题2:clearfocus“无效”?


在之前我们了解了清除焦点的机制,但为什么有时候会碰到调用clearfocus时"无效"呢?我们对比一下我们可以主动调用的clearfocus方法和系统内部调用的unfocus方法。


void unfocus(view focused) {
       clearfocusinternal(focused, false, false);
   }


发现一处可疑点,propagate和refocus的值决定了rootviewrequestfocus是否被调用,由于&&和||的短路作用,当propagate和refocus均为true时,才会执行rootviewrequestfocus,而在rootviewrequestfocus中会触发root的获取焦点逻辑。


boolean rootviewrequestfocus() {
       final view root = getrootview();
       return root != null && root.requestfocus();
   }


因此clearfocus看似“无效”,其实是焦点被清除后又立马被设置上了。那该如何解决呢?回顾之前提到的焦点分发逻辑,当父view抢先获取了焦点就能够解决,因此,让父view自动获取焦点是很好的解决方法。这里我们可以回忆上面分发焦点中所提及的三种焦点分发策略,我们希望父view先于子view获取焦点,很明显这符合focus_before_descendants策略,但我们好像并没有手动配置过这个策略,那focus_before_descendants策略是否是viewgroup的默认策略呢?我们查看viewgroup源码发现在initviewgroup中确实有默认的设置,如下:


private void initviewgroup() {
       // ...省略
       setdescendantfocusability(focus_before_descendants);
       // ...省略
   }


举一反三,如果我们想让子view先于父view获取焦点或者禁止子view获取焦点,即可通过setdescendantfocusability方法来设置。


另外感兴趣的同学可以继续探究refocus的取值逻辑。


问题3:焦点抢占


在问题2中,我们通过焦点抢占解决了一些问题,但有时候view错误的获取焦点会带来一些意料外的问题。比如edittext自动获取了焦点导致自动弹起输入法。又比如recyclerview在嵌套时子view抢占了焦点导致列表发生预期外的移动等等,这是个有趣的问题,感兴趣的同学可以查看recyclerview#requestchildfocus方法,其中执行的requestchildrectangleonscreen方法会为你解决这个疑惑。碰到这些问题时,我们可以考虑禁止不需要获取焦点的view的焦点获取能力,或者让其父view先获取焦点来解决问题。


2.3 总结


android中的焦点机制是一个很有趣的内容,很多疑难问题的答案都藏在源码中,理解了焦点的机制后,相关问题都将变得有迹可循。


03 android中cookie


3.1 首先什么是cookie:


cookie是服务器保存在浏览器的一小段文本信息,每个 cookie 的大小一般不能超过4kb。浏览器每次向服务器发出请求,就会自动附上这段信息。


3.2 webview的cookie存储:


webview是基于 webkit 内核的ui控件,相当于一个浏览器客户端。


它会在本地维护每次会话的cookie( 保存在 data/data/package_name/app_webview/cookies.db )



导出后可见:



3.3 cookie属性:


set-cookie:name=value [ ;expires=date][ ;max-age=time][ ;domain=domain][ ;path=path][ ;secure][ ;httponly]



例:

set-cookie: test=1234567890; expires=wed, 21 oct 2022 07:28:00 gmt; domain=baidu.com; path=/test;secure; httponly


2.4 cookie的设置


android中的webkit为我们提供了cookiemanager,它是一个单例,我们可以利用它进行cookie的读取和存储,例如

cookiemanager.getinstance().setcookie(url, cookie); cookiemanager.getinstance().getcookie(url);


2.5 cookie在请求中携带:



2.5.1 request的header:


webview中h5的请求:

在webview的h5中发送请求时,同浏览器一样,每次向服务器发出请求(domain&path与cookie中设置一致),就会自动附上这段信息。


客户端native发请求:


由客户端发送,包含在http请求的头部中。注意,native发送请求时,需要网络库主动addheader,所以建议封装网络库时,native仿照浏览器自动携带cookie的机制。如:

// 简单写了个意思,具体实现需要遍历拼接等判断,大家明白就好cookiemanager cookiemanager = cookiemanager.getinstance();string webviewcookies = cookiemanager.getcookie(url);httpurlconnection.setrequestproperty("cookie", webviewcookies);


2.5.2 response的set-header:


webview中h5的请求响应:

在webview的h5中接收到服务端响应时,同浏览器一样,会响应response的set-header自动为内核种上cookie。


客户端native请求响应:


由客户端接收到response后,需要注意的是系统并不会自动为内核种上cookie,建议封装网络库时,native仿照浏览器响应response的set-header自动为内核种上cookie。如:

// 简单写了个意思,具体实现需要添加安全性的判断,大家明白就好
map> responseheadermap = httpurlconnection.getheaderfields();
list cookielist = responseheadermap.get("set-cookie");
cookiesyncmanager.createinstance(context);
cookiemanager cookiemanager = cookiemanager.getinstance();
cookiemanager.setacceptcookie(true);
for (string cookie : cookielist) {
   list httpcookielist = httpcookie.parse(cookie);
   httpcookie httpcookie = httpcookielist.get(0);
   string relcookie = buildcookie(httpcookie.getdomain(), httpcookie.getname(),
           httpcookie.getvalue(), system.currenttimemillis() httpcookie.getmaxage() * 1000,
           httpcookie.getsecure());
   cookiemanager.setcookie(domain, relcookie);
}


其他额外知识:

cookie多进程使用及同步:https://iluhcm.com/2018/04/27/android-cookie-sync-between-multiprocess/


本文转自 百度geek说

哪些后端框架正在影响web应用程序开发

小程序开源业务架构是怎样做的

我们的位置

广州 广州市黄埔区科学大道18号芯大厦

深圳 深圳市南山区大冲国际中心九楼

粤西 茂名市茂南区油城三路粤西创业创新孵化基地b110

亚博平台app下载的服务

网站及移动应用 高端品牌网站 app开发 小程序开发 微信运营

系统应用开发 oa/erp/crm/hr系统开发

了解我们

yb体育官方的简介 联系亚博平台app下载 我们的案例 新闻资讯

© 2009-2022 广州睿网信息科技有限公司 yb体育官方的版权所有 

网站地图