本文将带您了解关于项目实战:Qt九宫格图片资源浏览器的新内容,同时我们还将为您解释支持window、linux、兼容各国产系统,支持子文件夹,多选,全选,图片预览等的相关知识,另外,我们还将为您提供关
本文将带您了解关于项目实战:Qt九宫格图片资源浏览器的新内容,同时我们还将为您解释支持window、linux、兼容各国产系统,支持子文件夹,多选,全选,图片预览等的相关知识,另外,我们还将为您提供关于1.vue 项目实战、5 Practical React Projects(5个 React 项目实战)、Android 音频 (7)—— 项目实战 —— 耳麦插拔、ASP.NET CORE 项目实战 ---图形验证码的实现的实用信息。
本文目录一览:- 项目实战:Qt九宫格图片资源浏览器(支持window、linux、兼容各国产系统,支持子文件夹,多选,全选,图片预览等)
- 1.vue 项目实战
- 5 Practical React Projects(5个 React 项目实战)
- Android 音频 (7)—— 项目实战 —— 耳麦插拔
- ASP.NET CORE 项目实战 ---图形验证码的实现
项目实战:Qt九宫格图片资源浏览器(支持window、linux、兼容各国产系统,支持子文件夹,多选,全选,图片预览等)
若该文为原创文章,转载请注明原文出处
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/115035611
长期持续带来更多项目与技术分享,咨询请加QQ:21497936、微信:yangsir198808
红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…(点击传送门)
开发专栏:商业项目实战
需求
做嵌入式设备,需求九宫格图片资源浏览器:
1.设置根目录;
2.可拖动;
3.可设置列数与行数;
4.点击文件夹可以进入文件夹;
5.点击图片可以浏览图片;
6.支持触摸屏上下拽拖浏览;
7.支持长安出现类似手机更多的操作,用于选择多个图片项做其他操作;
8.可设置文件排序规则,如大小,更新时间,创建时间等等;
Demo
下载体验地址
CDSN粉丝免积分下载:https://download.csdn.net/download/qq21497936/15993722
QQ群:1047134658(点击“文件”搜索“browser”,群内与博文同步更新)
V1.5.1
#ifndef MULTIMEDIABROSWERWIDGET_H
#define MULTIMEDIABROSWERWIDGET_H
#include <QWidget>
#include <QDir>
#include <QElapsedTimer>
#include <QTimer>
#include <QLine>
#ifdef designer
#if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
#include <QtDesigner/QDesignerExportWidget>
#else
#include <QtUiPlugin/QDesignerExportWidget>
#endif
class QDESIGNER_WIDGET_EXPORT MultimediaBroswerWidget : public QWidget
#else
class MultimediaBroswerWidget : public QWidget
#endif
{
Q_OBJECT
public:
enum SORT_TYPE {
SORT_TYPE_FILE_NAME_ASC = 0x00, // 文件名升序(默认)
SORT_TYPE_FILE_NAME_DES, // 文件名降序序
SORT_TYPE_MODIFY_TIME_ASC, // 文件修改时间升序
SORT_TYPE_MODIFY_TIME_DES, // 文件修改时间降序
SORT_TYPE_SIZE_ASC, // 文件大小升序
SORT_TYPE_SIZE_DES // 文件大小降序
};
public:
explicit MultimediaBroswerWidget(QWidget *parent = 0);
public:
QString getRootDirPath() const; // 获取当前设置的根目录路径
QString getCurrentDirPath() const; // 获取当前所在的目录路径
QStringList getNameFilters() const; // 获取文件名过滤列表
QPixmap getBackgroundPixmap() const; // 获取背景图片
QPixmap getSelectedPixmap() const; // 获取选择状态的按钮图片
QPixmap getUnSelectedPixmap() const; // 获取非选择状态的按钮图片
QRect getSelectRect() const; // 获取选择按钮的区域
int getSelectRightMargin() const; // 获取选择按钮的右边间距
int getSelectBottomMargin() const; // 获取选择按钮的底部边距
bool getLongPressed() const; // 获取是否是长按选择后的多选状态
bool getSelectAll() const; // 获取是否全选
SORT_TYPE getSortType() const; // 获取分类规则
QPixmap getFolderPixmap() const; // 获取文件夹图标
QPixmap getFolderBackgroundPixmap() const; // 获取文件夹时的九宫格背景
bool getCanInOutDir() const; // 获取是否可以进入子文件夹
bool getInPicture() const; // 是否当前是放大图片的
int getRows() const; // 获取显示行数
int getCols() const; // 获取显示列数
QList<QString> getListFile() const; // 获取当前目录下的所有多媒体文件
QList<QString> getSelectFiles() const; // 获取当前选择的所有文件列表
int getTopMargin() const; // 获取上边框
int getRightMargin() const; // 获取右边框
int getBottomMargin() const; // 获取下边框
int getLeftMargin() const; // 获取左边框
int getHorizalSpace() const; // 获取水平间隔
int getVerticalSpace() const; // 获取垂直间隔
public:
void setRootDirPath(const QString &dirPath); // 设置预览文件夹路径
void setCurrentDirPath(const QString ¤tDirPath); // 设置当前文件夹路径
void setNameFilters(const QStringList &nameFilters); // 设置文件名过滤
void setBackgroundPixmap(const QPixmap &backgroundPixmap); // 设置背景图片
void setSelectedPixmap(const QPixmap &selectedPixmap); // 设置选择状态图片
void setUnSelectedPixmap(const QPixmap &unSelectedPixmap); // 设置未选择状态的图片
void setSelectRect(const QRect &selectRect); // 设置未选择状态图片
void setSelectRightMargin(int selectRightMargin); // 设置选择按钮的右边间距
void setSelectBottomMargin(int selectBottomMargin); // 设置选择按钮的底部间距
void setLongPressed(bool longPressed); // 设置长按选择后的多选状态
void selectAll(bool selected = true); // 设置进入选择状态,并设置是否全选
void setSortType(const SORT_TYPE &sortType); // 设置分类规则
void setFolderPixmap(const QPixmap &folderPixmap); // 设置文件夹图标
void setFolderBackgroundPixmap(const QPixmap &folderBackgroundPixmap);
// 获取文件夹时的九宫格背景
void setCanInOutDir(bool canInOutDir); // 设置是否可以进入下一级返回上一级文件夹(
// false的时候不显示子文件夹)
void setRows(int rows); // 设置显示行数
void setCols(int value); // 设置显示列数
void setTopMargin(int topMargin); // 设置上边框
void setRightMargin(int rightMargin); // 设置右边框
void setBottomMargin(int bottomMargin); // 设置下边框
void setLeftMargin(int leftMargin); // 设置左边框
void setMargin(int topMargin, int rightMargin, int bottomMargin, int leftMargin); // 设置边框
void setHorizalSpace(int horizalSpace); // 设置水平间隔
void setVerticalSpace(int verticalSpace); // 设置垂直间隔
void setInPicture(bool inPicture); // 是否当前是放大图片的
public:
void reload(); // 重新加载缓存
void keyLeft();
void keyRight();
void keyOk();
protected:
void updateRects(); // 更新位置
void updateSlider(); // 更新滑动栏
protected slots:
void slot_timerOut(); // 长按超时
protected:
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
protected:
void drawBackground(QPainter *painter);
void drawPixmaps(QPainter *painter);
void drawSlider(QPainter *painter);
void drawCurrentPixmap(QPainter *painter);
private:
QDir _dir; // 目录
QPixmap _backgroundPixmap; // 背景图片
QPixmap _unSelectedPixmap; // 未选中的图片
QPixmap _selectedPixmap; // 选中时的图片
QPixmap _folderPixmap; // 文件夹图片
QPixmap _folderBackgroundPixmap; // 文件夹九宫格项的背景
QFont _fontFolder; // 文件夹字体
QStringList _nameFilters; // 文件名过滤
QString _dirPath; // 目录路径
QString _rootDirPath; // 目录根路径
SORT_TYPE _sortType; // 排序种类
QList<QFileInfo> _listFileInfo; // 可读取的多媒体文件目录
QList<QPixmap> _listPixmap; // 多媒体文件预览缓存
QList<bool> _listSelected; // 是否选中文件
QRect _selectRect; // 选择框大小
int _selectRightMargin; // 选择框与图片右边的间距
int _selectBottomMargin; // 选择框与图片底边的间距
int _rows; // 显示行数
int _cols; // 显示列数
int _topMargin; // 上边界
int _rightMargin; // 右边界
int _bottomMargin; // 下边界
int _leftMargin; // 左边界
int _horizalSpace; // 水平间隔
int _verticalSpace; // 垂直间隔
int _sliderWidth; // 滑块宽度
QRect _sliderHandleRect; // 滑块矩形
QRect _sliderRect; // 滑动条矩形
int _logicalTop; // 逻辑顶部
int _logicalBottom; // 逻辑底部
int _logicalHeight; // 逻辑底部
int _sliderFreeSpace; // 滑块余量
bool _sliderPressed; // 滑块按下
private:
float _pixmapWidth; // 辅助变量,图像宽度
float _pixmapHeight; // 辅助变量,图像高度
QList<QRect> _listRect; // 多媒体
int _originY; // 竖向位置
bool _pressed; // 标记是否按下鼠标
bool _isMoved; // 标记是否按下鼠标后移动
QPoint _lastPoint; // 记录最近一次拖动的鼠标位置
bool _longPressed; // 标记是否在长按状态
bool _longPressedFirst; // 标记是否刚进入长安状态
QTimer _timer; // 用于计时长按操作
bool _canInOutDir; // 是否允许进入下一级返回上以及
bool _inPicture; // 是否当前是放大图片的
int _inPictureIndex;
QPixmap _inPicturePixmap;
};
#endif // MULTIMEDIABROSWERWIDGET_H
若该文为原创文章,转载请注明原文出处
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/115035611
1.vue 项目实战
技术栈
vue2 + vuex + vue-router + mint-ui + zepto + es6 + less
环境搭建
如果你已经跟着教程做一遍了,那么接下来可以来做项目了
大家是不是还经历过这样的项目结构呢?
一直以来我觉得这样子就可以了,想要什么就去插件网上下载就好了,比如 jquery,jquery.datepicker 等等。
然而现在需要搭建一个复杂一点的环境,这会帮助我们做一些 合并压缩,热更新,自动化等等一些麻烦事,方便我们的开发。
我们不再推荐下载 vue.js 到 js 文件夹,然后 html 里面引入使用。
然而写这些配置是相当麻烦的一件事,这里 vue-cli 帮助我们很好的解决了这个问题。
它能自动帮助我们生成一些配置和基础项目。
它生成的项目结构是这样子的。
不得不说真是方便呢,如果对前端工程化一点基础的没有,是不是瞬间懵逼呢?
是的,刚开始我也觉得这个很奇怪,为什么要弄的这么复杂,难道以前那样子不好吗?
我以前的话基本上是写个模板,然后扔给后端,让后端去使用这个模板,现在前后端分离了,自然而然前端就需要一个项目,当然不能像之前那么简单啦,以前那样的目录只是方便后端使用而已。
压缩编译这些东西都得前端来解决,不能再像之前那样子了。
那么,跟着下面的步骤一步步搭建好环境吧。
1、下载 Git
首先需要下载一个命令行工具,话说我是 QQ 电脑管家里面直接下载的,也可以点这里下载
如果有的话就不需要了,win10 自带的命令行也是可以的。

2、下载 node
然后下载一个 node。

3、使用命令行
然后新建个文件夹放你的项目,进入文件夹,右键打开命令行工具。
4、使用淘宝镜像资源 cnpm
输入下面这个东西,安装淘宝镜像。
npm install -g cnpm --registry=https://registry.npm.taobao.org
这个比 npm 好用。
输入一下指令看是否安装完成。
如果安装失败请尝试清一下缓存再安装!
npm cache clean
5、安装 webpack vue-cli
cnpm install -g vue-cli
cnpm install -g webpack
输入一下 -v ,测试是否安装成功
6、生成项目
只要输入这三个东西之后一直 n 回车就好了,至于下面那堆是什么暂时就不管了。(⊙v⊙)
第一遍如果等待时间太长 直接 ctrl + c 跳出就好了。

到这里项目已经生成完毕了,你可以在目录下面看到你生成的项目了。
7、启动项目
然后按照提示安装一下以来,我们就可以看到页面了。
cd menu
cnpm install && cnpm run dev
恭喜恭喜,我们已经成功的搭建了一个项目了,接下来我们需要一款编辑器,如果你使用其他编辑器也是可以的。
5 Practical React Projects(5个 React 项目实战)
1小时前
精选5个React项目实战,让你在实战中学习,在React中提高自己。
Word版5 Practical React Projects.docx
PDF版5 Practical React Projects.docx
更多React电子书
微信公众号
Android 音频 (7)—— 项目实战 —— 耳麦插拔
一、驱动程序上报耳麦拔插事件
1. 在有些 Android 版本中并不会在状态栏上显示耳麦图标。切换声道也不在系统中实现,而是在驱动中实现的。
2. headset headPhone lineOut
headset:既有听筒又有 Mic
headPhone:只有听筒,没有 Mic
lineOut: 就是输出模拟信号到音箱
驱动需要上报三种设备的拔插:headset、headPhone、lineOut。
3. 怎么上报:
(1) 输入子系统:可以上报按键事件也可以上报开关事件 (EV_SW),事件类型包括 headset、headPhone、lineOut。
或
(2) switch class 子系统:通过 uevent 向用户空间发送数据,Android 中有个线程专门监听这类事件。
具体使用哪一个要看 Android 源代码。
对于输入设备都需要指定能产生同步类事件 EV_SYN. 对于按键事件,输入子系统还需要设置按键值的范围,但是对于开关类事件不需要设置。
使用 switch dev 子系统时,名字必须要设置为 "h2w",Android 系统监听 /sys/class/switch/h2w 这个虚拟设备。
4. 驱动 Demo 代码
由于耳塞的检查 Codec 给过来的硬件中断线没有接到 Soc 上所以使用软件模拟。


#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/switch.h>
#include <linux/input.h>
static struct input_dev *g_virtual_input;
static ssize_t input_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long code;
long val;
char *endp;
/* 如果字符串前面含有非数字, simple_strtol不能处理 */
while ((*buf == '' '') || (*buf == ''\t''))
buf++;
code = simple_strtol(buf, &endp, 0);
/* 如果字符串前面含有非数字, simple_strtol不能处理 */
while ((*endp == '' '') || (*endp == ''\t''))
endp++;
val = simple_strtol(endp, NULL, 0);
printk("emulate to report EV_SW: 0x%x 0x%x\n", code, val);
input_event(g_virtual_input, EV_SW, code, val);
input_sync(g_virtual_input);
return count;
}
static DEVICE_ATTR(test_input, S_IRUGO | S_IWUSR, NULL, input_test_store);
static int register_input_device_for_jack(void)
{
int err;
/* 分配input_dev */
g_virtual_input = input_allocate_device();
/* 设置 */
/* 2.1 能产生哪类事件 */
set_bit(EV_SYN, g_virtual_input->evbit);
set_bit(EV_SW, g_virtual_input->evbit);
/* 2.2 能产生这类事件中的哪些 */
/* headset = 听筒 + MIC = SW_HEADPHONE_INSERT + SW_MICROPHONE_INSERT
* 同时上报 SW_HEADPHONE_INSERT 和 SW_MICROPHONE_INSERT, 就表示headset
* 为了简化, 对于android系统只上报SW_MICROPHONE_INSERT也表示headset
*/
set_bit(SW_HEADPHONE_INSERT, g_virtual_input->swbit);
set_bit(SW_MICROPHONE_INSERT, g_virtual_input->swbit);
set_bit(SW_LINEOUT_INSERT, g_virtual_input->swbit);
/* 2.3 这些事件的范围 */
g_virtual_input->name = "alsa_switch"; /* 不重要 */
/* 注册 */
err = input_register_device(g_virtual_input);
if (err) {
input_free_device(g_virtual_input);
printk("input_register_device for virtual jack err!\n");
return err;
}
/*
* sysfs文件创建方法:
* 创建/sys/class/input/inputX/test_input文件
* 可以执行类似下面的命令来模拟耳麦的动作:
* 触发上报headset插入: echo 4 1 > /sys/class/input/inputX/test_input
* 触发上报headset取下: echo 4 0 > /sys/class/input/inputX/test_input
* 触发上报headphone插入: echo 2 1 > /sys/class/input/inputX/test_input
* 触发上报headphonet取下: echo 2 0 > /sys/class/input/inputX/test_input
*/
err = device_create_file(&g_virtual_input->dev, &dev_attr_test_input);
if (err) {
printk("device_create_file for test_input err!\n");
input_unregister_device(g_virtual_input);
input_free_device(g_virtual_input);
return err;
}
return 0;
}
static void unregister_input_device_for_jack(void)
{
device_remove_file(&g_virtual_input->dev, &dev_attr_test_input);
input_unregister_device(g_virtual_input);
input_free_device(g_virtual_input);
}
/**************************************************************************************************************/
static struct switch_dev g_virtual_switch;
static ssize_t state_test_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
long val;
val = simple_strtol(buf, NULL, 0);
printk("emulate to report swtich state: 0x%x\n", val);
switch_set_state(&g_virtual_switch, val);
return count;
}
static DEVICE_ATTR(test_state, S_IRUGO | S_IWUSR, NULL, state_test_store);
static int register_switch_device_for_jack(void)
{
int err;
g_virtual_switch.name = "h2w"; /*名字必须是这个,Android系统中使用它判断*/
err = switch_dev_register(&g_virtual_switch);
if (err) {
printk("switch_dev_register h2w err!\n");
return err;
}
/* 创建/sys/class/switch/h2w/test_state文件
* 可以执行类似下面的命令来模拟耳麦的动作:
* 触发上报headset插入: echo 1 > /sys/class/switch/h2w/test_state
* 触发上报headset取下: echo 0 > /sys/class/switch/h2w/test_state
*/
err = device_create_file(g_virtual_switch.dev, &dev_attr_test_state);
if (err) {
printk("device_create_file test err!\n");
switch_dev_unregister(&g_virtual_switch);
return err;
}
return 0;
}
static void unregister_switch_device_for_jack(void)
{
device_remove_file(g_virtual_switch.dev, &dev_attr_test_state);
switch_dev_unregister(&g_virtual_switch);
}
/**************************************************************************************************************/
static int __init virtual_jack_init(void)
{
int err;
err = register_input_device_for_jack();
err = register_switch_device_for_jack();
return 0;
}
static void __exit virtual_jack_exit(void)
{
unregister_input_device_for_jack();
unregister_switch_device_for_jack();
}
module_init(virtual_jack_init);
module_exit(virtual_jack_exit);
MODULE_AUTHOR("weidongshan@qq.com");
MODULE_DESCRIPTION("Virutal jack driver for sound card");
MODULE_LICENSE("GPL");
/*
对应的Makefile:
KERN_DIR = /media/ubuntu/works/tiny4412/linux-3.0.86
obj-m += virtual_jack.o
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
*/
二、在状态栏显示耳麦图标
1. 优秀相关博文:
Android4.4 监听耳机插入处理方法:https://blog.csdn.net/anndy_peng/article/details/30240135
直接给出的是补丁和图片①,可直接参考它来实现再状态栏上添加耳塞图标的 App:
https://github.com/fire855/android_frameworks_base-mtk/commit/7661c081b037a32e273afaf70349a6a1518dab48
[整理] Android 屏幕适配 (不同的屏幕分辨率和尺寸),选择 mic 的图标大小的时候会使用到:https://blog.csdn.net/ttkatrina/article/details/50623043
2. 参考①,tiny4412 上操作步骤:
a. 确定在状态栏上图标的位置
修改 frameworks/base/core/res/res/values/config.xml
添加一行:<item><xliff:g id="id">headset</xliff:g></item>
b. 创建图标文件
从①中下载对应图片保存在如下位置
frameworks/base/packages/SystemUI/res/drawable-hdpi/stat_sys_headset_with_mic.png
frameworks/base/packages/SystemUI/res/drawable-hdpi/stat_sys_headset_without_mic.png
c. 修改源码, 当接收到消息时显示/清除图标 :
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
参考①中的修改。
d. 编译源文件
d.1 mmm frameworks/base/core/res // 编译config.xml
得到 out/target/product/tiny4412/system/framework/framework-res.apk
d.2 mmm frameworks/base/packages/SystemUI // 编译图标文件, 编译源码
得到 out/target/product/tiny4412/system/priv-app/SystemUI/SystemUI.apk
e. 替换单板上的文件
adb push到 /system/framework/framework-res.apk
adb push到 /system/priv-app/SystemUI/SystemUI.apk
f. 测试
使用5.1所编译得到的zImage启动开发板,
执行以下命令:
触发上报headset插入: echo 4 1 > /sys/class/input/input0/test_input #4=SW_MICROPHONE_INSERT,1=PlugIn
触发上报headset取下: echo 4 0 > /sys/class/input/input0/test_input
触发上报headphone插入: echo 2 1 > /sys/class/input/input0/test_input #2=SW_HEADPHONE_INSERT,1=PlugIn
触发上报headphone取下: echo 2 0 > /sys/class/input/input0/test_input
触发上报lineout插入: echo 6 1 > /sys/class/input/input0/test_input #6=SW_LINEOUT_INSERT,1=PlugIn
触发上报lineout取下: echo 6 0 > /sys/class/input/input0/test_input
测试结果:input的测试成功。但是uevent的失败,原因见下文
三、耳麦拔插事件调用流程分析
1. Android 系统使用 input 子系统还是使用 Switch class (uevent) 上报拔插操作,取决于 config_useDevInputEventForAudioJack 配置值,
该值为 true 时使用 input 子系统,为 false 时使用 uevent 机制,该值在下述文件中定义,后一个文件会覆盖前一个文件,目前在 4412 的配置文件
中选择的是使用输入子系统的。
frameworks/base/core/res/res/values/config.xml
device/friendly-arm/tiny4412/overlay/frameworks/base/core/res/res/values/config.xml
mUseDevInputEventForAudioJack的赋值:
InputManagerService(Context context) //InputManagerService.java
mUseDevInputEventForAudioJack = context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
2. 输入子系统对耳麦插拔事件上报流程
InputReader::loopOnce() //InputReader.cpp
mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
processEventsLocked(mEventBuffer, count);
InputReader::processEventsForDeviceLocked
device->process(rawEvents, count); //输入子系统中为每一个/dev/input/eventX都创建一个InputDevice
mapper->process(rawEvent); //对于开关事件,这里的InputMapper mapper是SwitchInputMapper
SwitchInputMapper::process(const RawEvent* rawEvent)
case EV_SW: processSwitch(rawEvent->code, rawEvent->value); //只是简单记录下来按键值
case EV_SYN: sync(rawEvent->when); //收到sync(type=EV_SYN,code=SYN_REPORT)后处理
//获取监听器,调用监听器的notifySwitch,这里的监听器是InputDispatch
getListener()->notifySwitch(&args);
InputDispatcher::notifySwitch(const NotifySwitchArgs* args) //InputDispatcher.cpp
mPolicy->notifySwitch(args->eventTime, args->switchValues, args->switchMask, policyFlags);
//上面调用的就是com_android_server_input_InputManagerService.cpp中的下面这个函数
NativeInputManager::notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) //com_android_server_input_InputManagerService.cpp
//调用java的同名函数notifySwitch,位于InputManagerService.java中
env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifySwitch, when, switchValues, switchMask);
notifySwitch(long whenNanos, int switchValues, int switchMask) //InputManagerService.java
//调用到mWiredAccessoryCallbacks中的notifyWiredAccessoryChanged
if (mUseDevInputEventForAudioJack) //这个就是来自上面的config.xml文件配置,为true才会选择输入子系统
mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues, switchMask);
mWiredAccessoryCallbacks是何时注册的:
startOtherServices() //SystemServer.java
//WiredAccessoryManager作为一个callback传给了inputManager
inputManager.setWiredAccessoryCallbacks(new WiredAccessoryManager(context, inputManager));
mWiredAccessoryCallbacks = callbacks;
因此上面调用的就是
WiredAccessoryManager.notifyWiredAccessoryChanged
mSwitchValues = (mSwitchValues & ~switchMask) | switchValues;
//根据上报的值确定变量headset的取值,显示那个图标由变量headset决定的
switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT)) {
case 0: headset = 0; break;
case SW_HEADPHONE_INSERT_BIT: headset = BIT_HEADSET_NO_MIC; break;
case SW_LINEOUT_INSERT_BIT: headset = BIT_LINEOUT; break;
case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break;
case SW_MICROPHONE_INSERT_BIT: headset = BIT_HEADSET; break;
default: headset = 0; break;
}
//WiredAccessoryManager的函数,这个函数将会进入Audio系统,uevent分支最终也是调用这个函数。
updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset);
3. Switch class 使用 uevent 对耳麦插拔事件上报流程
有一个 UeventThread 线程,循环读取 socket 的 uevent 事件并且发送 uevent.
(1)初始化:
WiredAccessoryManager //WiredAccessoryManager.java
//创建一个观察者
mObserver = new WiredAccessoryObserver();
//WiredAccessoryObserver构造函数调用
WiredAccessoryObserver()
//创建一个观察者事件的列表
mUEventInfo = makeObservedUEventList();
//当上面的config.xml中配置这个变量为false时就使用uevent机制
if (!mUseDevInputEventForAudioJack) {
//用于监听名为"h2w"这个Switch class驱动中创建的虚拟文件
//shell@tiny4412:/sys # find ./ -name h2w
//./devices/virtual/switch/h2w
//./class/switch/h2w
//这里只监听这三个事件,所以驱动程序通过uevent上报事件也只能上报这三个事件之一
uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC, BIT_LINEOUT);
init() //WiredAccessoryManager.java
startObserving("DEVPATH="+uei.getDevPath());
//往UEventThread里面添加观察者
UEventThread t = getThread();
//这里的this就是子类WiredAccessoryObserver
t.addObserver(match, this); //①
(2)从native代码中获取uevent事件
run()
//本地初始化
nativeSetup();
uevent_init() //android_os_UEventObserver.cpp
s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); //hardware/uevent.c
setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));
bind(s, (struct sockaddr *) &addr, sizeof(addr))
//等待下一个事件
nativeWaitForNextEvent(); //UEventObserver.java
uevent_next_event(char* buffer, int buffer_length) //hardware/uevent.c
poll(&fds, 1, -1);
recv(fd, buffer, buffer_length, 0);
//读取到数据后发送
sendEvent(message);
observer.onUEvent(event);
//取出上面①中设置的观察者
UEventObserver observer = mTempObserversToSignal.get(i);
observer.onUEvent(UEventObserver.UEvent event) //WiredAccessoryManager.java
String devPath = event.get("DEVPATH");
String name = event.get("SWITCH_NAME");
int state = Integer.parseInt(event.get("SWITCH_STATE"));
updateStateLocked(devPath, name, state);
//WiredAccessoryManager的函数,两条分支(input和Switch class)在这里汇合了
updateLocked(String newName, int newState)
4. uevent 实现和 input 实现在这里汇合位置
updateLocked(String newName, int newState) //WiredAccessoryManager.java
//获得message,然后发送message
Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState, mHeadsetState, newName);
mHandler.sendMessage(msg);
上面发送消息会触发消息的handler被调用:
handleMessage(Message msg) {
case MSG_NEW_DEVICE_STATE:
setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName);
//终于进入了Audio系统了,设置有线设备的连接状态
mAudioManager.setWiredDeviceConnectionState(inDevice, state, headsetName);
IAudioService service = getService(); //AudioManager.java
service.setWiredDeviceConnectionState(device, state, name);
//指定一个延时时间后又把消息放到一个消息队列中
delay = checkSendBecomingNoisyIntent(device, state);
queueMsgUnderWakeLock(mAudioHandler, MSG_SET_WIRED_DEVICE_CONNECTION_STATE, delay);
//会触发消息的handler被调用
handleMessage(Message msg)
onSetWiredDeviceConnectionState(msg.arg1, msg.arg2, (String)msg.obj);
sendDeviceConnectionIntent(device, state, name);
//构建一个Intent结构,然后向应用程序广播它,注册对这个Intent感兴趣的App就会收到它
Intent intent = new Intent();
intent.putExtra("state", state);
intent.putExtra("name", name);
intent.setAction(Intent.ACTION_HEADSET_PLUG);
ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
5. App 注册感兴趣的 Intent:
//构造函数中添加
PhoneStatusBarPolicy(Context context, CastController cast) //PhoneStatusBarPolicy.java
filter.addAction(Intent.ACTION_HEADSET_PLUG);
收到消息后onReceive被调用,updateHeadset是处理函数
onReceive(Context context, Intent intent)
if (action.equals(Intent.ACTION_HEADSET_PLUG))
updateHeadset(intent);
在状态栏中设置head set图标的状态
private final void updateHeadset(Intent intent) {
final String action = intent.getAction();
final int state = intent.getIntExtra("state", 4);
final int mic = intent.getIntExtra("microphone", 4);
switch (state) {
case 0: //拔出
mService.setIconVisibility("headset", false);
break;
case 1: //插入
if (mic == 1) { //耳机上有mic显示这张图标
mService.setIcon("headset", R.drawable.stat_sys_headset_with_mic, 0, null);
} else { //耳机上没有mic显示这张图标
mService.setIcon("headset", R.drawable.stat_sys_headset_without_mic, 0, null);
}
mService.setIconVisibility("headset", true);
break;
}
}
6. native 函数有些是在 framework 中,有些是在 system 中,有些是在 hardware 中,有些是在 external 中。
四、切换声音通道流程
1. 切换声音通道流程
a. 在驱动程序中切换:
比如:插上耳麦发生中断,在中断处理程序中设置声卡让声音从耳机中输出
b. 把输出通道的选择权交给 android 系统
目前这是主流的做法,驱动应该提供切换的能力,但是不应该替 App 进行决策。此处讲解此种方法。
3. 有个查看 Android 源码非常方便的网站:androidxref.com/5.0.0_r2/
输入 audio_policy.conf,选择右边的 "select all", 点 search
一个完整复杂的 audio_policy.conf 的例子:http://androidxref.com/5.0.0_r2/xref/device/asus/grouper/audio_policy.conf
4. App 只指定 stream type, 从哪个声卡播放出去那是 AudioFlinger 的内容。
5. output 支持哪些 device 在策略配置文件 /system/etc/audio_policy.conf (/etc 下也有?) 中就指定了。
6. 对应关系:
一个 output 对应一个播放线程,也对应一个声卡,可能对应多个 device; 一个 device 对应一个播放设备,比如喇叭或者耳机.
7. 怎样切换通道:
主要涉及的文件是AudioPolicyManager.cpp,参考0006_handleDeviceConnection UML
一旦插上USB声卡,就会创建一个对应的播放线程
驱动程序上报音频拔插事件, 该事件为某个device插入或拔出, Android系统需要切换声音通道。
过程为(核心文件为frameworks/av/services/audiopolicy/AudioPolicyManager.cpp): (核心函数为 setDeviceConnectionState)
a. checkOutputsForDevice
针对该device, 打开新的output, 创建新的playbackthread.
方法:
从audio_policy.conf中确定"本该有多少个output"可以支持它, mOutputs表示"已经打开的output", 两者对比即可确定"尚未打开的output"
b. checkOutputForAllStrategies / checkOutputForStrategy
对所有的strategy分组声音, 判断是否需要迁移到新的output, 如果需要则迁移对应Track到新的output,方法:
b.1 判断是否需要迁移
对于该strategy, 得到它的oldDevice, 进而得到它的outputs (srcOutputs);
对于该strategy, 得到它的newDevice, 进而得到它的outputs (dstOutputs);
如果这2个srcOutputs、dstOutputs不相同, 表示需要迁移
b.2 如果迁移:
把对应的Track设置为invalidate状态即可, App写AudioTrack时发现它是invalidate状态, 就会重新创建新的Track
audio_devices_t oldDevice = getDeviceForStrategy(strategy, true /*fromCache*/);
audio_devices_t newDevice = getDeviceForStrategy(strategy, false /*fromCache*/);
SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevice(oldDevice, mPreviousOutputs);
SortedVector<audio_io_handle_t> dstOutputs = getOutputsForDevice(newDevice, mOutputs);
c. getNewOutputDevice/setOutputDevice
这要操作HAL
8. output 的参数信息会构成一个 profile 结构描述
audio_hw_modules {
primary {
outputs {
primary { //这些参数信息会构造成一个profile
ASP.NET CORE 项目实战 ---图形验证码的实现
简介
很长时间没有来更新博客了,一是,最近有些忙,二是,Core也是一直在摸索中,其实已经完成了一个框架了,并且正在准备在生产环境中试用,但是很多东西也是出于自己理解的肤浅和技术的不断更新,经常变动,所以,如果自己还没有完全搞好,就来写博客,反复的修正,可能会误导一些新手朋友。
如果有正在研究Core的朋友,可以大家一起交流下。
验证码
相信很多朋友跟我一样,图形验证码成为了 Core 的一个绊脚石。
System.Drawing.Primitives 这是官方的一个Drawing库,但是没有Bitmap、Graphics等很多东西,所以这个做图形验证码基本Pass了。
CoreCompat.System.Drawing 这个是一个第三方的,使用了mono的System.Drawing实现,只要安装了之前使用System.Drawing的代码完全不用修改,也支持描画验证码等描画类的功能。如果需要linux或osx支持,可以安装runtime.linux.CoreCompat.System.Drawing和runtime.osx.10.10-x64.CoreCompat.System.Drawing。(地址:https://github.com/CoreCompat/CoreCompat),基本大家都是在用这个吧,这个在Windows下是没有任何问题的,在Linux上一直没有成功,也不知道是自己编译的问题还是什么问题。
zkweb.system.drawing 这个也是第三方的,从mono的System.Drawing修改得来的。过程比较详细,也实现了。所以把这个的使用跟大家分享一下。
这个类库和CoreCompat的不同点如下
没有使用强名称,CoreCompat为了让程序集名称一样使用了一个伪造的签名,但是导致Asp.Net和Owin等会检查签名的旧项目启动失败
CoreCompat的项目如果直接下载编译会出现100多个错误,大多是类型找不到的错误,我也不知道作者是怎么编译过去的
这个项目从mono 4.6.1.13复制了所有需要的文件并修改,直接下载编译就可以通过可以使用dotnet test跑单元测试,目前通过率约为80%
实际在linux上测试过并且给出了各个发行版安装libgdiplus的命令,目前已测试不引用System.Drawing.Primitive,因为System.Drawing.Primitive在.Net Framework下同时引用了原来的System.Drawing,有可能导致编译时类型冲突(实测只有警告)
Ubuntu Server 16.04 LTS 64bit
Fedora 24 64bit
CentOS 7.2 64bit
Zkweb.system.drawing
首先通过Nuget: Install-Package ZKWeb.System.Drawing 添加 引用
简单的图形验证码生成:
public class VierificationCodeServices
{
/// <summary>
/// 该方法用于生成指定位数的随机数
/// </summary>
/// <param name="VcodeNum">参数是随机数的位数</param>
/// <returns>返回一个随机数字符串</returns>
private string RndNum(int VcodeNum)
{
//验证码可以显示的字符集合
string Vchar = "0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,p" +
",q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,P,P,Q" +
",R,S,T,U,V,W,X,Y,Z";
string[] VcArray = Vchar.Split(new Char[] { '','' });//拆分成数组
string code = "";//产生的随机数
int temp = -1;//记录上次随机数值,尽量避避免生产几个一样的随机数
Random rand = new Random();
//采用一个简单的算法以保证生成随机数的不同
for (int i = 1; i < VcodeNum + 1; i++)
{
if (temp != -1)
{
rand = new Random(i * temp * unchecked((int)DateTime.Now.Ticks));//初始化随机类
}
int t = rand.Next(61);//获取随机数
if (temp != -1 && temp == t)
{
return RndNum(VcodeNum);//如果获取的随机数重复,则递归调用
}
temp = t;//把本次产生的随机数记录起来
code += VcArray[t];//随机数的位数加一
}
return code;
}
/// <summary>
/// 该方法是将生成的随机数写入图像文件
/// </summary>
/// <param name="code">code是一个随机数</param>
/// <param name="numbers">生成位数(默认4位)</param>
public MemoryStream Create(out string code, int numbers = 4)
{
code = RndNum(numbers);
Bitmap Img = null;
Graphics g = null;
MemoryStream ms = null;
Random random = new Random();
//验证码颜色集合
Color[] c = { Color.Black, Color.Red, Color.DarkBlue, Color.Green, Color.Orange, Color.Brown, Color.DarkCyan, Color.Purple };
//验证码字体集合
string[] fonts = { "Verdana", "Microsoft Sans Serif", "Comic Sans MS", "Arial", "宋体" };
//定义图像的大小,生成图像的实例
Img = new Bitmap((int)code.Length * 18, 32);
g = Graphics.FromImage(Img);//从Img对象生成新的Graphics对象
g.Clear(Color.White);//背景设为白色
//在随机位置画背景点
for (int i = 0; i < 100; i++)
{
int x = random.Next(Img.Width);
int y = random.Next(Img.Height);
g.DrawRectangle(new Pen(Color.LightGray, 0), x, y, 1, 1);
}
//验证码绘制在g中
for (int i = 0; i < code.Length; i++)
{
int cindex = random.Next(7);//随机颜色索引值
int findex = random.Next(5);//随机字体索引值
Font f = new Font(fonts[findex], 15, FontStyle.Bold);//字体
Brush b = new SolidBrush(c[cindex]);//颜色
int ii = 4;
if ((i + 1) % 2 == 0)//控制验证码不在同一高度
{
ii = 2;
}
g.DrawString(code.Substring(i, 1), f, b, 3 + (i * 12), ii);//绘制一个验证字符
}
ms = new MemoryStream();//生成内存流对象
Img.Save(ms, ImageFormat.Jpeg);//将此图像以Png图像文件的格式保存到流中
//回收资源
g.Dispose();
Img.Dispose();
return ms;
}
}
测试运行
在Controller中新建一个 IActionResult 用于输出验证码:
/// <summary>
/// 图形验证码
/// </summary>
/// <returns></returns>
public IActionResult ValidateCode([FromServices]VierificationCodeServices _vierificationCodeServices)
{
string code = "";
System.IO.MemoryStream ms = _vierificationCodeServices.Create(out code);
HttpContext.Session.SetString("LoginValidateCode", code);
Response.Body.Dispose();
return File(ms.ToArray(), @"image/png");
}
前台输出:<img id="imgVerify" src="/sysmanage/account/ValidateCode" alt="看不清?点击更换" onclick="this.src = this.src + ''?''"/>
Windows
我们在Windows下运行一下:
Linux(CentOS7)
我们按照步骤,执行以下命令:
yum install autoconf automake libtool
yum install freetype-devel fontconfig libXft-devel
yum install libjpeg-turbo-devel libpng-devel giflib-devel libtiff-devel libexif-devel
yum install glib2-devel cairo-devel
git clone https://github.com/mono/libgdiplus
cd libgdiplus
./autogen.sh
make
make install
cd /usr/lib64/
ln -s /usr/local/lib/libgdiplus.so gdiplus.dll
把我们发布好的项目上传到Linux服务器上,进入项目目录:
cd OcelotWeb
dotnet Ocelot.Web.dll
我们的打开网站看一下:
没有显示出来,我们看下运行的日志:
FontFamily Not Found,字体的错误,我们看一下我们的代码:
这些字体Linux没有,这个解决办法很多,这里我用了最简单的,我不想再去修改我的代码,我就直接把windows下的这些字体拷贝出来:
上传到服务器的 /usr/share/fonts/chinese/TrueType 目录下(chinese/TrueType 两个目录是自己创建的)
进入这个目录:
cd /usr/share/fonts/chinese/TrueType
mkfontscale
mkfontdir
fc-cache -fv
再次打开之前的页面:
出现了!
原文地址:http://www.cnblogs.com/yuangang/p/6000460.html
.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注
本文分享自微信公众号 - dotNET跨平台(opendotnet)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
我们今天的关于项目实战:Qt九宫格图片资源浏览器和支持window、linux、兼容各国产系统,支持子文件夹,多选,全选,图片预览等的分享已经告一段落,感谢您的关注,如果您想了解更多关于1.vue 项目实战、5 Practical React Projects(5个 React 项目实战)、Android 音频 (7)—— 项目实战 —— 耳麦插拔、ASP.NET CORE 项目实战 ---图形验证码的实现的相关信息,请在本站查询。
本文标签: