专栏

COLUMN

MORE
搜索引擎 - solorCloud的搭建及基本使用

一 :单台solr的搭建 solr的简介:solor是基于lucene的全文搜索服务器。 2 .solr的启动与tomcat的结合 (1)在solr文件下的example目录下,将solr.war复制到tomcat下的webapps下 (2) 启动tomcat解压slor.war(将原来的solr.war解压成.jar文件) (3)将解压的solr.jar复制到solr的web-info下的lib中,将classes目录复制到web-info下 (4)将example中的solr目录复制到与tomcat同级的目录下 (5)打开tomcat的bin目录找到Catalina.bat文件 (6)设置:set "JAVA-OPTS=-Dsolr.home=solr目录位置solr-home" (7)启动tomcat重新访问一下。二:solrCloud的搭建 当一台solr能够运行的时候,开始搭建solr集群 1.将solr的配置文件交给zookeeper管理:在solr的文件夹/scripts/cloud-scripts/ 写上 ./zkcli.sh -zkhost zookeeper集群的主机名:端口号 2.修改solr.xml配置文件 在solr/solr-home 下修改配置文件: solr.xml {jetty.port.8080} 3.修改Catalina.sh: "JAVA_OPTS=-Dsolr.solr.home=....Pzhost:zookeeper集群的主机名:端口号" 4.将此配置文件发送到其他几台服务器上 scp -r ...三:solrCloud的基本使用 1.创建solr集群的对象 CluodServer cloudServer = new CluodSolrServer("集权的ip:端口号"); 2.连接索引库: cloudServer.setDefaultCollection("collection1"); 3.添加索引: SolrInputDocument solrDocument=new SolrInputDocument(); solrDocument.addField("id","1"); solrDocument.addField("title","普道科技官网"); solrDocument.addField("content","内容"); cloudServer.add(solrDocument); 4.删除索引: cloudServer.deleteById(1); 5.查询操作: SolrQuery solrQuery =new SolrQuery("*:*"); Response response = cloudServer.query(solrQuery ); (-------------------------以上是来自个人的浅尝辄止--------------------------)

上官沐雪
初识 WebFlux

