当前位置: 首页 > 编程日记 > 正文

仅需6步,教你轻易撕掉app开发框架的神秘面纱(3):构造具有个人特色的MVP模式

1. MVP的问题

之前我们说过MVP模式最大的问题在于:每写一个Activity/Fragment需要写4个对应的文件,对于一个简易的app框架来说太麻烦了。所以我们需要对MVP进行一定的简化。

关于MVP模式是什么及其简单实现,可以参照:浅谈 MVP in Android

MVP模式最大的特点是:业务逻辑和页面元素的分离,以适应业务逻辑和页面各自可能发生的变化和多样性。

该模式在面向对象的语言角度对2者进行隔离,隔离的很彻底,但是代价也大。

2. 分析问题

为了解决这个问题,我们可以在另一个角度对两者进行不那么彻底的隔离:即从功能角度进行隔离。

我们之前说过,业务逻辑的数据来自如下几个方面:1.服务端返回数据 2.其它途径传入数据 3.需要传出的自定义数据

所以我们可以把在Activity(iOS:ViewContorller)中可能会改变业务逻辑的操作提取出来,放在DataHandler中,也可以达到隔离的目的。

但是这种隔离依赖于我们对 ” 可能改变业务逻辑的操作 ” 的定义,而且这种操作的定义可能会随着项目的进行而变化(增减)。

那么可能会改变业务逻辑的操作有什么?使用最多的无非就是如下几种:

  1. 网络请求
  2. 页面跳转
  3. 点击或其它类似事件

3. 解决方案

具体如何实现呢?

首先我们定义一个接口(IDataHandlerInterface),包含上述3种事件的函数。然后令Activity(iOS:UIViewController,下同)和DataHandler实现该接口。

当对应事件发生时,依次调用DataHandler和Activity中的对应函数。

对此,我们需要定义BaseActivity(iOS:BaseViewController)类,把上述操作封装在此类中,后续自定义的Activity(iOS:ViewController)都需要继承BaseActivity(iOS:BaseViewController)。

//android: IDataHandlerInterface.java
public interface class IDataHandlerInterface{//网络请求,关于网络请求细节后续会介绍,这里ServerData就是服务端返回数据//如有人调用BaseDataHandler中的callserver,此函数会在接口回调后自动调用public void onServerCallback(ServerData data);//页面跳转public void onEnter();public void onExit();//点击事件public boolean onClick(View v, Object data);
}
//iOS: IDataHandlerInterface.h
@protocol IDataHandlerInterface <NSObject>
//网络请求,关于网络请求细节后续会介绍,这里ServerData就是服务端返回数据
//如有人调用BaseDataHandler中的callserver,此函数会在接口回调后自动调用
-(void) onServerCallbackWithData:(ServerData*) data;
//页面跳转
-(void) onEnter;
-(void) onExit;
//点击事件
-(BOOL) onClickWithView:(UIView *)view andData:(id)data;
@end

4. 其它问题

除了继承IDataHandleInterface之外,BaseActivity(iOS:BaseViewController)还有其它的责任,它需要对生命周期进行封装重构,使不同的函数职责分明,可以在增加可读性的同时,令不同的程序员更容易写出一致的代码。

如何重构BaseActivity(iOS:BaseViewController)生命周期呢?

其实很简单,BaseActivity(iOS:BaseViewController)的职责是显示UI控件,而习惯上UI相关的代码,大多数都是写在OnCreate(iOS:viewDidLoad)中。这样写有些违犯设计模式中的单一原则,因为会使一些UI无关的 私有变量 和 添加事件监听 的操作都放在OnCreate中,所以这些操作应该分离出来。

而BaseActivity(iOS:BaseViewController)也需要同BaseDataHandler的实例相互引用。这一点耦合是难以避免的。

另外,如何连接BaseActivity(iOS:BaseViewController)和BaseDataHandler是一个不小的问题。

有什么问题呢?是这样的,因为BaseActivity(iOS:BaseViewController)中引用的是BaseDataHandler的实例。

所以当子类继承BaseActivity(iOS:BaseViewController)后,只能拿到BaseDataHandler的引用。而不能拿到真实的DataHandler引用,这样每次想要调用DataHandler子类中某些不在BaseDataHandler中的方法时就需要强转。

