Android HAL到Framework

一、为什么需要Framwork?

Framework实际上是⼀个应⽤程序的框架,提供了很多服务:

1、丰富⽽⼜可扩展的视图(Views),

可以⽤来构建应⽤程序,它包括列表(lists),⽹格(grids),⽂本框(text boxes),按钮(buttons),甚⾄可嵌⼊的web浏览器。

2、内容提供器(Content Providers)

使得应⽤程序可以访问另⼀个应⽤程序的数据(如联系⼈数据库),或者共享它们⾃⼰的数据

3、资源管理器(Resource Manager)

提供⾮代码资源的访问,如本地字符串,图形,和布局⽂件(layout files)。

4、通知管理器(Notification Manager)

使得应⽤程序可以在状态栏中显⽰⾃定义的提⽰信息。

5、活动管理器(Activity Manager)

⽤来管理应⽤程序⽣命周期并提供常⽤的导航回退功能。

二、应用层访问硬件,如何自定义系统Service?

1、应用层如何访问硬件

(1)Linux

        对于Linux来说的话,就比较简单,应用层的APP直接通过open一类的接口直接访问我们底层的驱动文件

(2)Android 

        对于Android来说的话,它就会有多种方式去访问,

1) APP ----- JNI ----- Kernel:

        这种就很直接明了,上层app访问JNI,再去访问kernel

2)APP ----- Service ----- JNI ----- Kernel:

        当我们要往系统里添加一个硬件的话,我们更希望把它封装为一个系统的服务,就可以以这种方式去访问到底层

3)APP ----- Service ----- JNI ----- HAL ----- Kernel:

        一些驱动厂商的一个源码呢他是不希望开放给我们的一个开发者是吧,但是他们又依赖着Android的开源框架,所以就有一种比较好的方法,既不需要公开源码,又可以实现同样的功能。就是把它封装成库,这样可以让厂家去提供一个现成的库,然后我们直接去使用,他就不用开放这一层的源码,这就是HAL层的存在意义。

 为什么需要JNI? 

        应⽤使⽤java编写,驱动⼀般使⽤c/cpp编写,提供⼀种Java访问c/cpp的⽅法。也就是Java代码可通过JNI接⼝调⽤C/C++⽅法。

 JNI开发流程的步骤: 

1)编写JNI⽅法表并注册
2)实现JNI的.c⽂件

2、自定义系统Service 

        Framework还有一个很重要的功能,就是系统server。所有的硬件呢都是通过我们的系统server去进行管理,那我们怎样为我们的硬件接口去添加一个自定义的系统serve呢?

(1)建立aidl通信接口;

(2)在system_server中注册service到servicemanager;

(3)实现service,对应aidl中的接口函数。

(4)client向servicemanager请求service,成功后,调用aidl接口函数,建立client进程和service进程的通信关系。

总结来说就是:

1)system_server完成注册功能;
2)servicemanager完成服务管理功能;
3)aidl完成通讯功能;

(1)建立aidl通信接口

在frameworks/base/core/java/android/os/路径下新建对应名称的一个aidl文件

下面我们以顾凯歌的一个蓝牙模块的服务为大家举例:

路径:frameworks/base/core/java/android/os/IGocsdkService.aidl:
(因为他是Interface的一个接口,所有在前面加个 "I")+ package android.os;+ interface IEmbededService {
+   interface IFmService {
+
+       //蓝牙状态回调注册去注销 
+       void registerCallback(IGocsdkCallback callback);
+       // 注销蓝牙状态
+       void unregisterCallback(IGocsdkCallback callback);
+       
+       //注释后面带的为操作后相应的回调回复
+       //蓝牙协议软复位  ---》 onInitSucceed()
+       void restBluetooth();
+       
+       //获取本地蓝牙名称  ---》onCurrentDeviceName()
+       void getLocalName();
+       
+       //设置本地蓝牙名称  ---》onCurrentDeviceName()
+       void setLocalName(String name);+        ..................//等等一些,都为接口函数,会在下面实现}

编译到系统

路径:frameworks/base/Android.mk diff --git a/android/frameworks/base/Android.bp b/android/frameworks/base/Android.bp
old mode 100644
new mode 100755
index d8a7f06..953759c
--- a/android/frameworks/base/Android.bp
+++ b/android/frameworks/base/Android.bp
@@ -265,6 +265,11 @@ java_defaults {"core/java/android/os/IRecoverySystemProgressListener.aidl","core/java/android/os/IRemoteCallback.aidl","core/java/android/os/ISchedulingPolicyService.aidl",
+        "core/java/android/os/IGocsdkService.aidl",":statsd_aidl","core/java/android/os/ISystemUpdateManager.aidl","core/java/android/os/IThermalEventListener.aidl",

(2)在system_server中注册EmbededServicer到servicemanager

路径:frameworks/base/services/java/com/android/server/SystemServer.java

使用ServiceManager.addService添加我们自定义的server

@@ -1097,6 +1097,13 @@ public final class SystemServer {
}Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);+      try {+          Slog.i(TAG, "IGocsdkService");+           ServiceManager.addService("gocsdkService ", new GocsdkService());+   } catch (Throwable e) { +       Slog.e(TAG, "Failure starting Gocsdk Service", e);+   }

(3)实现EmbededService,对应aidl中的接⼝函数

路径:frameworks/base/services/java/com/android/server/EmbededService.java

package com.android.server;
import android.content.Context;
import android.os.IGocsdkService;
import android.util.Slog;
public class GocsdkService extends IGocsdkService.Stub {private static final String TAG = "GocsdkService";GocsdkService(){Slog.i(TAG,"GocsdkService init");}public void registerCallback(IGocsdkCallback callback){return xxx;}public void unregisterCallback(IGocsdkCallback callback){return xxx;}public void getLocalName(){return xxx;}.......................
}

(4)在app中使⽤IEmbededService的大致流程如下

        很好理解吧,把我们对应的一个服务导入,然后去初始化一个类,然后通过ServiceManager去找到我们自定义的这个server,然后使用自定义服务的函数获取数据。

import android.os.IGocsdkService; //导入private IGocsdkService mGocsdkService = null; //初始化类
mGocsdkService = IGocsdkService .Stub.asInterface(ServiceManager.getService("gocsdkService"));int version= mEmbededService.getLocalName();
String text = String.value(localName);

(5)编译service,烧录

直接全sdk编译,防止有遗漏

(6)验证

使⽤service list查看是否有EmbededService

xxx:/ $ service list | grep gocsdkServicegocsdkService: [android.os.IGocsdkService]

三、为什么需要Android HAL?

        Hardware Abstract Layer 硬件抽象层,由于Linux Kernel需要遵循GPL开源协议,硬件⼚商为了保护⾃⼰硬件⽅⾯的各项参数不被外泄,⽽⼀个设备的驱动程序包含了硬件的⼀些重要参数,所以驱动的开源势必会使硬件⼚商蒙受损失,Google为了保护硬件⼚商的利益,所以在Android系统中加⼊了HAL层,在HAL层中不必遵循GPL协议,所以代码可以封闭。
        所以如果硬件驱动开源的写在Kernel⾥,Framework直接调⽤,⽽不愿意开源的就写在HAL层⾥,实现闭源。也就是说,编写驱动分为两个部分,⼀个是HAL层的驱动代码,⼀个是Kernel层的驱动代码。


1、内核实现HAL驱动的⽅法有两种:

(1)采⽤直接调⽤so动态链接库⽅式

        采⽤共享库形式,在编译时会调⽤到。由于采⽤function call形式调⽤,因此可被多个进程使⽤,但会被mapping到多个进程空间中,造成浪费,同时需要考虑代码能否安全重⼊的问题。

(2)采⽤Stub代理⽅式调⽤

        采⽤HAL module和HAL stub结合形式,HAL stub不是⼀个share library,编译时
上层只拥有访问HAL stub的函数指针,并不需要HAL stub。上层通过HAL module提供的统⼀接⼝获取并操作HAL stub,so⽂件只会被mapping到⼀个进程,也不存在重复mapping和重⼊问题。

2、如何编写HAL层驱动

        我们现在一般都是采用第二种方式,基于HAL框架提供了三个结构体,分别为hw_device_t、hw_module_t、hw_module_methods_t,编写HAL层驱动则是依据这三个结构体作扩展,我们创建⾃⼰驱动的device_t,module_t代码,并且写hw_module_methods_t这个结构体中⽅法的实现代码,最后JNI层通过hw_get_module调⽤。

(1)在 android/hardware/libhardware/modules/xxx 路径下创建我们的HAL文件夹,例如LED:

mkdirhardware/libhardware/modules/ledpath:hardware/libhardware/include/hardware/led_hal.h
path:hardware/libhardware/modules/embeded/led_hal.c

(1)led_hal.c:

#define LOG_TAG "dLed"
#include <hardware/hardware.h>
#include <hardware/led_hal.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define DEVICE_NAME "sys/led/embeded_blue_led"
#define MODULE_NAME "EmLed"/*设备打开和关闭接⼝*/
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device);
static int embededled_device_close(struct hw_device_t* device);/*设备访问接⼝*/
static int embededled_set_val(struct embededled_device_t* dev, int val);
static int embededled_get_val(struct embededled_device_t* dev, int* val);
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device) {struct embededled_device_t* dev;dev = (structembededled_device_t*)malloc(sizeof(struct embededled_device_t));if(!dev) {ALOGI("embededled Stub: failed to alloc space");return -EFAULT;}memset(dev, 0, sizeof(struct embededled_device_t));//初始化设备相关信息,实现访问接⼝函数dev->common.tag = HARDWARE_DEVICE_TAG;dev->common.version = 0;dev->common.module = (hw_module_t*)module;dev->common.close = embededled_device_close;dev->set_val = embededled_set_val;dev->get_val = embededled_get_val;if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {ALOGI("embededled Stub: failed to opensys/embededled/embeded_blue_led -- %s.", strerror(errno));free(dev);return -EFAULT;}int status = 0;write(dev->fd, &status, sizeof(status));*device = &(dev->common);ALOGI("embededled Stub: open sys/embededled/embeded_blue_ledsuccessfully.");return 0;
}static int embededled_device_close(struct hw_device_t* device) {struct embededled_device_t* embededled_device = (structembededled_device_t*)device;if(embededled_device) {close(embededled_device->fd);free(embededled_device);}return 0;
}static int embededled_set_val(struct embededled_device_t* dev, int val) {ALOGI("embededled Stub: set value %d to device.", val);write(dev->fd, &val, sizeof(val));return 0;}static int embededled_get_val(struct embededled_device_t* dev, int* val) {
if(!val) {ALOGI("embededled Stub: error val pointer");return -EFAULT;
}read(dev->fd, val, sizeof(*val));ALOGI("embededled Stub: get value %d from device", *val);return 0;
}/*模块⽅法表*/
static struct hw_module_methods_t embededled_module_methods = {
open: embededled_device_open
};/*模块实例变量*/
struct embededled_module_t HAL_MODULE_INFO_SYM = {
common: {tag: HARDWARE_MODULE_TAG,version_major: 1,version_minor: 0,id: EMBEDEDLED_HARDWARE_MODULE_ID,name: MODULE_NAME,author: MODULE_AUTHOR,methods: &embededled_module_methods,
}
};

led_hal.h:
path:hardware/libhardware/include/hardware/led_hal.h

#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
/*定义模块ID*/
#define EMBEDEDLED_HARDWARE_MODULE_ID "led_hal"
/*硬件模块结构体*/
struct led_module_t {struct hw_module_t common;
};
/*硬件接⼝结构体*/
struct embededled_device_t {struct hw_device_t common;int fd;int (*set_val)(struct led_device_t* dev, int val);int (*get_val)(struct led_device_t* dev, int* val);
};
__END_DECLS
#endif

四、JNI层添加

JNI开发流程的步骤:

第1步:编写JNI⽅法表并注册
第2步:实现JNI的.c⽂件

里面呢就是我们要实现的三个函数,然后再把对应的方法注册到我们的server里面去

Android.mk
1 diff --git a/frameworks/base/services/core/jni/Android.mkb/frameworks/base/services/core/jni/Android.mk
2     index 0f0124bd46..305773298a 100644
3     --- a/frameworks/base/services/core/jni/Android.mk
4     +++ b/frameworks/base/services/core/jni/Android.mk
5     @@ -36,6 +36,7 @@ LOCAL_SRC_FILES += \
6     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
7     $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
8     $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
9     + $(LOCAL_REL_DIR)/com_android_server_EmbededLedService.cpp \
10     $(LOCAL_REL_DIR)/onload.cpp
11
12     LOCAL_SRC_FILES += \
把注册JNI⽅法函数添加到系统中
1 diff --git a/frameworks/base/services/core/jni/onload.cppb/frameworks/base/services/core/jni/onload.cpp
2     index d5861f8c41..b52f7917fd 100644
3     --- a/frameworks/base/services/core/jni/onload.cpp
4     +++ b/frameworks/base/services/core/jni/onload.cpp
5     @@ -47,6 +47,7 @@ intregister_android_server_PersistentDataBlockService(JNIEnv* env);
6     int register_android_server_Watchdog(JNIEnv* env);
7     int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
8     int register_com_android_server_rkdisplay_RkDisplayModes(JNIEnv* env);
9     +int register_android_server_EmbededLedService(JNIEnv* env);
10     };
11
12     using namespace android;
13     @@ -89,7 +90,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
14     register_android_server_Watchdog(env);
15     register_android_server_HardwarePropertiesManagerService(env);
16     register_com_android_server_rkdisplay_RkDisplayModes(env);
17     -
18     -
19     + register_android_server_EmbededLedService(env);
20     return JNI_VERSION_1_4;
21         }
然后按照同样的方法去建立AIDL&Service