什么是WebFlux?webflux是Spring 5提出的一个新的开发Web的技术栈;它是一种异步非阻塞的开发模式;运行在Servlet 3.1+或Netty等容器上。webflux支持两种编程模式,一种是使用MVC的注解,另外一种是基于 Functional 函数式路由;也就是说我们现在开发Web服务多出了一个选择;和Spring mvc有什么异同?我们先看一下官方提供的对比图;第一个不同点Web flux是异步非阻塞的开发模式,而我们传统的MVC是一种同步的阻塞的开发模式。非阻塞的话意味着我们可以在一个线程里面可以处理更多的请求。而以前老的模式是一个请求对应我们容器里的一个线程,这是最大的不同点。第二个不同点是运行的环境的不同,MVC是基于ServletAPI,所以他必须运行在我们的Servlet的容器上面,而WebFlux是基于响应式流,他可以运行在Servlet 3.1之后的容器,也就是支持异步Servlet的容器。或者说运行在Netty上面。第三个不同点就是数据库这一块,Spring webflux Data Reactive Repositories里面关系型数据库都是不支持这种响应式的,仅仅支持 Mongo、Redis、等非关系型数据库。当然 还有很多区别,就不一一赘述了。为什么只能运行在Servlet 3.1+ 容器中?Servlet 3.1 规范中一个新特性是异步处理支持所谓异步处理是Servlet 线程不需一直阻塞,就是不需要到业务处理完毕再输出响应,然后结束 Servlet线程。异步处理的作用是在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,在不生成响应的情况下返回至容器。可以减少服务器资源的占用,并且提高并发处理速度。所以他必须运行在Servlet 3.1或者说Nettey 等支持异步处理的容器上;为什么目前不支持MySQL等关系型数据库?从官方提供对比图可以看出,Web mvc 支持的是 Spring data库,而 webflux 支持的是 spring data reactive库,所以这个问题的答案是,spring data的事务管理是基于 ThreadLocal传递事务的,本质是基于阻塞IO模型,不是异步的,但 Reactive 是要求异步的,不同线程里面 ThreadLocal肯定是娶不到值的。所以使用 Spring Data Reactive ,原来的 Spring 针对 Spring Data (JDBC等)的事务管理肯定不起作用了。当然这个问题也是可以解决的,感兴趣的同学可以后去研究一下。有什么缺点?代码相对来说编写复杂、不利于维护、不好扩展、社区不大,对关系型数据库支持不友好等等。通过一个案例来演示一下package com.lister.study.demo.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import java.util.concurrent.TimeUnit; @Slf4j @RestController public class TestController { /** * 传统的Spring mvc写法 * @return */ @GetMapping("/1") private String get1() { log.info("get1 start"); String string = createStr(1); log.info("get1 end"); return string; } /** * Web Flux 写法 * @return */ @GetMapping("/2") private Mono<String> get2() { log.info("get2 start"); Mono<String> result = Mono.fromSupplier(() -> createStr(2)); log.info("get2 end"); return result; } private String createStr(int i) { log.info("createStr"); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException ignored) { } return "Some string from get" + i; } } 运行结果:Spring mvc 传统Servlet的运行结果:可以明显看出这个Servlet线程被阻塞了5秒钟再来看看我们WebFlux的运行结果:可以看出来请求线程并未被阻塞这就是为什么Flux能大大提高吞吐量的原因了再来看一组增删改查的案例Controller:@RestController @RequestMapping("/user") public class UserController { private UserService userService; public UserController(UserService userService) { this.userService = userService; } @PostMapping("/") public Mono<User> createUser(@RequestBody User user) { return userService.saveUser(user); } @PutMapping("/") public Mono<ResponseEntity<User>> updateUser(@RequestBody User user) { return userService.updateUser(user); } @DeleteMapping("/") public Mono<ResponseEntity> deleteUser(String id) { return userService.deleteUser(id); } @GetMapping("/{id}") public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) { return userService.getUserById(id).map(user -> new ResponseEntity<>(user, HttpStatus.OK)) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @GetMapping("/name/{name}") public Flux<User> getByName(@PathVariable("name") String name) { return userService.getUserByName(name); } @GetMapping("/") public Flux<User> all() { return userService.allUser(); } } Service:public interface UserService { /** * 根据ID查询用户 * @param id * @return */ Mono<User> getUserById(String id); /** * 保存用户信息 * @param user * @return */ Mono<User> saveUser(User user); /** * 保存用户信息 * @param user * @return */ Flux<User> saveUser(Mono<User> user); /** * 更新用户 * @param user * @return */ Mono<ResponseEntity<User>> updateUser(User user); /** * 根据Id删除用户 * @param id * @return */ Mono<ResponseEntity> deleteUser(String id); /** * 根据用户名称查询用户列表 * @param name * @return */ Flux<User> getUserByName(String name); /** * 获取所有用户信息 * @return */ Flux<User> allUser(); } ServiceImpl:@Service public class UserServiceImpl implements UserService { private UserRepository userRepository; public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } /** * 根据ID查询用户 * * @param id * @return */ @Override public Mono<User> getUserById(String id) { return userRepository.findById(id); } /** * 保存用户信息 * * @param user * @return */ @Override public Mono<User> saveUser(User user) { user.setId(null); return userRepository.save(user); } /** * 保存用户信息 * * @param user * @return */ @Override public Flux<User> saveUser(Mono<User> user) { return userRepository.saveAll(user); } /** * 更新用户 * * @param user * @return */ @Override public Mono<ResponseEntity<User>> updateUser(User user) { return userRepository.findById(user.getId()) .flatMap(u -> { u.setName(user.getName()); u.setSex(user.getSex()); return userRepository.save(u); }) .map(u -> new ResponseEntity<>(u, HttpStatus.OK)) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } /** * 根据Id删除用户 * * @param id * @return */ @Override public Mono<ResponseEntity> deleteUser(String id) { return userRepository.findById(id).flatMap(user -> userRepository.delete(user) .then(Mono.just(new ResponseEntity(HttpStatus.OK)))) .defaultIfEmpty(new ResponseEntity(HttpStatus.NOT_FOUND)); } /** * 根据用户名称查询用户列表 * * @param name * @return */ @Override public Flux<User> getUserByName(String name) { return userRepository.findByNameLike(name); } /** * 获取所有用户信息 * * @return */ @Override public Flux<User> allUser() { return userRepository.findAll(); } } Domain:@Data @Document(collection = "user") public class User { @Id private String id; private String name; private SexEnum sex; } Enum:public enum SexEnum { MAN, WOMAN,; } Repository:@Repository public interface UserRepository extends ReactiveMongoRepository<User, String> { /** * 根据名字模糊查询 * @param name * @return */ Flux<User> findByNameLike(String name); Flux<User> findByNameIsNotNull(); } 除了使用 Spring mvc 它还有 reactive stream 流的方式编程:Handler:@Component public class UserHandler { private UserService userService; public UserHandler(UserService userService) { this.userService = userService; } /** * 固定的方法格式 Mono<ServerResponse> xxx(ServerRequest request); * 根据Id查询用户 * @param request * @return */ public Mono<ServerResponse> getById(ServerRequest request) { String id = request.pathVariable("id"); return ok().contentType(APPLICATION_JSON_UTF8).body(userService.getUserById(id), User.class); } /** * 保存用户信息 * @param request * @return */ public Mono<ServerResponse> saveUser(ServerRequest request) { Mono<User> user = request.bodyToMono(User.class); return ok().contentType(APPLICATION_JSON_UTF8).body(userService.saveUser(user), User.class); } /** * 删除用户 * @param request * @return */ public Mono<ServerResponse> deleteUser(ServerRequest request) { String id = request.pathVariable("id"); return userService.deleteUser(id) .flatMap(responseEntity -> { if (responseEntity.getStatusCodeValue() == 200) { return ok().build(); } else { return notFound().build(); } }); } } 路由配置:@Configuration public class Routers { @Bean RouterFunction<ServerResponse> userRouter(UserHandler userHandler) { return RouterFunctions.nest(RequestPredicates.path("/user/router"), RouterFunctions.route(RequestPredicates.GET("/{id}"), userHandler::getById) .andRoute(RequestPredicates.POST("/") .and(RequestPredicates.accept(MediaType.APPLICATION_JSON_UTF8)), userHandler::saveUser) .andRoute(RequestPredicates.DELETE("/{id}"), userHandler::deleteUser) ); } } 这就是一个完整的 Spring flux 的增删改查的例子。

李斯特
Flutter 路由与导航

路由管理路由(Route)在移动开发中通常指页面(Page),在Android中通常指一个Activity。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: MainRoute(), ); } } class MainRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //导航到新路由 Navigator.push(context, MaterialPageRoute(builder: (context) { return SecondRoute(); })); }, child: Text("进入第二页"), ) ], ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context); }, child: Text("返回"), ) ], ), ); } } 命名路由命名路由(Named Route)即给路由起一个名字,然后可以通过路由名字直接打开新的路由。这为路由管理带来了一种直观、简单的方式。但是在早期的Flutter版本是不支持传参的,所以不要别网上的一些资料误导,看源码可以发现 Navigator.pushNamed()方法可以传一个arguments,一看到这个,让我想起Android里面fragment接收参数也是通过 arguments,于是找资料查看如何接收这个参数,发现有两种方式class CompileIncomePage extends StatefulWidget { @override _CompileIncomeState createState() => _CompileIncomeState(); } class _CompileIncomeState extends State<CompileIncomePage> { @override Widget build(BuildContext context) { var arguments = ModalRoute.of(context).settings.arguments; //通过这个来获取前一个页面传过来的参数(这里举例的是有状态的widget,亲测在无状态widget也是可以通过这个) return Container(); } } 来自Flutter官网的例子:https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments ,主要这种方式感觉比较麻烦所以我在项目中使用的是上面那种方式 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // Provide a function to handle named routes. Use this function to // identify the named route being pushed, and create the correct // Screen. onGenerateRoute: (settings) { // If you push the PassArguments route if (settings.name == PassArgumentsScreen.routeName) { // Cast the arguments to the correct type: ScreenArguments. final ScreenArguments args = settings.arguments; // Then, extract the required data from the arguments and // pass the data to the correct screen. return MaterialPageRoute( builder: (context) { return PassArgumentsScreen( title: args.title, message: args.message, ); }, ); } }, title: 'Navigation with Arguments', home: HomeScreen(), ); } } class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home Screen'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ // A button that navigates to a named route that. The named route // extracts the arguments by itself. RaisedButton( child: Text("Navigate to screen that extracts arguments"), onPressed: () { // When the user taps the button, navigate to the specific route // and provide the arguments as part of the RouteSettings. Navigator.push( context, MaterialPageRoute( builder: (context) => ExtractArgumentsScreen(), // Pass the arguments as part of the RouteSettings. The // ExtractArgumentScreen reads the arguments from these // settings. settings: RouteSettings( arguments: ScreenArguments( 'Extract Arguments Screen', 'This message is extracted in the build method.', ), ), ), ); }, ), // A button that navigates to a named route. For this route, extract // the arguments in the onGenerateRoute function and pass them // to the screen. RaisedButton( child: Text("Navigate to a named that accepts arguments"), onPressed: () { // When the user taps the button, navigate to a named route // and provide the arguments as an optional parameter. Navigator.pushNamed( context, PassArgumentsScreen.routeName, arguments: ScreenArguments( 'Accept Arguments Screen', 'This message is extracted in the onGenerateRoute function.', ), ); }, ), ], ), ), ); } } // A Widget that extracts the necessary arguments from the ModalRoute. class ExtractArgumentsScreen extends StatelessWidget { static const routeName = '/extractArguments'; @override Widget build(BuildContext context) { // Extract the arguments from the current ModalRoute settings and cast // them as ScreenArguments. final ScreenArguments args = ModalRoute.of(context).settings.arguments; return Scaffold( appBar: AppBar( title: Text(args.title), ), body: Center( child: Text(args.message), ), ); } } // A Widget that accepts the necessary arguments via the constructor. class PassArgumentsScreen extends StatelessWidget { static const routeName = '/passArguments'; final String title; final String message; // This Widget accepts the arguments as constructor parameters. It does not // extract the arguments from the ModalRoute. // // The arguments are extracted by the onGenerateRoute function provided to the // MaterialApp widget. const PassArgumentsScreen({ Key key, @required this.title, @required this.message, }) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Text(message), ), ); } } // You can pass any object to the arguments parameter. In this example, // create a class that contains both a customizable title and message. class ScreenArguments { final String title; final String message; ScreenArguments(this.title, this.message); } 自定义路由切换动画Material库中提供了MaterialPageRoute,它在Android上会上下滑动切换。如果想自定义路由切换动画,可以使用PageRouteBuilder。import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: MainRoute(), ); } } class MainRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () async { //导航到新路由 var result = await Navigator.push( context, PageRouteBuilder( ///动画时间 transitionDuration: Duration(milliseconds: 500), pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { ///平移 return SlideTransition( ///Tween:在补间动画中,定义开始点结束点 position: new Tween<Offset>( begin: const Offset(1.0, 0.0), end: const Offset(0.0, 0.0), ).animate(animation), child: SecondRoute(), ); }, ), ); debugPrint("返回:$result"); }, child: Text("进入第二页"), ) ], ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context, "结束"); }, child: Text("返回"), ) ], ), ); } } 同时我们也可以对动画进行组合import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: MainRoute(), ); } } class MainRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () async { //导航到新路由 var result = await Navigator.push( context, PageRouteBuilder( ///动画时间 transitionDuration: Duration(milliseconds: 500), pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { ///透明渐变与旋转 return new FadeTransition( opacity: animation, child: new RotationTransition( turns: new Tween<double>(begin: 0.5, end: 1.0) .animate(animation), child: SecondRoute(), ), ); },), ); debugPrint("返回:$result"); }, child: Text("进入第二页"), ) ], ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context, "结束"); }, child: Text("返回"), ) ], ), ); } } 注意点import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { ///Navigator.push内部其实就是 Navigator.of(context).push Navigator.of(context).push(MaterialPageRoute(builder: (_){ return new SecondRoute(); })); }, child: Text("进入第二页"), ) ], ), ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context); }, child: Text("返回"), ) ], ), ); } } 问题关键点在于Navigator operation requested with a context that does not include a Navigator.(导航操作请求使用了不包含Navigator的上下文context)​Navigator实际上也是一个Widget,这个异常出现在Navigator.of(context)路由器的获取上,而这句代码会从当前的context的父级一层层向上去查找一个Navigator,我们当前传递的context就是MyApp,它的父级是root——UI根节点。Navigator这个widget的并不是由root创建的,因此在root下一级的上下文中无法获得Navigator。所以问题就在于,Navigator需要通过MaterialApp或者它孩子的上下文。

ZL
Flutter动画

动画 了解过Flutter的都知道,在Flutter中一切皆widget,比如手势都是一个widget,那Flutter的动画是不是widget呢,通过源码可以看到Flutter的动画系统基于Animation对象的,所以它不是一个widget,这是因为Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,就相当于一个定时器,它用于保存动画的插值和状态,并执行数值的变化。widget可以在build函数中读取Animation对象的当前值, 并且可以监听动画的状态改变。AnimationController ​ AnimationController用于控制动画,它包含动画的启动forward()、停止stop()、反向播放reverse()等方法。 AnimationController会在动画的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字。AnimationController controller = AnimationController( duration: const Duration(milliseconds: 2000), //动画时间 lowerBound: 10.0, //生成数字的区间 upperBound: 20.0, //10.0 - 20.0 vsync: this //TickerProvider 动画驱动器提供者 ); Ticker ​ Ticker的作用是添加屏幕刷新回调,每次屏幕刷新都会调用TickerCallback。使用Ticker来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源。因为Flutter中屏幕刷 新时会通知Ticker,锁屏后屏幕会停止刷新,所以Ticker就不会再触发。最简单的做法为将SingleTickerProviderStateMixin添加到State的定义中。import 'package:flutter/material.dart'; void main() => runApp(AnimationApp()); class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } } // 动画是有状态的 class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } } class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller; bool forward = true; @override void initState() { super.initState(); controller = AnimationController( // 动画的时长 duration: Duration(milliseconds: 2000), lowerBound: 10.0, upperBound: 100.0, // 提供 vsync 最简单的方式,就是直接混入 SingleTickerProviderStateMixin // 如果有多个AnimationController,则使用TickerProviderStateMixin。 vsync: this, ); //状态修改监听 controller ..addStatusListener((AnimationStatus status) { debugPrint("状态:$status"); }) ..addListener(() { setState(() => {}); }); debugPrint("controller.value:${controller.value}"); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Container( width: controller.value, height: controller.value, color: Colors.blue, ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } } 动画状态监听:在forword结束之后状态为completed。在reverse结束之后状态为dismissedTween ​ 默认情况下,AnimationController对象值为:double类型,范围是0.0到1.0 。如果我们需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。要使用Tween对象,需要调用其animate()方法,然后传入一个控制器对象,同时动画过程中产生的数值由Tween的lerp方法决定。import 'package:flutter/material.dart'; void main() => runApp(AnimationApp()); class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } } // 动画是有状态的 class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } } class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller; bool forward = true; Tween<Color> tween; @override void initState() { super.initState(); controller = AnimationController( // 动画的时长 duration: Duration(milliseconds: 2000), // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin vsync: this, ); //使用Color tween = ColorTween(begin: Colors.blue, end: Colors.yellow); //添加动画值修改监听 tween.animate(controller)..addListener(() => setState(() {})); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Container( width: 100, height: 100, //获取动画当前值 color: tween.evaluate(controller), ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } } Curve​ 动画过程默认是线性的(匀速),如果需要非线形的,比如:加速的或者先加速后减速等。Flutter中可以通过Curve(曲线)来描述动画过程。import 'package:flutter/material.dart'; void main() => runApp(AnimationApp()); class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } } // 动画是有状态的 class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } } class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; bool forward = true; @override void initState() { super.initState(); controller = AnimationController( // 动画的时长 duration: Duration(milliseconds: 2000), // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin vsync: this, ); //弹性 animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn); //使用Color animation = Tween(begin: 10.0, end: 100.0).animate(animation) ..addListener(() { setState(() => {}); }); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Container( //不需要转换 width: animation.value, height: animation.value, //获取动画当前值 color: Colors.blue, ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } } AnimatedWidgetAnimation对象本身和UI渲染没有任何关系。而通过addListener()和setState()来更新UI这一步其实是通用的,如果每个动画中都加这么一句是比较繁琐的。AnimatedWidget类封装了调用setState()的细节,简单来说就是自动调用setState()。Flutter中已经封装了很多动画,比如对widget进行缩放,可以直接使用ScaleTransitionimport 'package:flutter/material.dart'; void main() => runApp(AnimationApp()); class AnimationApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: "animation", home: Scaffold( appBar: AppBar( title: Text('animation'), ), body: AnimWidget(), ), ); } } // 动画是有状态的 class AnimWidget extends StatefulWidget { @override State<StatefulWidget> createState() { return _AnimWidgetState(); } } class _AnimWidgetState extends State<AnimWidget> with SingleTickerProviderStateMixin { AnimationController controller; Animation<double> animation; bool forward = true; @override void initState() { super.initState(); controller = AnimationController( // 动画的时长 duration: Duration(milliseconds: 2000), // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin vsync: this, ); //弹性 animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn); //使用Color animation = Tween(begin: 10.0, end: 100.0).animate(animation); } @override Widget build(BuildContext context) { return Column( children: <Widget>[ ScaleTransition( child: Container( width: 100, height: 100, color: Colors.blue, ), scale: controller, ), RaisedButton( child: Text("播放"), onPressed: () { if (forward) { controller.forward(); } else { controller.reverse(); } forward = !forward; }, ), RaisedButton( child: Text("停止"), onPressed: () { controller.stop(); }, ) ], ); } } Hero动画​ Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画。import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text("主页"), ), body: Route1()), ); } } // 路由A class Route1 extends StatelessWidget { @override Widget build(BuildContext context) { return Container( alignment: Alignment.topCenter, child: InkWell( child: Hero( tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同 child: CircleAvatar( backgroundImage: AssetImage( "assets/banner.jpeg", ), ), ), onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { return Route2(); })); }, ), ); } } class Route2 extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Hero( tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同 child: Image.asset("assets/banner.jpeg")), ); } } 组合动画有些时候我们可能会需要执行一个动画序列执行一些复杂的动画。import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: Route(), ); } } class Route extends StatefulWidget { @override State<StatefulWidget> createState() { return RouteState(); } } class RouteState extends State<Route> with SingleTickerProviderStateMixin { Animation<Color> color; Animation<double> width; AnimationController controller; @override void initState() { super.initState(); controller = AnimationController( // 动画的时长 duration: Duration(milliseconds: 2000), // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin vsync: this, ); //高度动画 width = Tween<double>( begin: 100.0, end: 300.0, ).animate( CurvedAnimation( parent: controller, curve: Interval( //间隔,前60%的动画时间 1200ms执行高度变化 0.0, 0.6, ), ), ); color = ColorTween( begin: Colors.green, end: Colors.red, ).animate( CurvedAnimation( parent: controller, curve: Interval( 0.6, 1.0, //高度变化完成后 800ms 执行颜色编码 ), ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: InkWell( ///1、不用显式的去添加帧监听器,再调用setState() ///2、缩小动画构建的范围,如果没有builder,setState()将会在父widget上下文调用,导致父widget的build方法重新调用,现在只会导致动画widget的build重新调用 child: AnimatedBuilder( animation: controller, builder: (context, child) { return Container( color: color.value, width: width.value, height: 100.0, ); }), onTap: () { controller.forward().whenCompleteOrCancel(() => controller.reverse()); }, ), ); } }

ZL
EventBus原理和实现

一、原理通过注解收集所有的订阅方法,通过参数类型来找到对应的订阅者;所以要保证订阅者参数类型唯一性1. 注册:通过注册对象,获取到注册类中符合我们要求 方法(加了我们自定义的注解的)2. 存储:Map<Object, SubscribeMethod>; 其中Object为注册对象,SubscribeMethod存储注解方法,包括:方法对象Method, 参数类型,线程模式3. 通信:通过post中参数对象来遍历Map对象;找到和传入参数类型一样的 SubscribeMethod,然后调用注册对象中的该方法,并传入post的参数二、手写实现:1. 通进程内消息通信,为啥需要register和unregister<防止内存泄漏>// 注解类 用来标识订阅方法来在注册时 来获取保存 @Target(ElementType.METHOD) // 作用在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时 public @interface Subject { ThreadMode threadMode() default ThreadMode.POSTING; } 核心代码/** * @author xionghy2014@163.com * @description 手写EventBus类,包括 event,stickEvent,singleEvent,线程切换(实现了功能,需要完善) */ public class EventBus { private static EventBus instance = null; private final Map<Object, List<MethodManager>> map; private final ExecutorService executorService; // 存放 StickyEvent 事件 private final Map<Class<?>, Object> stickyEvents; private EventBus() { map = new HashMap<>(); executorService = Executors.newCachedThreadPool(); stickyEvents = new ConcurrentHashMap<>(); } public static EventBus getInstance() { if (instance == null) { synchronized (EventBus.class) { instance = new EventBus(); } } return instance; } /** * 如在Activity中注册:EventBus.getDefault().register(this); * @param object */ public void register(Object object) { List<MethodManager> list = new ArrayList<>(); // 获取 注册类 中所有的 自定义注解方法 Class<?> clazz = object.getClass(); // TODO 获取到当前类的方法集合;如果需要得到父类中的,则需要clazz.getMethods(); Method methods[] = clazz.getDeclaredMethods(); for (Method me : methods) { Subject subject = me.getAnnotation(Subject.class); if (subject == null) { continue; } // 获取到方法的接收参数的类型数组 Class<?>[] parameterType = me.getParameterTypes(); if (parameterType == null || parameterType.length != 1) { // TODO 限定为只有一个参数的注解方法,说明不是需要的订阅方法 // 可以自己扩展 continue; } // 获取到 订阅方法的线程模式 ThreadMode mode = subject.threadMode(); MethodManager mm = new MethodManager(me, parameterType[0],mode); list.add(mm); // TODO 如果 有符合条件的StickyEvent,则在订阅注册好了后直接发送 StickyEvent Object eventObj = getStickyEvent(parameterType[0]); if (eventObj != null) { try { me.invoke(object, eventObj); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (list.size() > 0) map.put(object, list); } public void post(Object val) { post(val, false); } /** * 数据发布到所有订阅着手中 * @param val * @param isSingle 是否为单次 */ private void post(final Object val, final boolean isSingle) { Iterator<Object> iterator = map.keySet().iterator(); while (iterator.hasNext()) { // 遍历Map,找到 对应订阅类型的 订阅者 final Object object = iterator.next(); final List<MethodManager> methodManagers = map.get(object); for (MethodManager me : methodManagers) { final Class<?> clazz = me.getType(); // 用于检测当前 调用post方法的线程,如未做处理,则是在UI线程中; // TODO 潜在问题-阻塞 final Method method = me.getMethod(); // 如果订阅者 是在MainThread if (me.getThreadMode() == ThreadMode.MAIN) { invoke(clazz, val, method, object); } else if (me.getThreadMode() == ThreadMode.THREAD) { // 子线程中 executorService.execute(new Runnable() { @Override public void run() { // 在线程中调用 invoke(clazz, val, method, object); } }); } } if (isSingle) { removeEvent(methodManagers, val); } } } private void removeEvent(List<MethodManager> methodManagers, Object event) { for (MethodManager mm : methodManagers) { if (event.getClass().isAssignableFrom(mm.getType())) { methodManagers.remove(mm); } } } /** * 调用方法 * @param clazz * @param val 方法的调用参数 * @param method 方法 * @param methodObject 方法所属类 */ private void invoke(Class<?> clazz, Object val, Method method, Object methodObject) { if (clazz.isAssignableFrom(val.getClass())) { // 判断 传入参数类型是否匹配 // 如果找到,则调用对应object中的method方法 try { // method.setAccessible(true); // 如果需要支持私有方法监听,则需要设置访问权限 method.invoke(methodObject, val); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } // Sticky事件,TODO public void postSticky(Object event) { // 将 event 添加到 StickyEvent中 synchronized (EventBus.class) { stickyEvents.put(event.getClass(), event); } // 将Event发布到已有的订阅者中 post(event, false); } public void postSingleEvent(Object event) { // TODO 单次订阅,订阅完成后 将移除 订阅者列表; 此处功能复用,可维护性差 post(event, true); // EventBus开源的写法是 将其功能细分分装,方便维护; // removeSubscribeEvent(event); } private Object getStickyEvent(Class<?> clazz) { return stickyEvents.get(clazz); } private void removeSubscribeEvent(Object event) { // TODO } public void unregister(Object object) { Class clazz = object.getClass(); if (map.get(clazz) != null) { map.remove(clazz); } } } 2. Sticky事件 sticky事件即:事件消费者在时间发布之后注册也能接收到该事件的特殊类型。 未完待续,1. 跨进程通信; 2. 升级版:带生命周期的EventBus ==>> LiveEventBus; 3. 在页面可见时才收到消息的优化

iOS中Block探究

iOS中Block探究前言:我们开发过程中,block会使用得很多,在其他语言中也有类似block的语法,像javascript的闭包,函数里面的函数,java中的代码块,c中的函数指针等。很多时候它给我们代码带来便利,让代码结构清晰不少,了解它的本质是才是用好它的关键。一、概过闭包= -个函数「或指向函数的指针」+该函数执行的外部的上下文变量「也就是自由变量」; Block是Objective-C对于闭包的实现。其中,Block:●可以嵌套定义,定义Block方法和定义函数方法相似●Block可以定义在方法内部或外部●只有调用Block时候,才会执行其{}体内的代码●本质是对象,使代码高聚合使用clang将OC代码转换为C++文件查看block 的方法:●在命令行输入代码clang -rewrite-objc需要编译的OC文件.m●这时查看当前的文件夹里多了-个相同的名称的.cpp 文件,在命令行输入open main.cpp查看。比如一个正常的block类型转成c++代码的结构这样的:struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;NSIntegerage;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger age, int flags=0) : age(_age) {impl.isa = &amp;_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}二、Block的copy操作1、Block的存储 域及copy操作在开始研究Block的copy操作之前,先来思考-下: Block是存储在栈上还是堆上呢?我们先来看看-个由C/C++/0BJC编译的程序占用内存分布的结构:其实,block有三种类型:●全局块( NSConcreteGlobalBlock)●栈块(_ NSConcreteStackBlock)●堆块( NSConcreteMallocBlock)这三种block各自的存储域如下图:三、变量捕获(1)默认情况对于block外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量,不使用则不截获,因为截获的自动变量会存储于block的结构体内部,会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局布变量.(2) __block 修饰的外部变量对于用_ _block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。__block修饰一个变量之后,我们访问一个变量age变成了 (age->__forwarding->ag)这种指针访问形式。具体流程图如下:问:这里为什么有__forwarding指针,而不是age直接指向age答:因为这个__Block_byref_age_0对象刚开始都是在栈上,当block被copy到堆上的时候,自动也就copy了一份__Block_byref_age_0对象,这个时候,第一个age是栈上的,而它的__forwarding指针是指向堆上的它自己,所以需要用__forwarding的age来取值或者赋值,但是如果block是在栈上,那么这些都不会被copy到堆上,所以__forwarding还是指向自己,所以,这不论是在栈还是再堆都实现了指向自己,可以赋值或者取值。详细讲解资料以及代码demo看这里:http://gitlab.paat.com/app/study/tree/master/iOS/share

团队

TEAM

MORE
架构
python
运维
大数据
CRM
众包

开发者手册

DEVELOPER GUIDE

MORE

nginx

Nginx是一款轻量级的 Web 服务器/反向代理服务器及电子邮件代理服务器,可在 BSD-like 协议下发行。其特点是占有内存少,并发能力强。

指南 | Guides
核心 | Core
ngx_google_perftools_module

HTML

超文本标记语言,它通过标记符号来标记要显示的网页中的各个部分。网页文件本身是一种文本文件,通过在文本文件中添加标记符,可以告诉浏览器如何显示其中的内容(如:文字如何处理,画面如何安排,图片如何显示等)。浏览器按顺序阅读网页文件,然后根据标记符解释和显示其标记的内容,对书写出错的标记将不指出其错误,且不停止其解释执行过程,编制者只能通过显示效果来分析出错原因和出错部位。但需要注意的是,对于不同的浏览器,对同一标记符可能会有不完全相同的解释,因而可能会有不同的显示效果。

属性 | Attributes
要点 | Elements
其他 | Miscellaneous

Redis

集合 | Cluster
连接 | Connection
Geo

Vue.js

指南 | Guide
接口 | API