专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

动态化引擎原理解析与开发实践

  导读
  本文介绍了Android端动态化引擎的实现原理,并分步骤地实现了动态化引擎的各个模块,带领大家更直观、更深入地了解移动端动态化方案。
  文谢同学
  编辑乔
  本文共6820字,预计阅读时间20分钟。
  背景
  什么是动态化
  近年来,越来越多基于前端技术栈的动态化方案被引入到客户端开发中,那么,在开发写代码时,使用的客户端技术栈和前端技术栈有什么不同呢?
  简单来说,无论是Android还是iOS应用,在发布前都要经历源码编写、打包编译、发布应用商店、用户升级安装等过程。首先,编译速度会随着应用规模成正比增长,对于规模较大的应用,有时仅是修改一个控件的颜色,就需要等待几分钟来验证结果,开发效率较低;其次,发布应用商店、用户升级安装,会极大地拉长应用发布周期,延迟产品效果的验证;最后,开发一个相同功能,至少需要双端各一个工程师,写两份代码,人力成本增加。
  前端开发流程中,由于是使用JavaScript脚本语言开发,并不需要提前编译,在浏览器中可以直接预览效果;新版本发布后,也可以直接触达到浏览器,用户无需额外操作;最重要的是,对于一个相同功能,只需要开发一次,即可在几乎所有操作系统的浏览器上运行。
  了解了两种开发模式后,我们会想到,为什么不将前端技术栈的开发流程引入到客户端中,让客户端应用也拥有前端的动态性和跨端性呢?
  其实业界早已有各种各样的解决方案,例如ReactNative、Weex、微信小程序等。甚至某些特定场景下,客户端功能已经完全使用前端技术进行开发了,比如需要动态下发的运营活动、需要快速试错的产品功能、小程序类的应用内生态建设等。
  动态化引擎是动态化方案中最核心的模块,有了动态化引擎,才能使开发者编写的JS应用运行在客户端中,实现UI和逻辑的动态化。
  HelloHybridWorld
  动态化没有想象中的困难,只要了解其中原理,每个同学都能从0到1打造一个动态化引擎。更进一步,我们甚至可以用自己实现的动态化引擎,为它编写一个如下的JS应用:
  从应用开发者的角度来看,要实现这样一个界面,需要在JS代码中创建纵向布局、文本组件、图片组件以及按钮组件,且按钮可以设置点击事件。
  这些就是动态化引擎需要支持的一部分能力,还有更多底层的能力开发者无法直观感受到,下一节会详细介绍。
  StepbyStep打造专属动态化引擎
  笔者是Android工程师,所以会使用Android及Java技术栈实现,iOS或其他端原理是类似的。
  Step1:目标拆解
  Question:要实现一个基于前端技术栈的动态化引擎,需要哪些模块?
  下图展示了一个基础的动态化引擎所需的模块和组件:
  从上到下依次是:
  模块
  应用
  BusinessCode
  JS应用的界面和逻辑代码
  JSFramework
  业务代码之下的一层JS运行时封装,提供了诸如生命周期回调、应用入口函数、VDom、Diff算法等基础能力,直接与Native侧进行通信
  JSEngine
  JS虚拟机,运行JS代码的核心模块,如V8、JavaScriptCore等
  JSBridge
  JS和Native之间双向通信的通道
  ModuleManager
  通常是所有Native桥的集合,并提供桥的注册、获取等方法
  RenderManager
  管理应用的渲染流程,例如解析JSFramework发来的VDom数据、渲染指令、构建Native侧的Dom树、View树等
  Debugging
  调试能力支持,主要是和CDP协议(ChromeDevToolsProtocol)对接,可在ChromeDevTools上进行调试操作
  NativeModules
  Native侧实现的桥,基本上是对NativeAPI的二次封装,供JS侧调用
  NativeComponents
  Native侧实现的控件,基本上是对NativeView的二次封装,供JS侧调用
  熟悉动态化引擎的重要模块之后,下一步我们就开始逐步实现。
  Step2:JS引擎
  JS引擎是处理JavaScript脚本的虚拟机,是动态化的前提和基础,开发者可通过JS引擎在客户端应用中运行JS代码。
  目前常见的JS引擎有V8和JavaScriptCore。
  V8引擎是C实现的。由于我们在Android中开发,所以需要使用J2V8。J2V8是V8引擎的Java封装,提供了各种易用的接口。
  J2V8:
  https:github。comeclipsesourceJ2V8
  依赖dependencies{implementationcom。eclipsesource。j2v8:j2v8:6。2。1aar}
  创建V8引擎V8runtimeV8。createV8Runtime();
  Native执行JS脚本
  执行一段JS逻辑:V8runtimeV8。createV8Runtime();intresultruntime。executeIntegerScript(vari0;i;i);System。out。println(result:result);result:1
  Native执行JS方法
  定义一个JS方法并执行:V8runtimeV8。createV8Runtime();runtime。executeVoidScript(functionadd(a,b){returnab});V8ArrayargsnewV8Array(runtime)。push(1)。push(2);intresultruntime。executeIntegerFunction(add,args);System。out。println(result:result);result:3
  封装JS引擎
  我们可以将引擎部分抽象成两个模块JsBundle和JsContext。
  JsBundle
  JsBundle是JS应用的打包文件,包含了应用的所有源码和资源,如本地图片资源和应用信息清单。不过,我们只是实现一个简单的动态化框架,暂时只包含JS源码文件就OK,或直接把一个。js文件当作bundle也可以。publicclassJsBundle{privateStringmAppJavaScript;publicStringgetAppJavaScript(){returnmAppJavaScript;}publicvoidsetAppJavaScript(StringappJavaScript){this。mAppJavaScriptappJavaScript;}}
  mAppJavaScript就是应用的JS代码。
  JsContext
  JsContext是对V8引擎的二次封装,用来描述一个JS引擎如何初始化和执行应用JS代码:publicclassJsContext{privateV8mEngine;publicJsContext(){init();}privatevoidinit(){mEngineV8。createV8Runtime();}publicV8getEngine(){returnmEngine;}publicvoidrunApplication(JsBundlejsBundle){mEngine。executeStringScript(jsBundle。getAppJavaScript());}}
  理论上,当我运行下面代码时,一个JS引擎就启动起来了,并可以执行任意和Native无关的JS代码:JsBundlejsBundlenewJsBundle();jsBundle。setAppJavaScript(vara1);JsContextjsContextnewJsContext();jsContext。runApplication(jsBundle);
  Tips:如果想使用Native的能力,还需要在引擎初始化之前,注入所谓的桥,用来完成JS到Native的通信
  Step3:双向通信JSBridge
  上节中,我们介绍了如何创建一个V8引擎并执行JS脚本了。但是,想做到JS调用原生系统的能力、原生系统通知JS有事件发生,则需要一种通信机制,也就是桥JSBrdige。
  作为一种双向通信机制,JSBridge保证了JS代码可以使用原生系统能力(如拍照、访问网络、获取设备信息等);同时当原生系统有消息或事件发生时,也可以通知到JS侧(如陀螺仪监听、推送消息触达、用户点击事件等)。
  JS执行Native方法
  V8引擎提供了向JS注入Native方法的能力,比如前端中最常见的console。info函数,我们可以这样实现:V8runtimeV8。createV8Runtime();V8ObjectconsolenewV8Object(runtime);console。registerJavaMethod((v8Object,params){Stringmsgparams。getString(0);Log。i(TAG,msg);returnnull;},info);runtime。add(console,console);
  console。info(printsomemessages!)
  在adblogcat中,我们就会看到这样一条日志打出来。
  Native执行JS函数
  直接执行executeScript,调用一个已经定义好的JS函数:functionsayHello(){returnHelloHybridWorld!}V8runtimeV8。createV8Runtime();Stringresultruntime。executeStringScript(sayHello());HelloHybridWorld!
  JS可以传递一个V8Function到Native,比如实现一个监听经纬度变化的回调:V8runtimeV8。createV8Runtime();V8ObjectdevicenewV8Object(runtime);console。registerJavaMethod((v8Object,params){V8Functionlistener(V8Function)params。getObject(0);V8ArraylocationsnewV8Array(runtime)。push(116。1234567)。push(46。1234567);listener。call(v8Object,locations);returnnull;},onLocationChanged);runtime。add(device,device);device。onLocationChanged(listener:function(x,y){console。info(x);})116。1234567
  构建JSBridge
  了解了如何利用V8引擎的能力实现JSNative双向通信后,现在我们将这些行为和信息进行抽象,从而更方便地对桥进行管理和注册。
  可以用JsModule代表一个Native桥的能力:publicabstractclassJsModule{publicabstractStringgetName();publicabstractListStringgetFunctionNames();publicabstractObjectexecute(StringfunctionName,V8Arrayparams);}console。info方法的抽象publicclassConsoleModuleextendsJsModule{OverridepublicStringgetName(){returnconsole;}OverridepublicListStringgetFunctionNames(){ListStringfunctionsnewArrayList();functions。add(info);returnfunctions;}OverridepublicObjectexecute(StringfunctionName,V8Arrayparams){switch(functionName){caseinfo:Log。i(JavascriptConsole,params。getString(0));break;}returnnull;}}
  使用ModuleManager来管理和注册所有的JsModule:publicclassModuleManager{privateModuleManager(){}privatestaticclassHolder{privatestaticfinalModuleManagerINSTANCEnewModuleManager();}publicstaticModuleManagergetInstance(){returnHolder。INSTANCE;}privatefinalListJsModulemModuleListnewArrayList();privateJsContextmJsContext;publicvoidinit(JsContextjsContext){mJsContextjsContext;mModuleList。add(newUiModule());mModuleList。add(newConsoleModule());registerModules();}privatevoidregisterModules(){for(JsModulemodule:mModuleList){V8ObjectmoduleObjnewV8Object(mJsContext。getEngine());for(StringfunctionName:module。getFunctionNames()){moduleObj。registerJavaMethod((v8Object,params){returnmodule。execute(functionName,params);},functionName);}mJsContext。getEngine()。add(module。getName(),moduleObj);}}}
  至此,动态化框架已经支持了JS调用Native能力,大家可以继承JsModule,编写任意所需要的桥,实现各种各样的能力。
  相比于上一节,现在JS应用的代码可以包含Native相关的方法了,不再局限于最原始的JS环境。
  Step4:渲染引擎
  到这一步,我们已经可以实现逻辑动态化了,理论上,所有不需要用户交互的逻辑行为都可以放到JS中执行。
  但一个现代化的应用,除了有后台逻辑,更重要的一部分是直接面向用户的UI界面,这意味着用户对应用的第一印象,所以这一节我们来看看如何实现UI动态化。
  UIFramework
  Android开发者都很熟悉XML,我们会在其中定义静态页面结构,例如:lt;?xmlversion1。0encodingutf8?LinearLayoutxmlns:androidhttp:schemas。android。comapkresandroidandroid:layoutwidthmatchparentandroid:layoutheightmatchparentandroid:orientationverticalTextViewandroid:layoutwidthmatchparentandroid:layoutheightwrapcontentandroid:layoutmarginTop16dpandroid:gravitycenterandroid:textHelloHybridWorld!android:textSize24spImageViewandroid:layoutwidthwrapcontentandroid:layoutheightwrapcontentandroid:layoutgravitycenterandroid:layoutmarginTop24dpandroid:srcdrawableiclauncherbackgroundButtonandroid:layoutwidthwrapcontentandroid:layoutheightwrapcontentandroid:layoutgravitycenterandroid:layoutmarginTop32dpandroid:textBUTTONLinearLayout
  Question:XML为什么能转换成屏幕上的UI组件?
  简单来说,AndroidUI框架会读取并解析XML文件,然后将其构建成一个一个View和ViewGroup,形成一棵页面的View树,最后交由系统自顶向下进行渲染,显示到屏幕上。所以,XML是AndroidUI框架的一种DSL,View系统是AndroidUI框架的一种渲染引擎。
  我们了解了UI框架核心的两点:面向开发者的DSL;面向操作系统的渲染引擎。
  ReactNative、Weex等原生渲染的动态化框架,其实改变的是DSL这一层,只是开发者书写UI的方式变了,但界面依然是构建成View树进行渲染。然而,像Flutter、JetpackCompose等UI框架不仅改变了DSL,也使用了完全不同的渲染引擎(基于skia),实现了在Android设备上的UI绘制。
  UI动态化
  要实现UI动态化,核心原理是通过构建页面的DSL支持动态下发,渲染引擎支持动态解析和创建视图组件即可。
  最简单的DSL,可以用JSON结构表示界面元素及布局。例如,在本文开篇中,我们期望实现的界面可以这样描述:consthelloHello;consttitlehelloHybridWorld!view。render({rootView:{type:verticalLayout,children:〔{type:text,text:title,textSize:24,marginTop:16},{type:image,width:72,height:72,marginTop:80,url:},{type:button,text:点击打印日志,marginTop:80,marginLeft:40,marginRight:40,onClick:function(){console。info(success!)}}〕}})
  我们定义了一个view。render方法,作为界面绘制的入口函数,当JS执行到这个方法时,就会开始渲染;在这之前,大家可以写任意界面无关的逻辑。
  Tips:React和Vue都是前端的UI框架,它们拥有直观的的DSL语法、强大的VDom机制以及各种语法糖,可以让开发者很轻松的编写UI界面,这也是UIDSL的目标之一。笔者使用JSON作为UIDSL,因为其数据结构最常见也易于理解,不需要额外的语法解析器就能实现,真正业界的UIDSL要比这个复杂得多。
  既然view。render是一个Native桥,那么就用上一节定义的JsModule来实现:publicclassUiModuleextendsJsModule{OverridepublicStringgetName(){returnview;}OverridepublicListStringgetFunctionNames(){ListStringfunctionNamesnewArrayList();functionNames。add(render);returnfunctionNames;}OverridepublicObjectexecute(StringfunctionName,V8Arrayparams){switch(functionName){caserender:V8Objectparam1params。getObject(0);V8ObjectrootViewObjparam1。getObject(rootView);RenderManager。getInstance()。render(rootViewObj);break;}returnnull;}}
  view。render方法传进来的是一个对象,其中rootView字段表明这个界面的根布局;一般来说,一个界面只能有一个根节点,根节点下面会有很多子节点,最终形成一个树状结构。
  rootView节点下有type字段,表示它是一个verticalLayout类型,即纵向布局;以及children字段,表明其子节点都有哪些。
  children数组中的第一个子节点是type为text的文本组件,它有很多属性,如文字大小、间距等;类似地,剩下的子节点分别是image图片组件和button按钮组件,也同样有各自的属性。
  DomElement
  JS传递过来的对象,会以V8Object的形式承载,不方便直接进行操作。我们可以将JS传递过来的V8Object抽象成DomElement,表示一个节点元素的属性信息,也方便之后NativeView使用这些属性。
  DomElement是数据类,直接对应JS侧传递过来的视图节点信息。视图元素可以有公用的属性publicclassDomElement{publicStringtype;publicintmarginTop;publicintmarginBottom;publicintmarginLeft;publicintmarginRight;publicV8FunctiononClick;publicvoidparse(V8Objectv8Object){for(Stringkey:v8Object。getKeys()){switch(key){casetype:this。typev8Object。getString(type);break;casemarginTop:this。marginTopv8Object。getInteger(marginTop);break;casemarginBottom:this。marginBottomv8Object。getInteger(marginBottom);break;casemarginLeft:this。marginLeftv8Object。getInteger(marginLeft);break;casemarginRight:this。marginRightv8Object。getInteger(marginRight);break;caseonClick:this。onClick(V8Function)v8Object。get(onClick);break;default:break;}}}}每个具体的视图元素也可以有自己独有的属性publicclassDomTextextendsDomElement{publicStringtext;publicinttextSize;publicStringtextColor;Overridepublicvoidparse(V8Objectv8Object){super。parse(v8Object);for(Stringkey:v8Object。getKeys()){switch(key){casetext:this。textv8Object。getString(text);break;casetextSize:inttextSizev8Object。getInteger(textSize);if(textSize0){textSize16;}this。textSizetextSize;break;casetextColor:StringtextColorv8Object。getString(textColor);if(TextUtils。isEmpty(textColor)){textColor000000;}this。textColortextColor;break;}}}}
  Question:可以尝试编写剩下所需要的DomElement。如:DomButton、DomVerticalLayout等。
  我们还需要一个DomFactory,使用工厂模式来创建不同类型的DomElement:publicclassDomFactory{publicstaticDomElementcreate(V8ObjectrootV8Obj){StringtyperootV8Obj。getString(type);switch(type){casetext:DomTextdomTextnewDomText();domText。parse(rootV8Obj);returndomText;caseimage:DomImagedomImagenewDomImage();domImage。parse(rootV8Obj);returndomImage;casebutton:DomButtondomButtonnewDomButton();domButton。parse(rootV8Obj);returndomButton;caseverticalLayout:DomVerticalLayoutdomVerticalLayoutnewDomVerticalLayout();domVerticalLayout。parse(rootV8Obj);returndomVerticalLayout;}returnnull;}}
  Tips:工厂模式只是其中一种实现方式,可以有更多灵活的创建方法,如利用注解记录类型信息,反射生成对应的DomElement对象,优点是创建对象完全自动化了,当以后有几十个UI控件时,不需要手动实例化。
  然后就可以创建一颗JS侧根布局的DomElement树:V8ObjectrootViewObj。。。;DomElementrootViewElementDomFactory。create(rootViewObj);
  JsView
  我们已经可以在Native中随意访问节点元素数据了,目的是为了给即将被渲染出来的NativeView使用。因为NativeView需要知道自己应该如何展示、展示什么文案、响应什么点击事件等。
  不过,直接在view。render方法执行后实例化NativeView、设置DomElement中的属性、构建NativeView树,会使UiModule类过于臃肿,所以我们还需要一个中间层抽象出NativeView所对应的虚拟视图JsView。
  JsView的作用是使元素节点更加内聚,只需要关注如何创建自己,JsView也和DomElement一样会构建出一颗树,用来表示界面结构;每个JsView都有createView方法,用来返回其真正对应的NativeView实例:
  publicabstractclassJsViewVextendsView,DextendsDomElement{protectedDmDomElement;protectedVmNativeView;publicvoidsetDomElement(DomElementdomElement){mDomElement(D)domElement;}publicabstractStringgetType();publicabstractVcreateViewInternal(Contextcontext);publicVcreateView(Contextcontext){VviewcreateViewInternal(context);mNativeViewview;returnview;}}
  比如,文本组件需要继承自JsView:publicclassTextJsViewextendsJsViewTextView,DomText{OverridepublicStringgetType(){returntext;}OverridepublicTextViewcreateViewInternal(Contextcontext){TextViewtextViewnewTextView(context);textView。setGravity(Gravity。CENTER);textView。setText(mDomElement。text);textView。setTextSize(mDomElement。textSize);textView。setTextColor(Color。parseColor(mDomElement。textColor));returntextView;}}
  Question:可以尝试编写剩下的JsView。如:ButtonJsView、VerticalLayoutJsView等。
  同样地,我们仍然需要一个JsViewFactory来创建不同类型的JsView实例,如同DomElement一样,这里就不赘述了。
  最后,我们可以使用RenderManager来管理DSL的解析、DomElement树的创建、JsView树的创建和NativeView的渲染。同时,RenderManager也需要一个NativeView容器,来承载JS渲染出来的根布局:publicclassRenderManager{privateRenderManager(){}privatestaticclassHolder{privatestaticfinalRenderManagerINSTANCEnewRenderManager();}publicstaticRenderManagergetInstance(){returnHolder。INSTANCE;}privateContextmContext;privateViewGroupmContainerView;publicvoidinit(Contextcontext,ViewGroupcontainerView){mContextcontext;mContainerViewcontainerView;}publicvoidrender(V8ObjectrootViewObj){DomElementrootDomElementDomFactory。create(rootViewObj);JsViewrootJsViewJsViewFactory。create(rootDomElement);if(rootJsView!null){ViewrootViewrootJsView。createView(mContext);mContainerView。addView(rootView);}}}
  Step5:整合动态化引擎
  目前为止,我们几乎完成了动态化引擎所需要的所有模块,现在只剩下把它组装起来了。
  我们期望Native在创建动态化引擎时,可以很方便地使用,所以可以将整个动态化容器对外抽象成一个JsApplication:publicclassJsApplication{privateJsContextmJsContext;publicstaticJsApplicationinit(Contextcontext,ViewGroupcontainerView){JsApplicationjsApplicationnewJsApplication();JsContextjsContextnewJsContext();jsApplication。mJsContextjsContext;RenderManager。getInstance()。init(context,containerView);ModuleManager。getInstance()。init(jsContext);returnjsApplication;}publicvoidrun(JsBundlejsBundle){mJsContext。runApplication(jsBundle);}}
  在MainActivity中,只需要初始化JsApplication,并执行JsBundle即可:FrameLayoutcontainerViewfindViewById(R。id。jscontainerview);JsBundlejsBundlenewJsBundle();jsBundle。setAppJavaScript(JSCODE);JsApplicationjsApplicationJsApplication。init(this,containerView);jsApplication。run(jsBundle);
  一个基础的动态化引擎已经完成了,只要我们理解了动态化引擎核心的原理和必要的模块,最终的实现方法就多种多样了,大家可以用自己熟悉、擅长的方式,改造这个引擎的各个模块。
  比如:将工厂模式创建JsView改造成注解自动实例化;或者将JSONDSL改造成类Vue的声明式语法;再或者直接使用Lua替换JavaScript,替换应用开发语言。
  下图是使用VSCode编写的JS应用,及实际运行在手机上的效果:
  总结
  笔者已将实现好的动态化引擎放到GitHub上,大家可以clone后按照自己的想法进行修改。
  动态化引擎:
  https:github。comkwaiecHybridDemo
  本文主要介绍了动态化引擎有哪些核心模块,并将每个模块的实现方法分步骤展开,希望大家能从手动实现的过程中,理解动态化引擎的原理,也了解前端技术栈和客户端的不同之处。
  大家感兴趣的话,可以再继续完善这个动态化引擎,添加自己想要的能力,写出更多有趣的JS应用。
  作者:谢同学
  来源:微信公众号:快手大前端技术
  出处:https:mp。weixin。qq。comsmwMQgXC2RwHbIWeXXUpWA

盛唐至尊从唐朝的衰亡看牡丹读者朋友们,在阅读文章之前,辛苦您动动小手点击一下关注,我们将持续更新,既方便您后续的阅读,又可以与志同道合的读友进行讨论,感谢您的支持。引言唐代经济繁荣,政治开放,盛行赏牡丹之风朝鲜王朝初期,邻保制度在实行过程中,遇到了哪些变故?五家作统制的作用朝鲜王朝初期,邻保制度在实行过程中,遇到了哪些变故?首先,在回答这个问题前,我们需要知道,公元1392年,李成桂通过威化岛回师登上王位,成为了新王朝的缔造者。朝鲜王伽马说外国史日本战国时代的开始与重新统一日本在足利幕府统治下中世纪的后期,地方封建领主和他们的军队势力持续上升,京都足利幕府的政策逐渐被地方选择性忽视或拒不执行,即使在京都近邻区域的地方大名也对足利幕府的威严不太重视,每被抛弃的3000残军,留6万后代在邻国建起繁华小镇毕生怀念中国头条创作挑战赛在抗日战争时期,有这样的一群人,残兵败将,他们先是被大部队抛弃,然后又被人利用,甚至被历史所遗忘在异国他乡。但就是这群命途多舛的残兵,他们的后代却在异国他乡建立起了一一图读懂先进的微芯片生产依赖于中国台湾省波士顿咨询公司公布的数据显示,最先进的计算和处理芯片类型的半导体切片即晶圆的生产是多么集中在一个地方。中国台湾省拥有92的逻辑半导体生产,其组件小于10纳米。图按类型和地点划分全球去全球化?新全球化?世界产业重构背景下,中国经济的危与机导读当前世界正经历产业重构与地缘政治重构,这对深度融入全球化的中国经济构成重大挑战。全球产业格局的重构既是去全球化的表现,又是全球化新安排的机会。中国需要在2023年采取新的经济策夜经济唤醒中国春城烟火气视频加载中新华社昆明1月13日电(记者赵珮然)华灯初上,春城昆明的南强街巷里人头攒动,年味渐浓。还不到晚上8点,王翠秀老人的鲜花摊已经卖出了存货的三分之一,手机到账提示声响个不停。月薪515k!六险两金宿舍!中国人民财产保险招20人!01hr公司简介中国人民财产保险股份有限公司(PICCPC)的前身是1949年10月1日成立的中国,人民保险公司,总部设在北京,是中国人民保险集团股份有限公司(PICCGroup,国漫复兴,全网刷屏的中国奇谭好在哪?2023年第一天,视频网站bilibili(B站)上线了一部原创动画中国奇谭。仅仅播出三集,该片的播放量就持续走高,动画口碑也维持高位。目前该片在B站已累计播放6524万次,超3万揭秘高分国创动画中国奇谭林林中的林林是人还是狼?上海美术电影制片厂(简称上美影)与B站联合出品的动画短片集中国奇谭正在热播,小妖怪的夏天和鹅鹅鹅播出之后,观众对于第三集林林承载了更多的期待,最新播出的林林由杨木执导,这集的场景灵重回NBL!周琦将签下大合同,回归辽篮悬念揭晓,恭喜中国男篮!大家好,我是詹妹,我们一起来关注CBA,第二阶段的比赛正在如火如荼进行,已经剩下不到一周的时间,第二阶段的比赛就会落下帷幕,而姚明在此时也做出了一个重大的决定,我们将会在CBA第三
17个婴儿护理技巧老月嫂经验面面俱到01岁是宝宝护理的重要时间段,刚出生的宝宝是很脆弱的,需要妈妈用细心和耐心来好好呵护。很多宝妈在养育宝宝的时候由于没有经验,会走很多弯路。最近在跟客户聊天的过程中,发现她虽然是个宝晚秋(诗歌)黄昏时分太阳渐渐地消失在地平线上钟声从遥远中传来我的思绪充满了梵音梦幻般的世界梦幻般的人生在人间我失落了多少情愫失落了热情失落了善良失落了天真我的白马王子啊,你在哪里?我亲爱的姑娘陕西一名酒,曾经落魄凤凰不如鸡,现在强势回归,能否逆风翻盘?要问陕西人最爱喝什么,他们一定会说西凤!外省人喝一口就干呕的西凤靠着陕西人强大的支持,每年卖到60亿,虽然比起其它三大名酒(茅台,汾酒,泸州老窖)确实不算多,但瘦死的骆驼比马大,西三星长公主vs华为长公主穿搭,我明白了气质比颜值更重要女性企业家的穿搭也太卷了,看看三星长公主和华为长公主的穿搭对比,你就知道顶级的审美穿搭有多显气质。同样是长款风衣,两人搭配出的风格和气质却截然不同,一为是时尚又清冷,一位是优雅且知男性也有生理期?提醒除了喝热水,男生还得做好这6件事女性每个月都会来月经是众所周知的事情,但男性会不会来生理期就不得而知了。虽然不会像女性一样来月经,但也会有不适的症状。当男性突然出现异常时,女性要给予理解,帮助男性顺利度过这个特殊2017年,不丹五公主的丹凤眼真惊艳,却被穿蓝裙的真子公主抢风头2017年5月,26岁的真子公主身穿一件湖蓝色的长裙,脚上穿着一双黑色尖头高跟鞋,来到不丹王室进行访问。只见真子公主手拿黑色小挎包,看起来姿态大方又优雅,湖蓝色的服饰让她显得优雅又明日立冬,不管多忙,记得吃3物忌2事,身心舒畅迎寒冬季节交替总是让人毫无准备,转眼明天就是立冬,逐渐降低的温度让人感到愈发寒冷,除必要的保暖措施之外,身体内部的调理工作也是时候该提上日程。人们常说内外兼修,这对于身体保养自然也是说得立冬将至,不论贫富多吃水中5宝,补充营养不受寒冬之苦寒来暑往,四季流转,转眼的时间我们就要跨进立冬的门槛。立冬在古代也是四时八节之一,是老百姓非常重视的季节节点之一。老话说春生夏长秋收冬藏,立冬是冬季的开始,这也就意味着万物都开始收吴起出将入相第一人,为何又是不仁不义不孝的代表?吴起(?前381年),战国时卫国左氏(今山东省定陶县西)人。战国初期著名的四大家,即政治家军事家改革家,兵家的代表人物。吴起和比他早100年的兵圣孙武齐名,后世将两人并称为孙吴,吴决赛赛程出炉!樊振东代表上海挑战老东家,王艺迪陈幸同合力争冠备受球迷关注的全国乒乓球锦标赛团体决赛,将于11月6日下午和晚上先后展开女团和男团的终极决赛。樊振东领衔的上海队将接受老东家广东队的挑战,女团方面,王艺迪和陈幸同联合出战的辽宁女排女神们一定要知道买秋冬衣服的定律头条创作挑战赛快到双十一剁手节,作为女神们,外内都要提升,内涵提升的同时,外表也要配合当下潮流,网购,线下买衣服的技巧你掌握了吗?女生衣柜都是满满的,但每天都是没有衣服可穿得感觉。
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网