GVKun编程网logo

自定义 Web 框架与 jinja2 模板(web平台自定义设置)

1

对于自定义Web框架与jinja2模板感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍web平台自定义设置,并为您提供关于Android自定义View详解AndroidView绘制流程之Deco

对于自定义 Web 框架与 jinja2 模板感兴趣的读者,本文将会是一篇不错的选择,我们将详细介绍web平台自定义设置,并为您提供关于Android 自定义 View 详解 Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解、Azure CDN 是否有助于提高 Microsoft bot 框架与 Twilio Adapter 一起工作的性能、Azure 函数应用:Flask 框架与 Azure 函数的集成、C++ 框架与 Java 框架的对比分析的有用信息。

本文目录一览:

自定义 Web 框架与 jinja2 模板(web平台自定义设置)

自定义 Web 框架与 jinja2 模板(web平台自定义设置)

web 应用与 web 框架

web 应用

对于所有的 Web 应用,本质上其实就是一个 socket 服务端,用户的浏览器其实就是一个 socket 客户端

import socket

def handle_request(client):

    buf = client.recv(1024)
    client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8"))
    client.send("<h1color:red''>Hello, yuan</h1>".encode("utf8"))

def main():

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((''localhost'',8001))
    sock.listen(5)

    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()

if __name__ == ''__main__'':

    main()

最简单的 Web 应用就是先把 HTML 用文件保存好,用一个现成的 HTTP 服务器软件,接收用户请求,从文件中读取 HTML,返回。

如果要动态生成 HTML,就需要把上述步骤自己来实现。不过,接受 HTTP 请求、解析 HTTP 请求、发送 HTTP 响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态 HTML 呢,就得花个把月去读 HTTP 规范。

正确的做法是底层代码由专门的服务器软件实现,我们用 Python 专注于生成 HTML 文档。因为我们不希望接触到 TCP 连接、HTTP 原始请求和响应格式,所以,需要一个统一的接口,让我们专心用 Python 编写 Web 业务。

这个接口就是 WSGI:Web Server Gateway Interface。

wsgiref 简单介绍

 

实现一个最简单的框架

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    <h1>index</h1>
    <h2>{{ name[0] }}</h2>
    <h3>{{ name[1] }}</h3>
</body>
</html>

start.py

from wsgiref.simple_server import make_server
from urls import urls

def app(env,response):
    print(env)
    route = env[''PATH_INFO'']

    response("200 OK",[(''Content-type'',''text/html'')])
    data = urls[''error'']()
    if route in urls:
        data = urls[route]()

    return [data]


if __name__ == ''__main__'':
    server = make_server(''localhost'',8886,app)
    print("http://localhost:8886")
    server.serve_forever() 

urls.py

from views import *
urls ={
    ''/index'':index,
    ''error'':error,
} 

views.py

#处理请求的功能函数(处理结果返回的都是页面 ————> views)
# 利用jinja2来渲染模板,将后台数据传输给前台

from jinja2 import Template

def index():
    with open(''templates/index.html'',''r'') as f:
        dt = f.read()
    tem = Template(dt)


    resp = tem.render(name=["主页","附页"])
    return resp.encode(''utf-8'')

def error():
    return b"404"

增加一点难度

manage.py

from wsgiref.simple_server import make_server

#  request            response


from app01.views import *

from app01 import urls


def routers():

    URLpattern=urls.URLpattern

    return URLpattern


def applications(environ,start_response):

    path=environ.get("PATH_INFO")
    print("path",path)
    start_response(''200 OK'', [(''Content-Type'', ''text/html''),(''Charset'', ''utf8'')])

    urlpattern=routers()
    func=None

    for item in urlpattern:
        if path==item[0]:
            func=item[1]
            break
    if func:
        return [func(environ)]
    else:
        return [b"<h1>404!<h1>"]
    # return [b"<h1>hello world<h1>"]

if __name__ == ''__main__'':

    t=make_server("",8810,applications)
    print("server is working...")
    t.serve_forever()

urls

from app01.views import *


URLpattern = (
    ("/login/", login),
)

views

import pymysql

from urllib.parse import parse_qs