这是一个很不友好的,带有写重复代码嫌疑的操作。

为了解决这个问题,我们需要使用范型,对DataHandler和Activity(iOS: ViewController)进行编译时自动绑定。

在iOS 中没有泛型的概念,但我们仍然可以使用 @property覆盖 及 @dynamic注解 来解决此问题。

android版代码及注释如下:

//BaseActivity.java
public abstract class BaseActivity<D extends BaseDataHandler> extends FragmentActivity implements IDataHandleInterface, View.OnClickListener{private final static String TAG = "BaseActivity";private D mDataHandler;//业务逻辑处理,使用泛型令子类可以动态绑定BaseDataHandler的子类@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);createDataHandler();mClickDataMap = new HashMap<>();if(mDataHandler != null){mDataHandler.setActivity(this);mDataHandler.onEnter();}onEnter();initArgs();initViews();initEvents();mDataHandler.loadDatas();}@Overrideprotected void onDestroy(){super.onDestroy();onExit();if(mDataHandler != null){mDataHandler.onExit();}}//实现动态绑定DataHandler 这样每次声明BaseActivity的子类时,指定范型为真实的DataHandler子类后,本方法会自动初始化此DataHandlerprivate void createDataHandler(){Class genericClass = (Class)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];try{mDataHandler = (D)genericClass.newInstance();}catch(Exception e){Log.e(TAG, e.toString());}}//子类获取DataHandler实例就是真实的类对象引用protected D getDataHandler(){return mDataHandler} //内部变量初始化protected abstract initArgs();//ui组件初始化protected abstract initViews();//添加点击事件protected abstract initEvents();//点击事件的处理 beginprivate HashMap<View, Object> mClickDataMap;//点击事件传入数据存储。@Overridepublic void onClick(View v){Object data = mClickDataMap.get(v);if(onClick(v, data)){//因为有些点击会因为某些错误,如输入不合法,不需要修改数据。所以在此判断if(mDataHandler != null){mDataHandler.onClick(v, data);}}}//子类需要调用此方法对View进行点击事件绑定。当然真实情况下可能不局限于点击事件,有可能还会有滑动/长按等等类似事件,这种情况下就需要扩展此方法。protected void addOnClickListener(View v, Object data){v.setOnClickListener(this);mClickDataMap.put(v, data);}//点击事件的处理 end//页面跳转 begin//这只是个演示版本,没有考虑有返回值的情况,具体细节需要进行再次开发。protected void jumpToPage(Class c){Intent i = new Intent(this, c);if(mDataHandler != null){mDataHandler.pushDataForJumpPage(i);}startActivity(i);}//页面跳转 end
}
//BaseDataHandler.java
public abstract BaseDataHandler<A extends BaseActivity> implements IDataHandleInterface{private final static String TAG = "BaseDataHandler";private A mActivity;//页面引用原理同上/*package*/void setActivity(A activity){mActivity = activity;}public A getActivity(){return mActivity}protected void callServer(String method, String ...params){//TODO:进行网络请求,此处留空,后续完成网络模块后,填充此方法// 伪代码如下:Server.call(method, params, new ServerCallback(){@Overridepublic void onResponse(ServerData data){if(data.status == succ){onServerCallback(data);if(mActivity != null){mActivity.onServerCallback(data);}}else{tip("接口调用失败,错误码:" + data.code + ", 错误信息:" + data.message);}}});}//有些页面刚进入时需要调用接口,这种情况的调用需写在此方法中。//第一次接口调用不能写在onEnter中是因为:onEnter时页面元素还没有构造,而回调用可能会对UI组件进行操作,所以可能会引起null异常。//所以增加loadDatas方法,此方法调用时,页面元素已经构建完毕。//OnEnter方法在构建页面之前调用的原因是,onEnter可能会接收来自其它页面的数据,为了令此数据全局有效,所以尽早调用是比较妥当的。//因此请在onEnter方法中获取来自其它页面传入的Intent内存储的数据。protected abstract void loadDatas();//页面跳转,因为页面跳转时,可能需要传递一些数据,而这些数据自然就在DataHandler中//通过此方法,向Intent中传递数据,子类可以根据intent中的class判断不同的页面protected abstract void pushDataForJumpPage(Intent intent);
}

iOS版代码及注释如下:

// BaseViewController.h
#import <UIKit/UIKit.h>
#import "BaseDataHandler.h"
#import "IDataHandlerInterface.h"@class BaseDataHandler;
@interface BaseViewController : UIViewController<IDataHandlerInterface>
@property (nonatomic, strong) BaseDataHandler *dataHandler;//子类需使用此方法添加点击事件
-(void) addOnClickListenerWithView:(UIView *)v andData:(id)data;//子类需使用此方法进行页面跳转
-(void) jumpToPageWithClass:(Class) clazz andDataHandlerClazz:(Class) dhClazz andData:(NSDictionary *)data isNavCtl:(BOOL) isNavCtl;//子类需使用此方法关闭页面
-(void) closePageWithIsNavCtl:(BOOL) isNavCtl;//!!!如下方法需要子类重载-(void) initArgs;//内部变量初始化-(void) initViews;//ui组件初始化-(void) initEvents;//添加点击事件-(void)onServerCallbackWithData:(id)data;-(void)onEnter;-(void)onExit;-(BOOL)onClickWithView:(UIView *)view andData:(id)data;
@end
//BaseViewController.m
#import "BaseViewController.h"
@implementation BaseViewController{//点击事件传入数据存储。data和view同步存储所以index相同的object为一对。NSMutableArray *mClickViewMap;NSMutableArray *mClickDataMap;
}- (instancetype)initWithDataHandler:(Class) dataHandlerClazz
{self = [super init];if (self) {self.dataHandler = [dataHandlerClazz new];mClickViewMap = [NSMutableArray new];mClickDataMap = [NSMutableArray new];if (self.dataHandler) {self.dataHandler.viewController = self;[self.dataHandler onEnter];}[self onEnter];}return self;
}-(void)viewDidLoad{[super viewDidLoad];[self initArgs];[self initViews];[self initEvents];[self.dataHandler loadDatas];
}-(void) addOnClickListenerWithView:(UIView *)v andData:(id)data{if ([v isKindOfClass:[UIButton class]]) {UIButton *btn = (UIButton *)v;[btn removeTarget:self action:@selector(onClickInner:) forControlEvents:UIControlEventTouchUpInside];[btn addTarget:self action:@selector(onClickInner:) forControlEvents:UIControlEventTouchUpInside];} else {UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onClickInner:)];for (NSInteger i = v.gestureRecognizers.count - 1; i >= 0; i--) {UIGestureRecognizer *gesture = v.gestureRecognizers[i];if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) {[v removeGestureRecognizer:gesture];}}[v addGestureRecognizer:tapGes];}if (data) {[mClickViewMap addObject:v];[mClickDataMap addObject:data];}
}-(void) onClickInner:(UIView *)view{id data = nil;if ([mClickViewMap containsObject:view]) {data = [mClickDataMap objectAtIndex:[mClickViewMap indexOfObject:view]];}//因为有些点击会因为某些错误,如输入不合法,不需要修改数据。所以在此判断if([self onClickWithView:view andData:data]){if (self.dataHandler) {[self.dataHandler onClickWithView:view andData:data];}}
}-(void) jumpToPageWithClass:(Class) clazz andDataHandlerClazz:(Class) dhClazz andData:(NSDictionary *)data isNavCtl:(BOOL) isNavCtl{if (![clazz isSubclassOfClass:[BaseViewController class]] ||![dhClazz isSubclassOfClass:[BaseDataHandler class]]) {NSLog(@"错误:clazz必须是BaseViewController的子类,dhClazz必须是BaseDataHandler的子类");return;}//数据NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithDictionary:data];if (self.dataHandler) {[self.dataHandler pushDataForJumpPageWithDict:dict];}//跳转BaseViewController *ctl = [[clazz alloc] initWithDataHandler:dhClazz];ctl.dataHandler.inputData = dict;if (self.navigationController && isNavCtl) {[self.navigationController pushViewController:[[UINavigationController alloc] initWithRootViewController:ctl] animated:YES];}else{//防止跳转时有延迟或跳转失败。dispatch_async(dispatch_get_main_queue(), ^{[self presentViewController:ctl animated:YES completion:nil];});}
}-(void) closePageWithIsNavCtl:(BOOL) isNavCtl{if (self.navigationController && isNavCtl) {[self.navigationController popViewControllerAnimated:YES];}else{dispatch_async(dispatch_get_main_queue(), ^{[self dismissViewControllerAnimated:YES completion:nil];});}//退出逻辑[self onExit];if (self.dataHandler) {[self.dataHandler onExit];}
}//-----------------------------
-(void) initArgs{}-(void) initViews{}-(void) initEvents{}-(void) onServerCallbackWithData:(id)data{}-(void) onEnter{}-(void) onExit{}-(BOOL) onClickWithView:(UIView *)view andData:(id)data{return NO;}
@end
//BaseDataHandler.h
#import <Foundation/Foundation.h>
#import "BaseViewController1.h"
#import "IDataHandlerInterface.h"@class BaseViewController;
@interface BaseDataHandler : NSObject<IDataHandlerInterface>
@property (nonatomic, weak) BaseViewController *viewController;
@property (nonatomic, strong) NSMutableDictionary *inputData;-(void) callServerWithMethod:(NSString *)method andParams:(id)params;//!!!下列方法子类需覆盖//loadDatas: 有些页面刚进入时需要调用接口,这种情况的调用需写在此方法中。
//第一次接口调用不能写在onEnter中是因为:onEnter时页面元素还没有构造,而回调用可能会对UI组件进行操作,所以可能会引起null异常。
//所以增加loadDatas方法,此方法调用时,页面元素已经构建完毕。
//OnEnter方法在构建页面之前调用的原因是,onEnter可能会接收来自其它页面的数据,为了令此数据全局有效,所以尽早调用是比较妥当的。
//因此请在onEnter方法中获取来自其它页面传入的Intent内存储的数据。
-(void) loadDatas;//页面跳转,因为页面跳转时,可能需要传递一些数据,而这些数据自然就在DataHandler中
//通过此方法,向Intent中传递数据,子类可以根据intent中的class判断不同的页面
-(void) pushDataForJumpPageWithDict:(NSMutableDictionary *)dict;-(void)onServerCallbackWithData:(id)data;-(void)onEnter;-(void)onExit;-(BOOL)onClickWithView:(UIView *)view andData:(id)data;@end
//BaseDataHandler.m
#import "BaseDataHandler.h"@implementation BaseDataHandler-(void) callServerWithMethod:(NSString *)method andParams:(id)params{//TODO:进行网络请求,此处留空,后续完成网络模块后,填充此方法// 伪代码如下:[Server callWithMethod:method andParams:params andCb:^(ServerData data){if(data.status == succ){[self onServerCallbackInnerWithData:data]}else{tip("接口调用失败,错误吗:" + data.code + ", 错误信息:" + data.message);}}];
}-(void) loadDatas{}-(void) pushDataForJumpPageWithDict:(NSMutableDictionary *)dict{}-(void) onServerCallbackWithData:(id)data{}-(void) onEnter{}-(void) onExit{}-(BOOL) onClickWithView:(UIView *)view andData:(id)data{return NO;}@end

android的使用方法不用多说,建立2个文件分别继承BaseActivity和BaseDataHandler,然后泛型指定为对方即可。后续使用同正常使用Activity。

iOS则需要额外做一点事情,进行实际的DataHandler与实际的ViewController对象之间的绑定(也就是android中泛型起到的作用)。并且跳转页面必须使用BaseViewController中的 jumpToPageWithClass方法。
例子如下所示:

//MyViewController.h
#import "BaseViewController.h"
#import "MyDataHandler.h"@class MyDataHandler; //!!!!!@@@@@[1]
@interface MyViewController : BaseViewController
@property (nonatomic, strong) MyDataHandler *dataHandler; //!!!!!@@@@@[2]
@end
//MyViewController.m
#import "MyViewController.h"
@implementation MyViewController{NSDictionary *mData;UILabel *mLabel;
}@dynamic dataHandler;//!!!!!@@@@@[3]//!!!如下方法需要子类重载-(void) initArgs{//内部变量初始化mData = [NSDictionary new];
}-(void) initViews{//ui组件初始化UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 30)];label.text = @"你好";label.font = [UIFont systemFontOfSize:15];label.textColor = [UIColor redColor];[self.view addSubview:label];mLabel = label;UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];btn.titleLabel.text = @"---按钮---";[self.view addSubview:btn];[self addOnClickListenerWithView:btn andData:@"btn"];
}-(void) initEvents{//添加点击事件
}-(void) onClickBtn{//点击变色mLabel.textColor = [mLabel.textColor isEqual:[UIColor redColor]] ? [UIColor blueColor]: [UIColor redColor];
}-(void)onServerCallbackWithData:(id)data{NSLog(@"onServerCallbackWithData");
}-(void)onEnter{NSLog(@"onEnter");
}-(void)onExit{NSLog(@"onExit");
}-(BOOL)onClickWithView:(UIView *)view andData:(id)data{if ([data isEqualToString:@"btn"]) {[self onClickBtn];}return YES;
}@end
//MyDataHandler.h
#import "BaseDataHandler.h"
#import "MyViewController.h"@class MyViewController;//!!!!!@@@@@[4]
@interface MyDataHandler : BaseDataHandler
@property (nonatomic, weak) MyViewController *viewController;//!!!!!@@@@@[5]
@end
//MyDataHandler.m
#import "MyDataHandler.h"@implementation MyDataHandler
@dynamic viewController;//!!!!!@@@@@[6]@end

[注意:]标记为 “//!!!!!@@@@@[x]“ 的地方就是动态绑定ViewController 和 DataHandler实例所写的代码,项目中可以把他们封装到宏定义中,更加方便使用。

另外,上述代码只是一个可用框架的最小集合,如果使用在项目中可根据需要进行扩展。比如:需要对Activity的生命周期进行关注;需要关注页面隐藏和显示的事件等等。

到此为止,我们已经搭建好了一个具有个人风格的MVP模式了。
下面是代码清单:

android:IDataHandleInterface.javaBaseActivity.javaBaseDataHandler.java
iOS:IDataHandleInterface.hIDataHandleInterface.mBaseViewController.hBaseViewController.mBaseDataHandler.hBaseDataHandler.m

相关文章:

Java进阶之自动拆箱与自动装箱

序. java基本类型介绍 java中&#xff0c;基本数据类型一共有8种&#xff0c;详细信息如下表&#xff1a; 类型大小范围默认值byte8-128 - 1270short16-32768 - 327680int32-2147483648-21474836480long64-9233372036854477808-92333720368544778080float32-3.40292347E38-3.40…

Ceilometer Polling Performance Improvement

Ceilometer的数据采集agent会定期对nova/keystone/neutron/cinder等服务调用其API的获取信息&#xff0c;默认是20秒一次&#xff0c; # Polling interval for pipeline file configuration in seconds.# (integer value)#pipeline_polling_interval 20 这在大规模部署中会对O…

vue使用pwa_如何使用HTML,CSS和JavaScript从头开始构建PWA

vue使用pwaProgressive web apps are a way to bring that native app feeling to a traditional web app. With PWAs we can enhance our website with mobile app features which increase usability and offer a great user experience.渐进式Web应用程序是一种将本地应用程…

仅需6步,教你轻易撕掉app开发框架的神秘面纱(4):网络模块的封装

程序框架确定了&#xff0c;还需要封装网络模块。 一个丰富多彩的APP少不了网络资源的支持&#xff0c;毕竟用户数据要存储&#xff0c;用户之间也要交互&#xff0c;用户行为要统计等等。 使用开源框架 俗话说得好&#xff0c;轮子多了路好走&#xff0c;我们不需要自己造轮…

结构体成员数组不定长如何实现

【目的】 定义一个结构体类&#xff0c;其中的成员变量数组长度不定&#xff0c;根据实例化的对象指定长度&#xff0c;所以想到用指针实现 【现状】 指针可以指向任意长度数组&#xff0c;但结构体类只分配指针本身4字节长度&#xff0c;所以无法扩展 1 /**2 ****************…

团队项目:二次开发

至此&#xff0c;我们有了初步的与人合作经验&#xff0c;接下来投入到更大的团队中去。 也具备了一定的个人能力&#xff0c;能将自己的代码进行测试。接下来尝试在别人已有的基础上进行开发。 上一界51冯美欣同学的项目&#xff1a;http://www.cnblogs.com/maxx/ 1.每个团队从…

arduino 呼吸灯_如何改善您的Arduino呼吸机:用于临时COVID-19呼吸机设计的RTS和SCS简介...

arduino 呼吸灯The world as we know it was recently taken by storm. That storm was the outbreak of the COVID-19 pandemic. This has in turn created a shortage of ventilators world wide which has led many people to foray into the world of ventilator design. 我…

reboot 百度网盘资源

提醒&#xff1a;同志们这是记录&#xff0c;视频文件是加密的&#xff0c;请勿下载 基础班第十三期&#xff1a;http://pan.baidu.com/s/1c2GcvKG 密码: 743j 基础班第十四期链接: http://pan.baidu.com/s/1c24AYa8 密码: x2sh 第十五期&#xff1a; https://pan.baidu.com…

仅需6步,教你轻易撕掉app开发框架的神秘面纱(5):数据持久化

遇到的问题 有的时候程序中需要全局皆可访问的变量&#xff0c;比如&#xff1a;用户是否登录&#xff0c;用户个人信息(用户名&#xff0c;地区&#xff0c;生日)&#xff0c;或者一些其他信息如&#xff1a;是否是首次登录&#xff0c;是否需要显示新手引导等等。 其中有些…

响应因特网端口ping命令_如何使用Ping命令识别基本的Internet问题

响应因特网端口ping命令Next time you call your help desk, do you want to wow them with your networking knowledge? Using a command called “ping”, built right into your existing Mac, Windows, or Linux computer, will help identify basic connection problems.…

Android 常见工具类封装

1&#xff0c;MD5工具类&#xff1a; public class MD5Util {public final static String MD5(String s) {char hexDigits[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,a, b, c, d, e, f };try {byte[] btInput s.getBytes();// 获得MD5摘要算法的 MessageDigest 对象MessageDigest md…

keras系列︱图像多分类训练与利用bottleneck features进行微调(三)

引自&#xff1a;http://blog.csdn.net/sinat_26917383/article/details/72861152 中文文档&#xff1a;http://keras-cn.readthedocs.io/en/latest/ 官方文档&#xff1a;https://keras.io/ 文档主要是以keras2.0。 训练、训练主要就”练“嘛&#xff0c;所以堆几个案例就知…

LIKE 操作符

LIKE 操作符LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式。SQL LIKE 操作符语法SELECT column_name(s)FROM table_nameWHERE column_name LIKE pattern原始的表 (用在例子中的)&#xff1a;Persons 表:IdLastNameFirstNameAddressCity1AdamsJohnOxford StreetLondon2Bush…

服务器云ide_语言服务器协议如何影响IDE的未来

服务器云ideThe release of Visual Studio Code single-handedly impacted the developer ecosystem in such a way that theres no going back now. Its open source, free, and most importantly, a super powerful tool. Visual Studio Code的发布以一种无可匹敌的方式对开发…

仅需6步,教你轻易撕掉app开发框架的神秘面纱(6):各种公共方法及工具类的封装

为什么要封装公共方法 封装公共方法有2方面的原因&#xff1a; 一是功能方面的原因&#xff1a;有些方法很多地方都会用&#xff0c;而且它输入输出明确&#xff0c;并且跟业务逻辑无关。比如检查用户是否登录&#xff0c;检查某串数字是否为合法的手机号。像这种方法就应该封…

MySQL优化配置之query_cache_size

原理MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存&#xff0c;会立刻返回结果&#xff0c;跳过了解析&#xff0c;优化和执行阶段。 查询缓存会跟踪查询中涉及的每个表&#xff0c;如果这写表发生变化&#xff0c;那么和这个表相关的所有缓存都将失效。 但是随着服…

request.getSession()

request.getSession(); 与request.getSession(false);区别 服务器把session信息发送给浏览器 浏览器会将session信息存入本地cookie中 服务器本地内存中也会留一个此session信息 以后用户发送请求时 浏览器都会把session信息发送给服务器 服务器会依照浏览器发送过来的se…

alpine 交互sh_在这个免费的交互式教程中学习Alpine JS

alpine 交互shAlpine.js is a rugged, minimal framework for composing Javascript behavior in your markup. Thats right, in your markup! Alpine.js是一个坚固的最小框架&#xff0c;用于在标记中构成Javascript行为。 是的&#xff0c;在您的标记中&#xff01; It allo…

浅谈 MVP in Android

一、概述 对于MVP&#xff08;Model View Presenter&#xff09;&#xff0c;大多数人都能说出一二&#xff1a;“MVC的演化版本”&#xff0c;“让Model和View完全解耦”等等。本篇博文仅是为了做下记录&#xff0c;提出一些自己的看法&#xff0c;和帮助大家如何针对一个Acti…

test markdown

test test public void main(String[] args){System.out.println("test"); } 转载于:https://www.cnblogs.com/cozybz/p/5427053.html

java开发工具对比eclipse·myeclipse·idea

eclipse:不说了&#xff0c;习惯了 myeclipse&#xff1a;MyEclipse更适合企业开发者&#xff0c;更团队开发 idea:idea更适合个人开发者,细节优化更好转载于:https://www.cnblogs.com/gjack/p/8136964.html

软件测试质量过程检测文档_如何编写实际上有效的质量检查文档

软件测试质量过程检测文档A software product is like an airplane: it must undergo a technical check before launch.软件产品就像飞机&#xff1a;必须在发射前经过技术检查。 Quality Assurance is a necessary step towards launching a successful software product. I…

Android深度探索--HAL与驱动开发----第一章读书笔记

1.1 Android拥有非常完善的系统构架可以分为四层&#xff1a; 第一层&#xff1a;Linux内核。主要包括驱动程序以及管理内存、进程、电源等资源的程序 第二层&#xff1a;C/C代码库。主要包括Linux的.so文件以及嵌入到APK程序中的NDK代码 第三层&#xff1a;android SDK API …

[NOI2011]Noi嘉年华

题解:我们设计状态方程如下: num[i][j]表示从时间i到j中有多少个 pre[i][j]表示时间1~i中,A选了j个时的B能选的数量的最大值. nex[i][j]表示时间i~cnt中,A选了j个时的B能选的数量的最大值. mus[i][j]表示从时间i到j的保证选时,A和B选的数量中的较小值的最大值. ①对于num数组直…

只有20%的iOS程序员能看懂:详解intrinsicContentSize 及 约束优先级/content Hugging/content Compression Resistance

在了解intrinsicContentSize之前&#xff0c;我们需要先了解2个概念&#xff1a; AutoLayout在做什么约束优先级是什么意思。 如果不了解这两个概念&#xff0c;看intinsic content size没有任何意义。 注&#xff1a;由于上面这几个概念都是针对UIView或其子类(UILabel&…

redux rxjs_可观察的RxJS和Redux入门指南

redux rxjsRedux-Observable is an RxJS-based middleware for Redux that allows developers to work with async actions. Its an alternative to redux-thunk and redux-saga.Redux-Observable是Redux的基于RxJS的中间件&#xff0c;允许开发人员使用异步操作。 它是redux-t…

javascript数组排序和prototype详解

原型的概念:&#xff1a;原型对象里的所有属性和方法 被所有构造函数实例化出来的对象所共享&#xff0c;类似于java中的 static 正因为共享所以单一的操作 就会影响了全局&#xff0c;因此使用时需注意 基于prototype&#xff1a;为数组扩展方法 //获取数组最大值function get…

Qt 在Label上面绘制罗盘

自己写的一个小小的电子罗盘的一个小程序&#xff0c;不过是项目的一部分&#xff0c;只可以贴绘制部分代码 效果如下图 首先开始自己写的时候&#xff0c;虽然知道Qt 的坐标系是从左上角开始的&#xff0c;所以&#xff0c;使用了算法&#xff0c;在绘制后&#xff0c;在移动回…

终极方案!解决正确设置LaunchImage后仍然不显示的问题

对于如何设置LaunchImage&#xff0c;网络上有各种各样的教程。 主要分2点&#xff1a; 1. 正确设置图片尺寸 2. 取消LaunchScreen.xib 但是经过上述步骤之后&#xff0c;你觉得完全没有问题了&#xff0c;但是仍然无法显示LaunchImage。 或者&#xff0c;你在多个模拟器上…

c# 持续集成 单元测试_如何在不进行单元测试的情况下设置持续集成

c# 持续集成 单元测试Do you think continuous integration is not for you because you have no automated tests? Or no unit tests at all? Not true. Tests are important. But there are many more aspects to continuous integration than just testing. Lets see what…