其他
百度程序员开发避坑指南(移动端篇)
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 分析如下:
// 伪代码
@interface NSTimer ()
@property (strong, nonatomic) id target;
@end
// 强引用该对象
self.target = target
1.3 如何解决?
按照分析,那应该打破ViewController和NSTimer双方的强引用。使用弱引用(弱引用不增加对象的引用计数)。
方案1
- (void)viewDidLoad {
[super viewDidLoad];
__weak typedef(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
}
方案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];
}
2.1 Android焦点概念
2.2 焦点处理
2.2.1 获取焦点
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;
}
并不是所有View都可以获取焦点。获取焦点的前提是视图必需要有获取焦点的资格。
2.2.3 分发焦点
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 清除焦点
/**
* 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:错误启用获取焦点能力导致点击失效
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“无效”?
void unFocus(View focused) {
clearFocusInternal(focused, false, false);
}
boolean rootViewRequestFocus() {
final View root = getRootView();
return root != null && root.requestFocus();
}
private void initViewGroup() {
// ...省略
setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
// ...省略
}
问题3:焦点抢占
2.3 总结
3.1 首先什么是Cookie:
Cookie是服务器保存在浏览器的一小段文本信息,每个 Cookie 的大小一般不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。
3.2 Webview的Cookie存储:
3.3 Cookie属性:
Set-Cookie: TEST=1234567890; Expires=Wed, 21 Oct 2022 07:28:00 GMT; Domain=baidu.com; Path=/test;Secure; HttpOnly
2.4 Cookie的设置
CookieManager.getInstance().setCookie(url, cookie);
CookieManager.getInstance().getCookie(url);
2.5 Cookie在请求中携带:
2.5.1 Request的Header:
// 简单写了个意思,具体实现需要遍历拼接等判断,大家明白就好
CookieManager cookieManager = CookieManager.getInstance();
String webviewCookies = cookieManager.getCookie(url);
httpURLConnection.setRequestProperty("Cookie", webviewCookies);
2.5.2 Response的Set-Header:
// 简单写了个意思,具体实现需要添加安全性的判断,大家明白就好
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);
}