def login(request):

    if request.get("REQUEST_METHOD")=="POST":
        print("+++++",request)


        #当请求方式是GET时
        # user_union,pwd_union=request.get("QUERY_STRING").split("&")
        # _,user=user_union.split("=")
        # _,pwd=pwd_union.split("=")

        # 环境变量 CONTENT_LENGTH 可能是空值 或者 值丢失
        try:
            request_body_size = int(request.get(''CONTENT_LENGTH'', 0))
        except (ValueError):
            request_body_size = 0
        # 当请求方式是POST时, 变量将会被放在存在域wsgi.input文件中的HTTP请求信息中, 由WSGI 服务器一起发送.
        request_body = request[''wsgi.input''].read(request_body_size)
        d = parse_qs(request_body)


        user=d.get(b"user")[0].decode("utf8")
        pwd=d.get(b"pwd")[0].decode("utf8")

        print("user",user,pwd)

        #连接数据库
        conn = pymysql.connect(host='''',port= 3306,user = ''root'',passwd='''',db=''s6'') #db:库名
        #创建游标
        cur = conn.cursor()

        SQL="select * from userinfo2 WHERE NAME =''%s'' AND PASSWORD =''%s''"%(user,pwd)

        cur.execute(SQL)

        if cur.fetchone():

            f=open("templates/backend.html","rb")

            data=f.read()
            data=(data.decode("utf8"))%user
            return data.encode("utf8")

        else:
             return b"user or pwd is wrong"

    else:
        f = open("templates/login.html", "rb")

        data = f.read()
        f.close()

        return data

models

import pymysql

import pymysql
#连接数据库
conn = pymysql.connect(host='''',port= 3306,user = ''root'',passwd='''',db=''s6'') #db:库名
#创建游标
cur = conn.cursor()

# sql=''''''
# create table userinfo2(
#         id INT PRIMARY KEY ,
#         name VARCHAR(32) ,
#         password VARCHAR(32)
# )
#
# ''''''
#
# cur.execute(sql)
#
# cur.executemany("insert into userinfo2 values(%s,%s,%s)", [(1,"yuan","123"),
#                                                           (2,"alex","456"),
#                                                           (3,"egon","789")])

cur.execute("select * from userinfo2 WHERE NAME=''yuan'' AND PASSWORD =''123''")
#提交
conn.commit()
#关闭指针对象
cur.close()
#关闭连接对象
conn.close()

模板

要了解 jinja2,那么需要先理解模板的概念。模板在 Python 的 web 开发中广泛使用,它能够有效的将业务逻辑和页面逻辑分开,使代码可读性增强、并且更加容易理解和维护。

模板简单来说就是一个其中包涵占位变量表示动态的部分的文件,模板文件在经过动态赋值后,返回给用户。  --> 可以理解为渲染

python 中自带一个简单的模板,就是 string 提供的。

>>> import string
>>> a = string.Template(''$who is $role'')
>>> a.substitute(who=''daxin'',role=''Linux'')
''daxin is Linux''
>>> a.substitute(who=''daxin'',role=''cat'')
''daxin is cat''
>>>

Python 自带的模板功能极其有限,如果我们想要在模板中使用控制语句,和表达式,以及继承等功能的话,就无法实现了。

目前主流的模板系统,最常用的就是 jinja2 和 mako

jinja2

jinja2 是 Flask 作者开发的一个模板系统,起初是仿 django 模板的一个模板引擎,为 Flask 提供模板支持,由于其灵活,快速和安全等优点被广泛使用。

jinja2 之所以被广泛使用是因为它具有以下优点

相对于Template,jinja2更加灵活,它提供了控制结构,表达式和继承等。
相对于Mako,jinja2仅有控制结构,不允许在模板中编写太多的业务逻辑。
相对于Django模板,jinja2性能更好。
Jinja2模板的可读性很棒。

安装 jinja2

由于 jinja2 属于第三方模块,首先需要对其进行安装

pip3 install jinja2

测试模板是否安装成功

python -c "import jinja2"
# 没有报错就表示安装成功
# 必须用双引号"

jinja2 语法

作为一个模板系统,它还提供了特殊的语法,我们按照它支持的语法进行编写之后,就能使用 jinja2 模块进行渲染

基本语法

在 jinja2 中,存在三种语法

控制结构 {% %}
变量取值 {{ }}
注释 {# #}

下面是一个简单的 jinja2 例子

{# This is jinja code
 
    {% for file in filenames %}
    ...
    {% endfor %}
 
#}

可以看到,for 循环的使用方式和 Python 比较类似,但是没有了句尾的冒号,另外需要使用 endfor 最为结尾,其实在 jinja2 中,if 也是一样的,结尾需要使用 endif。

jinja2 变量

jinja2 模板中使用 {{}} 语法表示一个变量,它是一种特殊的占位符。当利用 jinja2 进行渲染的时候,它会把这些特殊的占位符进行填充 / 替换,jinja2 支持 python 中所有的 Python 数据类型比如列表、字段、对象等

<p>this is a dicectory:{{ mydict[''key''] }} </p>
<p>this is a list:{{ mylist[3] }} </p>
<p>this is a object:{{ myobject.something() }} </p>

jinja2 中的过滤器

变量可以通过 “过滤器” 进行修改,过滤器可以理解为是 jinja2 里面的内置函数和字符串处理函数。

常用的过滤器有:

过滤器名称     说明     safe  渲染时值不转义 capitialize  把值的首字母转换成大写,其他子母转换为小写  lower  把值转换成小写形式   upper  把值转换成大写形式   title  把值中每个单词的首字母都转换成大写  trim  把值的首尾空格去掉  striptags  渲染之前把值中所有的 HTML 标签都删掉 join   拼接多个值为字符串  replace  替换字符串的值  round  默认对数字进行四舍五入,也可以用参数进行控制 int   把值转换成整型

那么如何使用这些过滤器呢? 只需要在变量后面使用管道 (|) 分割,多个过滤器可以链式调用,前一个过滤器的输出会作为后一个过滤器的输入

{{ ''abc'' | captialize  }}
# Abc
 
{{ ''abc'' | upper  }}
# ABC
 
{{ ''hello world'' | title  }}
# Hello World
 
{{ "hello world" | replace(''world'',''daxin'') | upper }}
# HELLO DAXIN
 
{{ 18.18 | round | int }}
# 18

jinja2 的控制结构

jinja2 中的 if 语句类似与 Python 的 if 语句,它也具有单分支,多分支等多种结构,不同的是,条件语句不需要使用冒号结尾,而结束控制语句,需要使用 endif 关键字

{% if daxin.safe %}
daxin is safe.
{% elif daxin.dead %}
daxin is dead
{% else %}
daxin is okay
{% endif %}

jinja2 的 for 循环

jinja2 中的 for 循环用于迭代 Python 的数据类型,包括列表,元组和字典。在 jinja2 中不存在 while 循环。

迭代列表

<ul>
{% for user in users %}
<li>{{ user.username|title }}</li>
{% endfor %}
</ul>

迭代字典

<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key }}</dt>
<dd>{{ value}}</dd>
{% endfor %}
</dl>

当然也可以加入 else 语句,在循环正确执行完毕后,执行

在 for 循环中,jinja2 还提供了一些特殊的变量,用以来获取当前的遍历状态:

变量 描述 loop.index 当前迭代的索引(从 1 开始) loop.index0 当前迭代的索引(从 0 开始) loop.first 是否是第一次迭代,返回 bool loop.last 是否是最后一次迭代,返回 bool loop.length 序列中的项目数量 loop.revindex 到循环结束的次数(从 1 开始) loop.revindex0 到循环结束的次数 (从 0 开始)

jinja2 的宏

宏类似于 Python 中的函数,我们在宏中定义行为,还可以进行传递参数,就像 Python 中的函数一样一样儿的。

在宏中定义一个宏的关键字是 macro,后面跟其 宏的名称和参数等

{% macro input(name,age=18) %}   # 参数age的默认值为18
 
 <input type=''text'' name="{{ name }}" value="{{ age }}" >
 
{% endmacro %}

调用方法也和 Python 的类似

<p>{{ input(''daxin'') }} </p>
<p>{{ input(''daxin'',age=20) }} </p>

jinja2 的继承和 Super 函数

jinja2 中最强大的部分就是模板继承。模板继承允许我们创建一个基本 (骨架) 文件,其他文件从该骨架文件继承,然后针对自己需要的地方进行修改。

jinja2 的骨架文件中,利用 block 关键字表示其包涵的内容可以进行修改。

以下面的骨架文件 base.html 为例

<!DOCTYPE html>
<html lang="en">
<head>
    {% block head %}
    <link rel="stylesheet" href="style.css"/>
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
    {% block  footer %}
    <script>This is javascript code </script>
    {% endblock %}
</div>
</body>
</html>

这里定义了四处 block,即:head,title,content,footer。那怎么进行继承和变量替换呢?注意看下面的文件

{% extend "base.html" %}       # 继承base.html文件
 
{% block title %} Dachenzi {% endblock %}   # 定制title部分的内容
 
{% block head %}
    {{  super()  }}        # 用于获取原有的信息
    <style type=''text/css''>
    .important { color: #FFFFFF }
    </style>
{% endblock %}   
 
# 其他不修改的原封不同的继承

PS: super () 函数 表示获取 block 块中定义的原来的内容

利用 jinja2 进行渲染

jinja2 模块中有一个名为 Enviroment 的类,这个类的实例用于存储配置和全局对象,然后从文件系统或其他位置中加载模板。

基本使用方法

大多数应用都在初始化的时候撞见一个 Environment 对象,并用它加载模板。Environment 支持两种加载方式:

    • PackageLoader:包加载器
    • FileSystemLoader:文件系统加载器

PackageLoader

使用包加载器来加载文档的最简单的方式如下

from jinja2 import PackageLoader,Environment
env = Environment(loader=PackageLoader(''python_project'',''templates''))    # 创建一个包加载器对象
 
template = env.get_template(''bast.html'')    # 获取一个模板文件
template.render(name=''daxin'',age=18)   # 渲染

其中:

      • PackageLoader () 的两个参数为:python 包的名称,以及模板目录名称。
      • get_template ():获取模板目录下的某个具体文件。
      • render ():接受变量,对模板进行渲染

FileSystemLoader

文件系统加载器,不需要模板文件存在某个 Python 包下,可以直接访问系统中的文件。

 

Android 自定义 View 详解 Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解

Android 自定义 View 详解 Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解

View 的绘制系列文章:

  • Android View 绘制流程之 DecorView 与 ViewRootImpl

  • Android View 的绘制流程之 Measure 过程详解 (一)

  • Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

  • Android View 的事件分发原理解析

  • Android 自定义 View 详解

对于 Android 开发者来说,原生控件往往无法满足要求,需要开发者自定义一些控件,因此,需要去了解自定义 view 的实现原理。这样即使碰到需要自定义控件的时候,也可以游刃有余。

基础知识

自定义 View 分类

自定义 View 的实现方式有以下几种:

类型 定义 自定义组合控件 多个控件组合成为一个新的控件,方便多处复用 继承系统 View 控件 继承自TextView等系统控件,在系统控件的基础功能上进行扩展 继承 View 不复用系统控件逻辑,继承View进行功能定义 继承系统 ViewGroup 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展 继承 View ViewGroup 不复用系统控件逻辑,继承ViewGroup进行功能定义

从上到下越来越难,需要的了解的知识也是越来越多的。

构造函数

当我们在自定义 View 的时候,构造函数都是不可缺少,需要对构造函数进行重写,构造函数有多个,至少要重写其中一个才行。例如我们新建 MyTextView:

   
public class MyTextView extends View {
  /** * 在java代码里new的时候会用到 * @param context */ public MyTextView(Context context) { super(context); } /** * 在xml布局文件中使用时自动调用 * MyTextView(Context context,@Nullable AttributeSet attrs) { (context,attrs); } * 不会自动调用,如果有默认style时,在第二个构造函数中调用 * context * attrs * defStyleAttr public MyTextView(Context context,@Nullable AttributeSet attrs,int defStyleAttr) { * 只有在API版本>21时才会用到 * 不会自动调用,如果有默认style时,在第二个构造函数中调用 * defStyleAttr * defStyleRes */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) int defStyleAttr,1)"> defStyleRes) { 函数的作用,都已经再代码里面写出来了。

自定义属性

写过布局的同学都知道,系统控件的属性在 xml 中都是以 android 开头的。对于自定义 View,也可以自定义属性,在 xml 中使用。

Android 自定义属性可分为以下几步:

  1. 自定义一个 View

  2. 编写 values/attrs.xml,在其中编写 styleable 和 item 等标签元素

  3. 在布局文件中 View 使用自定义的属性(注意 namespace)

  4. 在 View 的构造方法中通过 TypedArray 获取

e.g  还是以上面的 MyTextView 做演示:

首先我在 activity_main.xml 中引入了 MyTextView:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height
    tools:context=".MainActivity">

    com.example.myapplication.MyTextView
        android:layout_width="100dp"
        android:layout_height="200dp"
        app:testAttr="520"
        app:text="helloWorld" />

</android.support.constraint.ConstraintLayout>

然后我在 values/attrs.xml 中添加自定义属性:

resources>
    declare-styleable name="test">
        attr ="text" format="string" />
        ="testAttr"="integer" />
    declare-styleable>
>

记得在构造函数里面说过,xml 布局会调用第二个构造函数,因此在这个构造函数里面获取属性和解析:

    context.obtainStyledAttributes(attrs,R.styleable.test);
        int textAttr = ta.getInteger(R.styleable.test_testAttr,-1);
        String text = ta.getString(R.styleable.test_text);
        Log.d(TAG," text = " + text + ",textAttr = " + textAttr);
     // toast 显示获取的属性值 Toast.makeText(context,text
+ " " + textAttr,Toast.LENGTH_LONG).show(); ta.recycle(); }

注意当你在引用自定义属性的时候,记得加上 name 前缀,否则会引用不到。

这里本想截图 log 的,奈何就是不显示,就搞成 toast 了。

当然,你还可以自定义很多其他属性,包括 color,string, integer,boolean,flag,甚至是混合等。

自定义组合控件

自定义组合控件就是将多个控件组合成为一个新的控件,主要解决多次重复使用同一类型的布局。如我们顶部的 HeaderView 以及 dailog 等,我们都可以把他们组合成一个新的控件。

我们通过一个自定义 MyView1 实例来了解自定义组合控件的用法。

xml 布局 

merge ="wrap_content"="wrap_content">
    
    TextView
        android:id="@+id/Feed_item_com_cont_title"
        android:layout_width
        android:ellipsize="end"
        android:includeFontPadding="false"
        android:maxLines="2"
        android:text="title" />

    ="@+id/Feed_item_com_cont_desc"
        android:layout_below="@id/Feed_item_com_cont_title"="desc" merge>

 自定义 View 代码 :

package com.example.myapplication;

import android.content.Context;
 android.util.AttributeSet;
 android.view.LayoutInflater;
 android.view.View;
 android.widget.RelativeLayout;
 android.widget.TextView;

class MyView1 extends RelativeLayout {

     标题 private TextView mTitle;
     描述  TextView mDesc;

     MyView1(Context context) {
        this(context,1)">null);
    }

     MyView1(Context context,AttributeSet attrs) {
        public MyView1(Context context,AttributeSet attrs,defStyleAttr);
        initView(context);
    }

    
     * 初使化界面视图
     *
     *  context 上下文环境
     protected void initView(Context context) {
        View rootView = LayoutInflater.from(getContext()).inflate(R.layout.my_view1,1)">this);

        mDesc = rootView.findViewById(R.id.Feed_item_com_cont_desc);
        mTitle = rootView.findViewById(R.id.Feed_item_com_cont_title);
    }
}

在布局当中引用该控件 

LinearLayout 
    android:orientation="vertical"="@+id/text"
        android:clickable="true"
        android:enabled
        android:focusable="trsfnjsfksjfnjsdfjksdhfjksdjkfhdsfsdddddddddddddddddddddddddd" ="@+id/myview"com.example.myapplication.MyView1
        ="wrap_content" LinearLayout>

最终效果如下图所示 :

 

继承系统控件

继承系统的控件可以分为继承 View子类(如 TextView 等)和继承 ViewGroup 子类(如 LinearLayout 等),根据业务需求的不同,实现的方式也会有比较大的差异。这里介绍一个比较简单的,继承自View的实现方式。

业务需求:为文字设置背景,并在布局中间添加一条横线。

因为这种实现方式会复用系统的逻辑,大多数情况下我们希望复用系统的 onMeaseur 和 onLayout 流程,所以我们只需要重写 onDraw 方法 。实现非常简单,话不多说,直接上代码。

 android.graphics.Canvas;
 android.graphics.LinearGradient;
 android.graphics.Shader;
 android.text.TextPaint;
 android.widget.TextView;


import static android.support.v4.content.ContextCompat.getColor;


 * 包含分割线的textView
 * 文字左右两边有一条渐变的分割线
 * 样式如下:
 * ———————— 文字 ————————
 */
class DividingLineTextView  TextView {
     线性渐变  LinearGradient mLinearGradient;
     textPaint  TextPaint mPaint;
     文字 private String mText = "";
     屏幕宽度 private  mScreenWidth;
     开始颜色  mStartColor;
     结束颜色  mEndColor;
     字体大小  mTextSize;


    
     * 构造函数
     public DividingLineTextView(Context context,1)"> defStyle) {
         getResources().getDimensionPixelSize(R.dimen.text_size);
        mScreenWidth = getCalculateWidth(getContext());
        mStartColor = getColor(getContext(),R.color.colorAccent);
        mEndColor =new LinearGradient(0,mScreenWidth,1)">,new []{mStartColor,mEndColor,mStartColor},1)">float[]{0,0.5fnew TextPaint();
    }

     DividingLineTextView(Context context,1)"> DividingLineTextView(Context context) {
        );
    }

    @Override
     onDraw(Canvas canvas) {
        .onDraw(canvas);
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(mTextSize);
        int len = getTextLength(mText,mPaint);
        // 文字绘制起始坐标
        int sx = mScreenWidth / 2 - len / 2;
         文字绘制结束坐标
        int ex = mScreenWidth / 2 + len / 2int height = getMeasuredHeight();
        mPaint.setShader(mLinearGradient);
         绘制左边分界线,从左边开始:左边距15dp, 右边距距离文字15dp
        canvas.drawLine(mTextSize,height / 2,sx - mTextSize,height / 2 绘制右边分界线,从文字右边开始:左边距距离文字15dp,右边距15dp
        canvas.drawLine(ex + mTextSize,mScreenWidth - mTextSize,mPaint);
    }

    
     * 返回指定文字的宽度,单位px
     *
     *  str   要测量的文字
     *  paint 绘制此文字的画笔
     * @return 返回文字的宽度,单位px
      getTextLength(String str,TextPaint paint) {
        return () paint.measureText(str);
    }

    
     * 更新文字
     *
     *  text 文字
      update(String text) {
        mText = text;
        setText(mText);
         刷新重绘
        requestLayout();
    }


    
     * 获取需要计算的宽度,取屏幕高宽较小值,
     *
     *  context context
     *  屏幕宽度值
     static  getCalculateWidth(Context context) {
         context.getResources().getdisplayMetrics().heightPixels;
         动态屏幕宽度,在折叠屏手机上宽度在分屏时会发生变化
        int Width = context.getResources().getdisplayMetrics().widthPixels;

        return Math.min(Width,height);
    }
}