1、AIDL:

1 package android.os;
2
3 interface IEmbededLedService {
4     void setVal(int val);
5     int getVal();
6 }
添加下⾯mk⽂件内容后,编译⽣成接⼝
1 diff --git a/frameworks/base/Android.mk b/frameworks/base/Android.mk
2 index b9692de0e1..c426a3cd99 100755
3 --- a/frameworks/base/Android.mk
4 +++ b/frameworks/base/Android.mk
5 @@ -240,6 +240,7 @@ LOCAL_SRC_FILES += \
6     core/java/android/os/IUpdateLock.aidl \
7     core/java/android/os/IUserManager.aidl \
8     core/java/android/os/IVibratorService.aidl \
9     + core/java/android/os/IEmbededLedService.aidl \
10     core/java/android/os/IDisplayDeviceManagementService.aidl \
11     core/java/android/os/IRkDisplayDeviceManagementService.aidl \
12     core/java/android/security/IKeystoreService.aidl \

2、Service

frameworks/base/services/java/com/android/server/EmbededLedService.java
1 package com.android.server;
2 import android.content.Context;
3 import android.os.IEmbededLedService;
4 import android.util.Slog;
5     public class EmbededLedService extends IEmbededLedService.Stub {
6         private static final String TAG = "EmbededLedService";
7         EmbededLedService() {
8
9     boolean status = init_native();
10     Slog.i(TAG,"EmbededLedService Stub init"+status);
11     }
12     public void setVal(int val) {
13         setVal_native(val);
14     }
15     public int getVal() {
16     return getVal_native();
17 }
18
19 //JNI⽅法
20 private static native boolean init_native();
21 private static native void setVal_native(int val);
22 private static native int getVal_native();
23 };

3、添加Service到System启动

1 diff --git
a/frameworks/base/services/java/com/android/server/SystemServer.java
b/frameworks/base/services/java/com/android/server/SystemServer.java
2 index cc6f1850e6..b22ecda734 100644
3 --- a/frameworks/base/services/java/com/android/server/SystemServer.java
4 +++ b/frameworks/base/services/java/com/android/server/SystemServer.java
5 @@ -1086,6 +1086,15 @@ public final class SystemServer {
6 } catch (Throwable e) {
7 reportWtf("starting DiskStats Service", e);
8 }
9 +
10 + try {
11 + Slog.i(TAG, "Embededled Service");
12 + ServiceManager.addService("embededled", new
EmbededLedService());
13 + } catch (Throwable e) {
14 + Slog.e(TAG, "Failure starting Embededled Service", e);
15 + }
16 +
17 +
18 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
19
20 if (!disableSamplingProfiler) {

4、编译&烧写

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/333558.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第20天 |● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树

文章目录 前言654.最大二叉树思路方法一 递归法方法一2 老师的优化递归法 617.合并二叉树思路方法一 递归法方法二 迭代法 700.二叉搜索树中的搜索思路方法一 递归法方法二 迭代法 98.验证二叉搜索树思路方法一 使用数组方法二 不使用数组代码注意点&#xff1a; 方法二 使用双…

SecureCRT for Mac注册激活版:专业终端SSH工具

SecureCRT是一款支持SSH&#xff08;SSH1和SSH2&#xff09;的终端仿真程序&#xff0c;简单地说是Windows下登录UNIX或Linux服务器主机的软件。 SecureCRT支持SSH&#xff0c;同时支持Telnet和rlogin协议。SecureCRT是一款用于连接运行包括Windows、UNIX和VMS的理想工具。通过…

零售EDI:Target DVS EDI项目案例

Target塔吉特是美国一家巨型折扣零售百货集团&#xff0c;与全球供应商建立长远深入的合作关系&#xff0c;目前国内越来越多的零售产品供应商计划入驻Target。完成入驻资格审查之后&#xff0c;Target会向供应商提出EDI对接邀请&#xff0c;企业需要根据指示完成供应商EDI信息…

Vue学习笔记3——事件处理

事件处理 1、事件处理器&#xff08;1&#xff09;内联事件处理器&#xff08;2&#xff09;方法事件处理器 2、事件参数3、事件修饰符 1、事件处理器 我们可以使用v-on 指令(简写为)来监听DOM事件&#xff0c;并在事件触发时执行对应的JavaScript。 用法: v-on:click"me…

[自动驾驶技术]-6 Tesla自动驾驶方案之硬件(AI Day 2021)

1 硬件集成 特斯拉自动驾驶数据标注过程中&#xff0c;跨250万个clips超过100亿的标注数据&#xff0c;无论是自动标注还是模型训练都要求具备强大的计算能力的硬件。下图是特斯拉FSD计算平台硬件电路图。 1&#xff09;神经网络编译器 特斯拉AI编译器主要针对PyTorch框架&am…

【面试必看】Java并发

并发 1. 线程 1. 线程vs进程 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff0c;因此进程是动态的。 系统运行一个程序即是一个进程从创建&#xff0c;运行到消亡的过程。在 Java 中&#xff0c;当我们启动 main 函数时其实就是启动了一个 JVM 的进…

java文档管理系统的设计与实现源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的文档管理系统的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 文档管理系统的…

初学C语言100题:经典例题节选(源码分享)

1.打印Hello World! #include <stdio.h>int main() {printf("hello world\n");//使用printf库函数 注意引用头文件return 0; } 2.输入半径 计算圆的面积 int main() {float r, s;//定义变量scanf("%f", &r);//输入半径s 3.14 * r * r;// 圆的…

Android network — 进程指定网络发包

Android network — 进程指定网络发包 0. 前言1. 进程绑定网络1.1 App进程绑定网络1.2 Native进程绑定网络 2. 源码原理分析2.1 申请网络requestNetwork2.2 绑定网络 BindProcessToNetwork 3. 总结 0. 前言 在android 中&#xff0c;一个app使用网络&#xff0c;需要在manifest…

el-table 实现嵌套表格的思路及完整功能代码

要实现的需求是这样的&#xff1a; 本来我是用 el-table 的 :span-method 方法实现的&#xff0c;但发现合并起来有问题&#xff0c;跟我的需求差距有些大&#xff0c;于是我想到了嵌套表格。但是嵌套完之后的样子也是很奇怪&#xff1a; 不要气馁&#xff0c;思路还是对的&a…

InTouch历史报警、历史事件按时段查询,导出

简介&#xff1a;本插件基于上位机组态InTouch的历史报警、操作记录而开发 适用InTouch版本&#xff1a;不限 适用Windows系统&#xff1a;不限 适用数据库&#xff1a;SQL Server 标记名点数&#xff1a;不限 配套软件安装&#xff1a;Excel、WPS、SQL Server 功能&…

创新力作 焕新首发丨捷顺科技·捷曜系列智慧停车新品全新上市

2024捷顺科技智慧停车全家族新品全面上市 全新外观、全新特性、全新体验 新控制机、新道闸、新超眸相机... 每款新品都有哪些功能亮点 带您一探究竟

linux mail命令及其历史

一、【问题描述】 最近隔壁组有人把crontab删了&#xff0c;crontab这个命令有点反人类&#xff0c;它的参数特别容易误操作&#xff1a; crontab - 是删除计划表 crontab -e 是编辑&#xff0c;总之就是特别容易输入错误。 好在可以通过mail命令找回&#xff0c;但是mai…

2.冒泡排序

样例输入 5 8 3 6 4 9 样例输出 3 4 6 8 9 以下是解题答案&#xff1a; class demo1{public static void main(String[] args) {Scanner scnnew Scanner(System.in);int[] array new int[scn.nextInt()];if(array.length>0&&array.length<200){for(int…

书生·浦语大模型全链路开源体系-作业1

视频链接&#xff1a;书生浦语大模型全链路开源体系_哔哩哔哩_bilibili 1. LLM发展 LLM是近年来人工智能领域的一个重要发展方向。大型语言模型的历史可以追溯到2017年,当时OpenAI推出了GPT-1(Generative Pre-trained Transformer)模型,这是一个基于Transformer架构的语言生成…

【前端三剑客之HTML】详解HTML

1. HTML(超文本标记语言) HTML意为超文本标记语言&#xff0c;其可以通过标签把其他网页/图片/视频等资源引入到当前网页中&#xff0c;让网页最终呈现出来的效果超越了文本.HTML是一种标记语言&#xff0c;其是由一系列标签组成的. 而且每个标签都有特定的含义和确定的页面显…

LeetCode/NowCoder-链表经典算法OJ练习3

孜孜不倦&#xff1a;孜孜&#xff1a;勤勉&#xff0c;不懈怠。指工作或学习勤奋不知疲倦。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;返回倒数第k个节点 题目二&#xff1a;链表的回文结构 题目三&#xff1a;相交链表 SUMUP结尾 说在前…

K-means聚类算法详细介绍

目录 &#x1f349;简介 &#x1f348;K-means聚类模型详解 &#x1f348;K-means聚类的基本原理 &#x1f348;K-means聚类的算法步骤 &#x1f348;K-means聚类的优缺点 &#x1f34d;优点 &#x1f34d;缺点 &#x1f348;K-means聚类的应用场景 &#x1f348;K-mea…

全局查询筛选器适用场景 以及各场景示例

EF Core中的全局查询筛选器&#xff08;Global Query Filters&#xff09;是一种强大的功能&#xff0c;可以在实体框架的DbContext级别为特定的EntityType设置默认的过滤条件。这些筛选器自动应用于所有涉及到相关实体的LINQ查询中&#xff0c;无论是直接查询还是通过Include或…

借助 CloudFlare 增强站点内容保护防采集

今天在一位站长的帮助下实测了 CloudFlare 增强站点内容保护实现防采集的功能,效果那是杠杠的,如果您的站点原创内容比较多的话,明月强烈建议试试 CloudFlare 这个内容保护,无论是 WordPress 、Typecho 都有非常好的效果,并且几乎没有任何误伤,搜索引擎爬虫蜘蛛更是不会影…