对于 View 的绘制还需要对 Paint()canvas 以及 Path 的使用有所了解,不清楚的可以稍微了解一下。 

看下布局里面的引用:

xml 布局 

>

   // ...... 跟前面一样忽视
    com.example.myapplication.DividingLineTextView
        ="@+id/divide"
        android:gravity="center" >

 

activty 里面代码如下 :
   onCreate(Bundle savedInstanceState) {
        .onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DividingLineTextView te = findViewById(R.id.divide);
        te.update("DividingLineTextView");
  }

这里通过 update() 对来重新绘制,确保边线在文字的两边。视觉效果如下:

 

直接继承View

直接继承 View 会比上一种实现方复杂一些,这种方法的使用情景下,完全不需要复用系统控件的逻辑,除了要重写 onDraw 外还需要对 onMeasure 方法进行重写。

我们用自定义 View 来绘制一个正方形。

首先定义构造方法,以及做一些初始化操作

ublic class RectView  View{
    定义画笔
    private Paint mPaint =  Paint();

    
     * 实现构造方法
     *  RectView(Context context) {
        (context);
        init();
    }

     RectView(Context context,attrs);
        init();
    }

    public RectView(Context context,defStyleAttr);
        init();
    }

     init() {
        mPaint.setColor(Color.BLUE);

    }

}

 重写 draw 方法,绘制正方形,注意对 padding 属性进行设置:


     * 重写draw方法
     *  canvas
     
    @Override
    .onDraw(canvas);
        获取各个编剧的padding值
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        获取绘制的View的宽度
        int width = getWidth()-paddingLeft-paddingRight;
        获取绘制的View的高度
        int height = getHeight()-paddingTop-paddingBottom;
        绘制View,左上角坐标(0+paddingLeft,0+paddingTop),右下角坐标(width+paddingLeft,height+paddingTop)
        canvas.drawRect(0+paddingLeft,0+paddingTop,width+paddingLeft,height+paddingTop,mPaint);
    }

在 View 的源码当中并没有对 AT_MOST 和 EXACTLY 两个模式做出区分,也就是说 View 在 wrap_content 和 match_parent 两个模式下是完全相同的,都会是 match_parent,显然这与我们平时用的 View 不同,所以我们要重写 onMeasure 方法。

    
     * 重写onMeasure方法
     *
     *  widthMeasureSpec
     *  heightMeasureSpec
     void onMeasure(int widthMeasureSpec,1)"> heightMeasureSpec) {
        .onMeasure(widthMeasureSpec,heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        处理wrap_contentde情况
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300,300);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(300if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSize,300);
        }
    }

 最终效果如图所示:

可以发现,我们设置的是 wrap_content,但是最后还是有尺寸的。

整个过程大致如下,直接继承 View 时需要有几点注意:

  1. 在 onDraw 当中对 padding 属性进行处理。

  2. 在 onMeasure 过程中对 wrap_content 属性进行处理。

  3. 至少要有一个构造方法。

继承ViewGroup

自定义 ViewGroup 的过程相对复杂一些,因为除了要对自身的大小和位置进行测量之外,还需要对子 View 的测量参数负责。

需求实例

实现一个类似于 Viewpager 的可左右滑动的布局。

布局文件:

com.example.myapplication.MyHorizonView
        
        android:background="@color/colorAccent"="400dp">

        ListView
            ="@+id/list1"
            android:layout_width
            android:layout_height
            android:background="@color/colorAccent" />

        ="@+id/list2"="@color/colorPrimary" ="@+id/list3"="@color/colorPrimaryDark" com.example.myapplication.MyHorizonView="1dp"="2dp"com.example.myapplication.RectView
        />


>

一个 ViewGroup 里面放入 3 个 ListView,注意 ViewGroup 设置的宽是 wrap_conten,在测量的时候,会对 wrap_content 设置成与父 View 的大小一致,具体实现逻辑可看后面的代码。

代码比较多,我们结合注释分析。

class MyHorizonView  ViewGroup {

    final String TAG = "HorizontaiView"private List<View> mMatchedChildrenList = new ArrayList<>();


     MyHorizonView(Context context) {
         MyHorizonView(Context context,AttributeSet attributes) {
        public MyHorizonView(Context context,AttributeSet attributes,attributes,defStyleAttr);
    }

    @Override
    void onLayout(boolean changed,1)">int l,1)">int t,1)">int r,1)"> b) {
        int childCount = getChildCount();
        int left = 0;
        View child;
        for (int i = 0; i < childCount; i++) {
            child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                int childWidth = child.getMeasuredWidth();
                 因为是水平滑动的,所以以宽度来适配
                child.layout(left,left + childWidth,child.getMeasuredHeight());
                left += childWidth;
            }
        }
    }

    @Override
    int widthSpecMode =int widthSpecsize =int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecsize = 如果不是确定的的值,说明是 AT_MOST,与父 View 同宽高
        final boolean measureMatchParentChildren = heightSpecMode != MeasureSpec.EXACTLY ||
                widthSpecMode != MeasureSpec.EXACTLY;
         getChildCount();
        View child;
        final LayoutParams layoutParams = child.getLayoutParams();
                measureChild(child,widthMeasureSpec,heightMeasureSpec);
                if (measureMatchParentChildren) {
                     需要先计算出父 View 的高度来再来测量子 view
                    if (layoutParams.width == LayoutParams.MATCH_PARENT
                            || layoutParams.height == LayoutParams.MATCH_PARENT) {
                        mMatchedChildrenList.add(child);
                    }
                }
            }
        }

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
             如果宽高都是AT_MOST的话,即都是wrap_content布局模式,就用View自己想要的宽高值
            setMeasuredDimension(getMeasuredWidth(),getMeasuredHeight());
        } if (widthSpecMode == 如果只有宽度都是AT_MOST的话,即只有宽度是wrap_content布局模式,宽度就用View自己想要的宽度值,高度就用父ViewGroup指定的高度值
pecsize);
        } if (heightSpecMode == 如果只有高度都是AT_MOST的话,即只有高度是wrap_content布局模式,高度就用View自己想要的宽度值,宽度就用父ViewGroup指定的高度值
            setMeasuredDimension(widthSpecsize,getMeasuredHeight());
        }

        int i = 0; i < mMatchedChildrenList.size(); i++) {
            View matchChild =if (matchChild.getVisibility() != matchChild.getLayoutParams();
                 计算子 View 宽的 MeasureSpec
                 childWidthMeasureSpec;
                 LayoutParams.MATCH_PARENT) {
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,layoutParams.width);
                }
                 计算子 View 高的 MeasureSpec
                 childHeightMeasureSpec;
                if (layoutParams.height == LayoutParams.MATCH_PARENT) {
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(),1)"> {
                    childHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec,layoutParams.height);
                }
                 根据 MeasureSpec 计算自己的宽高
                matchChild.measure(childWidthMeasureSpec,childHeightMeasureSpec);
            }
        }
    }
}

这里我们只是重写了两个绘制过程中的重要的方法:onMeasure 和 onLayout 方法。

对于 onMeasure 方法具体逻辑如下:

  1. super.onMeasure 会先计算自定义 view 的大小;

  2. 调用 measureChild 对 子 View 进行测量;
  3. 自定义 view 设置的宽高参数不是 MeasureSpec.EXACTLY 的话,对于子 View 是 match_parent 需要额外处理,同时也需要对 MeasureSpec.AT_MOST 情况进行额外处理。

  4.  当自定义view 的大小确定后,在对子 View 是 match_parent 重新测量;

上述的测量过程的代码也是参考 FrameLayout 源码的,具体可以参看文章:

对于 onLayout 方法,因为是水平滑动的,所以要根据宽度来进行layout。

到这里我们的 View 布局就已经基本结束了。但是要实现 Viewpager 的效果,还需要添加对事件的处理。事件的处理流程之前我们有分析过,在制作自定义 View 的时候也是会经常用到的,不了解的可以参考文章 Android Touch事件分发超详细解析。

  init(Context context) {
        mScroller =  Scroller(context);
        mTracker = VeLocityTracker.obtain();
    }

    
     * 因为我们定义的是ViewGroup,从onInterceptTouchEvent开始。
     * 重写onInterceptTouchEvent,对横向滑动事件进行拦截
     *
     *  event
     * @return
     boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = falseint x = () event.getX();
        int y = () event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;必须不能拦截,否则后续的ACTION_MOME和ACTION_UP事件都会拦截。
                break;
             MotionEvent.ACTION_MOVE:
                intercepted = Math.abs(x - mLastX) > Math.abs(y - mLastY);
                ;
        }
        Log.d(TAG,"onInterceptTouchEvent: intercepted " + intercepted);
        mLastX = x;
        mLastY = y;
        return intercepted ? intercepted : .onInterceptHoverEvent(event);
    }

    
     * 当ViewGroup拦截下用户的横向滑动事件以后,后续的Touch事件将交付给`onTouchEvent`进行处理。
     * 重写onTouchEvent方法
      onTouchEvent(MotionEvent event) {
        mTracker.addMovement(event);
         MotionEvent.ACTION_DOWN:
                 MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                Log.d(TAG,"onTouchEvent: deltaX " + deltaX);

                 scrollBy 方法将对我们当前 View 的位置进行偏移
                scrollBy(-deltaX,1)">);
                 MotionEvent.ACTION_UP:
                Log.d(TAG,"onTouchEvent: " + getScrollX());
                 getScrollX()为在X轴方向发生的便宜,mChildWidth * currentIndex表示当前View在滑动开始之前的X坐标
                 distance存储的就是此次滑动的距离
                int distance = getScrollX() - mChildWidth * mCurrentIndex;
                当本次滑动距离>View宽度的1/2时,切换View
                if (Math.abs(distance) > mChildWidth / 2) {
                    if (distance > 0) {
                        mCurrentIndex++;
                    }  {
                        mCurrentIndex--;
                    }
                }  {
                    获取X轴加速度,units为单位,默认为像素,这里为每秒1000个像素点
                    mTracker.computeCurrentVeLocity(1000);
                    float xV = mTracker.getXVeLocity();
                    当X轴加速度>50时,也就是产生了快速滑动,也会切换View
                    if (Math.abs(xV) > 50) {
                        if (xV < 0) {
                            mCurrentIndex++;
                        }  {
                            mCurrentIndex--;
                        }
                    }
                }

                对currentIndex做出限制其范围为【0,getChildCount() - 1】
                mCurrentIndex = mCurrentIndex < 0 ? 0 : mCurrentIndex > getChildCount() - 1 ? getChildCount() - 1 : mCurrentIndex;
                滑动到下一个View
                smoothScrollTo(mCurrentIndex * mChildWidth,1)">);
                mTracker.clear();

                ;
        }

        Log.d(TAG,"onTouchEvent: ");
        mLastX =return .onTouchEvent(event);
    }

    @Override
     dispatchTouchEvent(MotionEvent ev) {
        .dispatchTouchEvent(ev);
    }

    void smoothScrollTo(int destX,1)"> destY) {
         startScroll方法将产生一系列偏移量,从(getScrollX(),getScrollY()),destX - getScrollX()和destY - getScrollY()为移动的距离
        mScroller.startScroll(getScrollX(),getScrollY(),destX - getScrollX(),destY - getScrollY(),1000);
         invalidate方法会重绘View,也就是调用View的onDraw方法,而onDraw又会调用computeScroll()方法
        invalidate();
    }

     重写computeScroll方法
    @Override
     computeScroll() {
        .computeScroll();
         当scroller.computeScrollOffset()=true时表示滑动没有结束
         (mScroller.computeScrollOffset()) {
             调用scrollTo方法进行滑动,滑动到scroller当中计算到的滑动位置
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
             没有滑动结束,继续刷新View
            postInvalidate();
        }
    }

具体效果如下图所示:


对于 Scroller 的用法总结如下:

  1. 调用 Scroller 的 startScroll() 方法来进行一些滚动的初始化设置,然后迫使 View 进行绘制 (调用 View 的 invalidate() 或 postInvalidate() 就可以重新绘制 View);

  2. 绘制 View 的时候 drawchild 方法会调用 computeScroll() 方法,重写 computeScroll(),通过 Scroller 的 computeScrollOffset() 方法来判断滚动有没有结束;

  3. scrollTo() 方法虽然会重新绘制 View,但还是要调用下 invalidate() 或者 postInvalidate() 来触发界面重绘,重新绘制 View 又触发 computeScroll();

  4. 如此往复进入一个循环阶段,即可达到平滑滚动的效果;

也许有人会问,干嘛还要调用来调用去最后在调用 scrollTo() 方法,还不如直接调用 scrollTo() 方法来实现滚动,其实直接调用是可以,只不过 scrollTo() 是瞬间滚动的,给人的用户体验不太好,所以 Android 提供了 Scroller 类实现平滑滚动的效果。

为了方面大家理解,我画了一个简单的调用示意图:

 

 

到此,自定义 view 的方法就讲完了。希望对大家有用。

参考文献:

1、Android自定义View全解

总结

以上是小编为你收集整理的Android 自定义 View 详解 Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解全部内容。

如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。

Azure CDN 是否有助于提高 Microsoft bot 框架与 Twilio Adapter 一起工作的性能

Azure CDN 是否有助于提高 Microsoft bot 框架与 Twilio Adapter 一起工作的性能

如何解决Azure CDN 是否有助于提高 Microsoft bot 框架与 Twilio Adapter 一起工作的性能

我有一个使用 Microsoft-bot-framework 开发的机器人,对于 Whatsapp,我使用的是 Twilio-Adapter。 因此,如果我为我的 BotService-WebApp 创建一个 CDN,那么对于性能调整,这会有用吗,

CDN 通过在不同的 POP 中创建缓存并在那里存储静态和内容文件来工作 因此,当最终用户尝试从不同位置访问网站时,内容将被缓存在最近的区域并将响应发送给最终用户。但是在 Bot 的情况下,最终用户在 Whatsapp 中发送消息并使用 twilio webhook botframework 将被调用。所以最终用户不是直接访问 Bot,它是通过 Twilio 连接的。将 CDN 添加到我的 BotService 是否可行?

解决方法

在我看来,CDN 将适用于您的情况。因为无论如何所有的服务都来自服务提供商,所以为 bot 服务创建 CDN 将有利于那些与服务提供商联系的人。并且您需要知道 CDN 还可以帮助用户请求选择一个更好的点(不仅是最近的,而且是免费的井点)来连接到服务,例如一个端点突然出错或繁忙。 Azure CDN 将提供一个新的 url 来替换前一个,这就是它起作用的地方。

我认为任何假设都小于实验结果,您可以先尝试使用CDN来测试是否有效。 Azure CDN 是按使用量付费的,支付的越多说明它越有用。

Azure 函数应用:Flask 框架与 Azure 函数的集成

Azure 函数应用:Flask 框架与 Azure 函数的集成

如何解决Azure 函数应用:Flask 框架与 Azure 函数的集成

我有一个 azure 函数,它将使用来自事件中心的数据并将相同的数据重定向到 REST API。 我已经编写了一组代码,它将事件中心 输出重定向到 API(测试挂起),但现在我想将 Flask 框架与我现有的代码集成,但我不知道我应该对以下代码进行哪些更改,以便我可以使用

======================================================================================================
__init__.py
======================================================================================================
from PWO_EventHubTrigger.postCall import GetPost
from typing import List
import logging
import os
import azure.functions as func
from azure.storage.blob import BlobClient
import datetime
import json


storage_connection_string = os.getenv(''eh_rec_storage_connection_string_Fromkeyvault'')
logging.info(''storage_connection_string: %s'',storage_connection_string)

container_name = os.getenv(''eh_rec_storage_container_name_Fromkeyvault'')
logging.info(''container_name: %s'',container_name)

today = datetime.datetime.today()


def main(events: List[func.EventHubEvent]):
    logging.info(''-----------main function starts-----------'')
    try:
        for event in events:
            data = event.get_body().decode(''utf-8'')
            json.loads(data)
            logging.info(''Python EventHub trigger processed an event: %s'',data)
            logging.info(f''  SequenceNumber = {event.sequence_number}'')
            logging.info(f''  Offset = {event.offset}'')
            
            try:
                logging.info("Calling GetPost method from main")
                res = GetPost(data)
                logging.info("GetPost response recived: ",res)
            except Exception as Argument: 
                logging.exception("Error occurred during the GetPost method")
            
    except Exception as Argument: 
        logging.exception("Error occured in main") 

======================================================================================================
postcall.py
======================================================================================================
import requests
import logging
 
# data={''number'': 12524,''type'': ''issue'',''action'': ''show''}
# data = {"Source": "localhost","Timestamp": "2021-05-14 14:30:00","Tags": [{"TagName": "testtag1","TagValue": "10.52","TagQuality": 192}]}
def GetPost(data):
    logging.info(''-----------GetPost function starts-----------'')
    try: 
        headers = {
            ''user-agent'': ''customize header string'',''Content-Type'': ''application/json; charset=utf-8''
            }  
        response = requests.post(''https://10.199.215.156:443/RestEndpoint'',data= data,headers=headers,timeout=3) 
        # response = requests.post(''http://bugs.python.org'',timeout=3)
        print("Http response code:",response.status_code) 
        # print("Http response text:",response.text) 
        response.raise_for_status()  # Raise error in case of failure 
    except requests.exceptions.HTTPError as httpErr: 
        logging.info(''Http Error:'',httpErr)
    except requests.exceptions.ConnectionError as connErr: 
        logging.info(''Error Connecting:'',connErr)
    except requests.exceptions.Timeout as timeOutErr: 
        logging.info(''Timeout Error:'',timeOutErr)
    except requests.exceptions.RequestException as reqErr:  
        logging.info(''Something Else:'',reqErr)
    except Exception as err:
        logging.info(''Something Else:'',err)
======================================================================================================
function.json --> Azure configuration file
======================================================================================================
{
  "scriptFile": "__init__.py","bindings": [
    {
      "type": "eventHubTrigger","name": "events","direction": "in","eventHubName": "my-events","connection": "EventHubReceiverPolicy_Fromkeyvault","cardinality": "many","consumerGroup": "$Default","dataType": "binary"
    }
  ]
}

======================================================================================================
local.settings.json --> Azure configuration file
======================================================================================================
{
  "IsEncrypted": false,"Values": {
    "AzureWebJobsstorage": "<Endpoint1>","FUNCTIONS_WORKER_RUNTIME": "python","storage_connection_string_Fromkeyvault": "<connectionString","storage_container_name_Fromkeyvault": "<container_name>","EventHubReceiverPolicy_Fromkeyvault": "<Endpoint2>"
  }
}

我是 Python 开发的新手。如果您认为我的问题很愚蠢,我深表歉意。非常感谢!!

C++ 框架与 Java 框架的对比分析

C++ 框架与 Java 框架的对比分析

c++++ 框架以其性能、资源效率和系统访问能力著称,但学习曲线陡峭,维护复杂,跨平台性差。java 框架专注于可移植性、安全性和大规模开发,语法简洁,开发便捷,但性能开销较高,内存消耗较大,底层控制有限。实战案例表明,对于图像处理等需要高性能的应用程序,c++ 框架更合适;对于电子商务等跨平台部署和安全至上的应用程序,java 框架更合适。

C++ 框架与 Java 框架:深入对比分析

引言

在现代软件开发中,框架发挥着至关重要的作用,它们提供了可重用的组件和库,简化了代码编写和维护。C++ 和 Java 都是流行的编程语言,各自拥有广泛的框架生态系统。本文将深入比较 C++ 和 Java 框架,重点关注其优点、缺点以及实战案例。

立即学习“Java免费学习笔记(深入)”;

C++ 框架

C++ 框架因其性能、资源效率和对底层系统硬件的直接访问而闻名。常见框架包括:

  • Boost: 一个大型、多用途 C++ 库,提供各种功能,例如算法、数据结构和并发。
  • Qt: 一个跨平台应用程序框架,易于创建图形用户界面 (GUI) 和桌面应用程序。
  • Eigen: 一个模板元编程库,专门用于数学和科学计算。

优点:

  • 性能和效率: C++ 代码以高性能原生代码编译,速度和资源使用上具有优势。
  • 灵活性: C++ 语言和大多数框架允许非常细粒度的控制,让开发人员可以根据需要定制代码。
  • 系统访问: C++ 框架可以与底层系统硬件和操作系统直接交互,在需要精确控制的情况下非常有用。

缺点:

  • 学习曲线陡峭: C++ 是一种复杂且面向对象的语言,对于初学者来说可能具有挑战性。
  • 代码维护复杂: 由于 C++ 的指针和内存管理,维护 C++ 代码可能很困难。
  • 与跨平台不佳: C++ 应用程序通常紧密绑定到特定的操作系统和硬件,导致跨平台部署困难。

Java 框架

Java 框架专注于可移植性、安全性和大规模开发。流行框架包括:

  • Spring: 一个全面的 Web 应用程序框架,提供模块化架构、依赖关系注入和数据库集成。
  • Hibernate: 一个对象-关系映射 (ORM) 框架,使 Java 对象易于与关系型数据库交互。
  • Apache Maven: 一个构建自动化工具,简化项目管理、依赖关系管理和发布流程。

优点:

  • 跨平台: Java 应用程序可以在任何支持 Java 虚拟机 (JVM) 的平台上运行,实现真正的跨平台部署。
  • 安全性: Java 框架和 JVM 本身内置了广泛的安全功能,有助于保护应用程序免受攻击。
  • 便利性: Java 具有简洁的语法和广泛的库,使开发和维护更容易。

缺点:

  • 性能开销: Java 代码通常比原生 C++ 代码运行得慢一点,因为它是解释执行的。
  • 内存消耗: Java 应用程序可能比 C++ 应用程序占用更多的内存,因为它们必须将所有对象存储在受 JVM 管理的堆中。
  • 有限的底层控制: Java 框架通常抽象了底层硬件和操作系统,这可能会限制对某些系统功能的访问。

实战案例

示例 1:

  • 任务:开发一个高性能图像处理应用程序。
  • 语言:C++
  • 框架:Eigen

示例 2:

  • 任务:开发一个基于 Web 的电子商务应用程序。
  • 语言:Java
  • 框架:Spring

结论

C++ 和 Java 框架都在各自的领域具有优势和劣势。对于需要性能、效率和底层控制的应用程序,C++ 框架可能是更好的选择。对于可移植性、安全性和大规模开发至关重要的应用程序,Java 框架通常是首选。最终,选择合适的框架取决于应用程序的具体要求。

以上就是C++ 框架与 Java 框架的对比分析的详细内容,更多请关注php中文网其它相关文章!

关于自定义 Web 框架与 jinja2 模板web平台自定义设置的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于Android 自定义 View 详解 Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解、Azure CDN 是否有助于提高 Microsoft bot 框架与 Twilio Adapter 一起工作的性能、Azure 函数应用:Flask 框架与 Azure 函数的集成、C++ 框架与 Java 框架的对比分析的相关知识,请在本站寻找。

本文标签: