• [技术干货] Handle-转载
    1.Handler的实现原理从四个方面看 Handler 、 Message 、 MessageQueue 和 LooperHandler: 负责消息的发送和处理Message: 消息对象,类似于链表的一个结点 ;MessageQueue: 消息队列,用于存放消息对象的数据结构 ;Looper: 消息队列的处理者(用于轮询消息队列的消息对象 )Handler 发送消息时调用 MessageQueue 的 enqueueMessage 插入一条信息到 MessageQueue,Looper 不断轮询调用MeaasgaQueue 的 next 方法 如果发现 message 就调用 handler 的 dispatchMessage , dispatchMessage 被成功调 用,接着调用handlerMessage() 。2.子线程中能不能直接new一个Handler,为什么主线程可以 主线程的Looper第一次调用loop方法,什么时候,哪个类        不能,因为Handler 的构造方法中,会通过 Looper.myLooper() 获取 looper 对象,如果为空,则抛出异常, 主线程则因为已在入口处ActivityThread 的 main 方法中通过 Looper.prepareMainLooper() 获取到这个对象, 并通过 Looper.loop() 开启循环,在子线程中若要使用 handler ,可先通过 Loop.prepare 获取到 looper 对象,并使用 Looper.loop()开启循环。3.Handler导致的内存泄露原因及其解决方案1.Java 中非静态内部类和匿名内部类都会隐式持有当前类的外部引用2. 我们在 Activity 中使用非静态内部类初始化了一个 Handler, 此 Handler 就会持有当前 Activity 的引用。3. 我们想要一个对象被回收,那么前提它不被任何其它对象持有引用,所以当我们 Activity 页面关闭之后 , 存在引用关 系:" 未被处理 / 正处理的消息 -> Handler 实例 -> 外部类 ", 如果在 Handler 消息队列 还有未处理的消息 / 正在处理消 息时 导致Activity 不会被回收,从而造成内存泄漏解决方案 :1. 将 Handler 的子类设置成 静态内部类 , 使用 WeakReference弱引用持有 Activity 实例2. 当外部类结束生命周期时,清空 Handler 内消息队列4.一个线程可以有几个Handler,几个Looper,几个MessageQueue对象        一个线程可以有多个Handler, 只有一个 Looper 对象 , 只有一个 MessageQueue 对象。 Looper.prepare() 函数中知道,。在Looper 的 prepare 方法中创建了 Looper 对象,并放入到 ThreadLocal 中,并通过 ThreadLocal 来获取 looper 的对象, ThreadLocal 的内部维护了一个 ThreadLocalMap 类 ,ThreadLocalMap 是以当前 thread 做为 key 的 , 因此可以得 知,一个线程最多只能有一个Looper 对象, 在 Looper 的构造方法中创建了 MessageQueue 对象,并赋值给 mQueue字段。因为 Looper 对象只有一个,那么 Messagequeue 对象肯定只有一个。5.Message对象创建的方式有哪些 & 区别,Message.obtain()怎么维护消息池的1.Message msg = new Message();每次需要 Message 对象的时候都创建一个新的对象,每次都要去堆内存开辟对象存储空间2.Message msg = Message.obtain();obtainMessage 能避免重复 Message 创建对象。它先判断消息池是不是为空,如果非空的话就从消息池表头的 Message取走 , 再把表头指向 next 。如果消息池为空的话说明还没有 Message 被放进去,那么就 new 出来一个 Message 对象。消息池使用 Message 链表 结构实现,消息池默认最大值 50 。消息在 loop 中被 handler 分发消费之后会执行回收的操作,将该消息内部数据清空并添加到消息链表的表头。3.Message msg = handler.obtainMessage(); 其内部也是调用的 obtain() 方法6.Handler 有哪些发送消息的方法sendMessage(Message msg)sendMessageDelayed(Message msg, long uptimeMillis)post(Runnable r)postDelayed(Runnable r, long uptimeMillis)sendMessageAtTime(Message msg,long when)7.Handler的post与sendMessage的区别和应用场景1. 源码sendMessagesendMessage-sendMessageAtTime-enqueueMessage 。postsendMessage-getPostMessage-sendMessageAtTime-enqueueMessage getPostMessage 会先生成一个 Messgae,并且把 runnable 赋值给 message 的 callback2.Looper->dispatchMessage 处理时public void dispatchMessage(@NonNull Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}dispatchMessage 方法中直接执行 post 中的 runnable 方法。而 sendMessage 中如果 mCallback 不为 null 就会调用 mCallback.handleMessage(msg) 方法,如果 handler 内的 callback不为空,执行 mCallback.handleMessage(msg) 这个处理消息并判断返回是否为 true ,如果返回 true ,消息 处理结束,如果返回false,handleMessage(msg) 处理。否则会直接调用 handleMessage 方法。post方法和 handleMessage 方法的不同在于,区别就是调用 post 方法的消息是在 post 传递的 Runnable 对象的 run 方法中处理,而调用sendMessage 方法需要重写 handleMessage 方法或者给 handler 设置 callback ,在 callback 的handleMessage 中处理并返回 true应用场景post 一般用于单个场景 比如单一的倒计时弹框功能 sendMessage 的回调需要去实现 handleMessage Message 则做为参数 用于多判断条件的场景。8.handler postDealy后消息队列有什么变化,假设先 postDelay 10s, 再 postDelay 1s, 怎么处理这2条消息sendMessageDelayed-sendMessageAtTime-sendMessage————————————————版权声明:本文为CSDN博主「界斗士」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_14931305/article/details/126245284
  • [投屏] hello,我想问下Huawei IdeaHub API有没有什么相关接口可以从windows系统中获取android系统的ip
    RThello,我想问下Huawei IdeaHub API有没有什么相关接口可以从windows系统中获取android系统的ip,目前看了下文档没有相关接口,那有没有其他方式可以获取?
  • [技术干货] React Native 开发真有那么好?!-转载
    整理 | 苏宓出品 | CSDN(ID:CSDNnews)兜兜转转,不少开发者还是发现 React Native 的真相定律。日前,国外知名聊天软件 Discord 于官方博客上发布了一则《Android 版本 Discord 激动人心的更新》公告。在公告中,Discord 指出,其将在未来几周内改进 Android 版本的应用程序,具体包括通过跨 Android、iOS 和桌面端的集中式简化应用开发流程,实现跨平台的功能一致性。iOS、桌面端、Android 步调不一,苦不堪言的跨平台开发一直以来,我们在使用软件时经常会发现不同平台上,相同的应用程序更新步伐都不太一致,往往都是 iOS 端和桌面端先行发布之后,Android 平台上的应用程序才姗姗来迟。这种现象也同样出现在了 Discord 上。如今,Discord 决定改变这种现状,其表示正在使用 React Native 来进行 Android 应用开发,这意味着在 Discord 的每个平台上都能以更快速度改进体验,同时仍然在 UI 中保留 Android 和 iOS 特定的模式。Discord Android 版使用 React Native 开发作为一款跨平台框架,React Native 是由 Facebook (现更名为 Meta)软件工程师 Jordan Walke 在 2013 年发起了的一个黑客马拉松项目,彼时他发现了一种为 iOS 应用程序生成 UI 组件的方法。因此,React 开源框架最初是为 iOS 构建的,并于 2015 年 3 月作为开源项目在 GitHub(https://github.com/facebook/react-native)上可供公众使用。随后,Facebook 也迅速为 React Native 提供了 Android 支持。截至目前,React Native 已经成为 GitHub 中最大的项目之一,拥有 104k Star 和 22.3k 分支。它也被广泛用于许多流行的移动应用程序,包括Instagram、Microsoft Outlook、Shopify、Tesla、Pinterest 等等。Discord 自 2015 年以来一直在其 iOS 应用程序中使用 React Native。如今在将 Android 应用程序开发迁移到 React Native 框架下, Discord 表示,“Android 用户还将享受更快的应用程序更新发布周期的好处。React Native 允许我们简化和整合流程,有助于工程师更高效地工作,更频繁地推送更新,特别是现在团队不会花太多时间为不同的设备维护不同的代码库。”React Native 是跨平台神器?针对 Discord 的迁移,不少网友也发起了对 React Native 的使用的讨论。@ramesh31:React Native 确实是移动开发的游戏规则改变者。去年,我用它来构建一个新的 iOS 应用程序,这是自旧的 Objective-C / UI-Kit 时代以来的第一次。实际上,我在几分钟内构建了一些东西,这在当时需要花费数小时或数天的时间编写自定义 OpenGL 和网络代码。在这一点上,任何性能权衡都是值得的。不过,由于性能、迭代速度、复杂度等维度的重重挑战,曾经也有不少公司如 Airbnb、Udacity 在使用一段时间的 React Native 之后选择了放弃,重新拥抱原生开发。对此,也有网友@Grim-444 根据自己公司的实践分享道:“我非常确信 React Native 只带来一件真正的改变,那就是它为 PM/经理提供了一些很好的宣传材料,告诉他们如何领导团队“切换到共享的跨平台代码库”。然后,他们在使用 React Native 做迁移工作时,进而需要一直处理迁移到 RN 的长期后果。以我们公司为例,近日,技术团队将 Android、iOS 应用程序从原生开发转为 React Native 开发。RN 发起人承诺在平台之间共享代码以及性能会和原生开发一样好,以及 UI 也会像原生开发一样获得各种各样的好处,然而最终这些都没有真正的实现。到了最后,RN 的代码只占了 Android 应用程序的 20%。技术团队不能实现“编写一次”,在两端运行。原来,我们有两个团队:iOS 和 Android 开发团队。现在,我们有三个团队,iOS 原生、Android 原生和 React Native 团队。进而 React Native 团队内部还被分为 iOS 和 Android 两大阵营,显得特别乱。另外使用 RN 开发还存在一些问题,如:对于在 RN 中没有办法完成的事情,我们还得需要一个配备齐全的 Android 团队的支持。在使用 React Native 开发时,与原生开发的应用程序性能差异非常明显。现在我们组织也有问题,因为应用程序涉及多个团队/语言,应用程序的不同部分由不同的团队和管理层拥有,而不是只有一个团队来开发一款好的应用程序。我们团队的不少工程师都不想与 RN 有任何关系。我们的代码库现在依赖于第三方平台和完全不同于 Google 所说的应该构建 Android 应用程序的语言。为什么我们不按照 Android 团队所说的方式构建 Android 应用程序呢?是否真的有必要长期支持 RN/JS?”为此,你怎么看?是否使用过 React Native?参考:https://news.ycombinator.com/item?id=32310392https://discord.com/blog/android-react-native-framework-update————————————————版权声明:本文为CSDN博主「CSDN资讯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/csdnnews/article/details/126132583
  • [二次开发] 关于android SDKdemo 崩溃问题的小结
    在开发者二次开发的过程中可能遇到过安卓端的崩溃问题,请开发者参考以下指导;由于gradle版本引起的崩溃:由于开发者对gradle版本有项目级别的要求,修改了gradle版本号,导致编译出来的版本在运行过程中崩溃,这种崩溃,请开发者按照https://support.huaweicloud.com/sdkreference-meeting/sdk_demo_0002.html的指导,重新配置一下运行环境。第二类崩溃
  • [技术干货] STM32+HC05串口蓝牙设计简易的蓝牙音箱
    # 一、环境介绍 **MCU:** STM32F103C8T6 **蓝牙模块:** HC05 (串口蓝牙) **音频解码模块:** VS1053B **OLED显示屏:** 0.96寸SPI接口OLED **开发软件:** Keil5 **上位机:** 使用QT设计Android端APP # 二、功能介绍 Android手机打开APP,设置好参数之后,选择音乐文件发送给蓝牙音箱设备端,HC05蓝牙收到数据之后,再传递给VS1053进行播放。程序里采用环形缓冲区,接收HC05蓝牙传递的数据,设置好传递的参数之后,基本播放音乐是很流畅的。 ## 三、硬件实物 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657071049631663770.png) VS1053可以接耳机或者接音箱设备即可听音乐。 # 四、设置HC05蓝牙波特率 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657071065191389720.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657071075223339945.png) HC05蓝牙串口默认波特率是38400,为了提高蓝牙传输速率,需要修改波特率为: 921600。 # 五、APP端界面 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657071121416927792.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657071130267282037.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657071160271559862.png) # 六、设备端:核心代码 ## 6.1 vs1053.c ```cpp #include "vs1053b.h" /* 函数功能:移植接口--SPI时序读写一个字节 函数参数:data:要写入的数据 返 回 值:读到的数据 */ u8 VS1053_SPI_ReadWriteByte(u8 tx_data) { u8 rx_data=0; u8 i; for(i=0;i8;i++) { VS1053_SCLK=0; if(tx_data&0x80){VS1053_OUTPUT=1;} else {VS1053_OUTPUT=0;} tx_data=1; VS1053_SCLK=1; rx_data=1; if(VS1053_INPUT)rx_data|=0x01; } return rx_data; } /* 函数功能:初始化VS1053的IO口 */ void VS1053_Init(void) { RCC->APB2ENR|=10; AFIO->MAPR&=~(0x724); //释放PA13/14/15 AFIO->MAPR|=0x424; RCC->APB2ENR|=12; RCC->APB2ENR|=13; GPIOA->CRH&=0x00000FFF; GPIOA->CRH|=0x33338000; GPIOB->CRL&=0xFFF00FFF; GPIOB->CRL|=0x00083000; VS1053_SCLK=1; VS1053_XCS=1; } /* 函数功能:软复位VS10XX */ void VS1053_SoftReset(void) { u8 retry=0; while(VS1053_DREQ==0); //等待软件复位结束 VS1053_SPI_ReadWriteByte(0Xff); //启动传输 retry=0; while(VS1053_ReadReg(SPI_MODE)!=0x0800) // 软件复位,新模式 { VS1053_WriteCmd(SPI_MODE,0x0804); // 软件复位,新模式 DelayMs(2);//等待至少1.35ms if(retry++>100)break; } while(VS1053_DREQ==0);//等待软件复位结束 retry=0; while(VS1053_ReadReg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD { VS1053_WriteCmd(SPI_CLOCKF,0X9800); //设置VS10XX的时钟,3倍频 ,1.5xADD if(retry++>100)break; } DelayMs(20); } /* 函数 功 能:硬复位MP3 函数返回值:1:复位失败;0:复位成功 */ u8 VS1053_Reset(void) { u8 retry=0; VS1053_RESET=0; DelayMs(20); VS1053_XDCS=1;//取消数据传输 VS1053_XCS=1; //取消数据传输 VS1053_RESET=1; while(VS1053_DREQ==0&&retry200)//等待DREQ为高 { retry++; DelayUs(50); }; DelayMs(20); if(retry>=200)return 1; else return 0; } /* 函数功能:向VS10XX写命令 函数参数: address:命令地址 data :命令数据 */ void VS1053_WriteCmd(u8 address,u16 data) { while(VS1053_DREQ==0); //等待空闲 VS1053_XDCS=1; VS1053_XCS=0; VS1053_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令 VS1053_SPI_ReadWriteByte(address); //地址 VS1053_SPI_ReadWriteByte(data>>8); //发送高八位 VS1053_SPI_ReadWriteByte(data); //第八位 VS1053_XCS=1; } /* 函数参数:向VS1053写数据 函数参数:data:要写入的数据 */ void VS1053_WriteData(u8 data) { VS1053_XDCS=0; VS1053_SPI_ReadWriteByte(data); VS1053_XDCS=1; } /* 函数功能:读VS1053的寄存器 函数参数:address:寄存器地址 返回值:读到的值 */ u16 VS1053_ReadReg(u8 address) { u16 temp=0; while(VS1053_DREQ==0);//非等待空闲状态 VS1053_XDCS=1; VS1053_XCS=0; VS1053_SPI_ReadWriteByte(VS_READ_COMMAND);//发送VS10XX的读命令 VS1053_SPI_ReadWriteByte(address); //地址 temp=VS1053_SPI_ReadWriteByte(0xff); //读取高字节 temp=temp8; temp+=VS1053_SPI_ReadWriteByte(0xff); //读取低字节 VS1053_XCS=1; return temp; } /* 函数功能:读取VS1053的RAM 函数参数:addr:RAM地址 返 回 值:读到的值 */ u16 VS1053_ReadRAM(u16 addr) { u16 res; VS1053_WriteCmd(SPI_WRAMADDR, addr); res=VS1053_ReadReg(SPI_WRAM); return res; } /* 函数功能:写VS1053的RAM 函数参数: addr:RAM地址 val:要写入的值 */ void VS1053_WriteRAM(u16 addr,u16 val) { VS1053_WriteCmd(SPI_WRAMADDR,addr); //写RAM地址 while(VS1053_DREQ==0); //等待空闲 VS1053_WriteCmd(SPI_WRAM,val); //写RAM值 } /* 函数参数:发送一次音频数据,固定为32字节 返 回 值:0,发送成功 1,本次数据未成功发送 */ u8 VS1053_SendMusicData(u8* buf) { u8 n; if(VS1053_DREQ!=0) //送数据给VS10XX { VS1053_XDCS=0; for(n=0;n32;n++) { VS1053_SPI_ReadWriteByte(buf[n]); } VS1053_XDCS=1; }else return 1; return 0;//成功发送了 } /* 函数参数:发送一次音频数据,固定为32字节 返 回 值:0,发送成功 1,本次数据未成功发送 */ void VS1053_SendMusicByte(u8 data) { u8 n; while(VS1053_DREQ==0){} VS1053_XDCS=0; VS1053_SPI_ReadWriteByte(data); VS1053_XDCS=1; } /* 函数功能:设定VS1053播放的音量 函数参数:volx:音量大小(0~254) */ void VS1053_SetVol(u8 volx) { u16 volt=0; //暂存音量值 volt=254-volx; //取反一下,得到最大值,表示最大的表示 volt=8; volt+=254-volx; //得到音量设置后大小 VS1053_WriteCmd(SPI_VOL,volt);//设音量 } ``` # 七、Android手机APP核心源码 ## 7.1 代码页面 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657071219934930241.png) ## 7.2 mainwindow.cpp代码 ```cpp #include "mainwindow.h" #include "ui_mainwindow.h" /* * 设置QT界面的样式 */ void MainWindow::SetStyle(const QString &qssFile) { QFile file(qssFile); if (file.open(QFile::ReadOnly)) { QString qss = QLatin1String(file.readAll()); qApp->setStyleSheet(qss); QString PaletteColor = qss.mid(20,7); qApp->setPalette(QPalette(QColor(PaletteColor))); file.close(); } else { qApp->setStyleSheet(""); } } static const QLatin1String serviceUuid("00001101-0000-1000-8000-00805F9B34FB"); //这个字符串里面的内容就是串口模式的Uuid MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); this->SetStyle(":/qss/blue.css"); //设置样式表 this->setWindowTitle("HC05蓝牙音箱"); //设置标题 this->setWindowIcon(QIcon(":/wbyq.ico")); //设置图标 /*1. 实例化蓝牙相关的对象*/ discoveryAgent = new QBluetoothDeviceDiscoveryAgent(); localDevice = new QBluetoothLocalDevice(); socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); //RfcommProtocol表示该服务使用RFCOMM套接字协议。RfcommProtocol属于模拟RS232模式,就叫串口模式 /*2. 关联蓝牙设备相关的信号*/ /*2.1 关联发现设备的槽函数,当扫描发现周围的蓝牙设备时,会发出deviceDiscovered信号*/ connect(discoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), this, SLOT(addBlueToothDevicesToList(QBluetoothDeviceInfo)) ); //蓝牙有数据可读 connect(socket, SIGNAL(readyRead()), this, SLOT(readBluetoothDataEvent()) ); //蓝牙连接建立成功 connect(socket, SIGNAL(connected()), this, SLOT(bluetoothConnectedEvent()) ); //蓝牙断开连接 connect(socket, SIGNAL(disconnected()), this, SLOT(bluetoothDisconnectedEvent()) ); //蓝牙写成功的数据 connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(bluetoothbytesWritten(qint64)) ); /*3.2 设置标签显示本地蓝牙的名称*/ QString name_info("本机蓝牙:"); name_info+=localDevice->name(); ui->label_BluetoothName->setText(name_info); ui->plainTextEdit->setEnabled(false); //不可编辑 /*定时器用于定时发送文件*/ timer = new QTimer(this); //创建定时器 connect(timer, SIGNAL(timeout()), this, SLOT(update())); //关联槽函数 ConnectStat=0; //连接状态 SendStat=0; //文件打开状态 FileSendTime=100; //默认每次发送的时间 单位ms ui->lineEdit_Timer->setText(QString::number(FileSendTime)); FileSendCnt=32; //默认每包数据值32字节 ui->lineEdit_SendFileCnt->setText(QString::number(FileSendCnt)); //创建存放音乐文件的目录 QDir dir; if(dir.mkpath("/sdcard/WBYQ_MP3")) { ui->plainTextEdit->insertPlainText("/WBYQ_MP3 目录创建成功!\n"); } else { ui->plainTextEdit->insertPlainText("/WBYQ_MP3 目录创建失败!\n"); } MusicFileDir="assets:/nansannan.mp3"; //目录的路径 Def_MusicName="assets:/nansannan.mp3"; file.setFileName(Def_MusicName); //设置文件名称 ui->lineEdit_MusicName->setText(Def_MusicName); //默认文件名称 } int count=0; //更新 void MainWindow::update() { if(SendStat) { int len; if(file.atEnd()==false) //文件未结束 { len=file.read(FileBuff,FileSendCnt); len=socket->write(FileBuff,len); //发送数据 } else { file.close(); timer->stop(); //停止定时器 SendStat=0; ui->plainTextEdit->setPlainText("文件读取写入完毕!\n"); } } } MainWindow::~MainWindow() { delete ui; delete discoveryAgent; delete localDevice; delete socket; timer->stop(); delete timer; } void MainWindow::on_pushButton_CloseBluetooth_clicked() { /*关闭蓝牙设备*/ localDevice->setHostMode(QBluetoothLocalDevice::HostPoweredOff); } void MainWindow::on_pushButton_BluetoothScan_clicked() { /*3.1 检查蓝牙是否开启*/ if(localDevice->hostMode() == QBluetoothLocalDevice::HostPoweredOff) { /*请求打开蓝牙设备*/ localDevice->powerOn(); } /*开始扫描周围的蓝牙设备*/ discoveryAgent->start(); ui->comboBox_BluetoothDevice->clear(); //清除条目 } void MainWindow::on_pushButton_DeviceVisible_clicked() { /*设置蓝牙可见,可以被周围的设备搜索到,在Android上,此模式最多只能运行5分钟。*/ // localDevice->setHostMode( QBluetoothLocalDevice::HostDiscoverable); static bool cnt=1; cnt=!cnt; if(cnt) { MusicFileDir="assets:/nansannan.mp3"; //目录的路径 QMessageBox::information(this,"提示","路径切换为内部路径!\n请选择文件!",QMessageBox::Ok,QMessageBox::Ok); } else { MusicFileDir="/mnt/sdcard"; //目录的路径 QMessageBox::information(this,"提示","路径切换为SD路径!\n请选择文件!",QMessageBox::Ok,QMessageBox::Ok); } } void MainWindow::on_pushButton_StopScan_clicked() { /*停止扫描周围的蓝牙设备*/ discoveryAgent->stop(); } /*当扫描到周围的设备时会调用当前的槽函数*/ void MainWindow::addBlueToothDevicesToList(const QBluetoothDeviceInfo &info) { // QString label = QString("%1 %2").arg(info.name()).arg(info.address().toString()); QString label = QString("%1 %2").arg(info.address().toString()).arg(info.name()); ui->comboBox_BluetoothDevice->addItem(label); //添加字符串到comboBox上 } //有数据可读 void MainWindow::readBluetoothDataEvent() { QByteArray all = socket->readAll(); ui->plainTextEdit->insertPlainText(all); } //建立连接 void MainWindow::bluetoothConnectedEvent() { QMessageBox::information(this,tr("连接提示"),"蓝牙连接成功!"); ConnectStat=1; /*停止扫描周围的蓝牙设备*/ discoveryAgent->stop(); } //断开连接 void MainWindow::bluetoothDisconnectedEvent() { ConnectStat=0; QMessageBox::information(this,tr("连接提示"),"蓝牙断开连接!"); } /* 在说蓝牙设备连接之前,不得不提一个非常重要的概念,就是蓝牙的Uuid,引用一下百度的: 在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。 正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。 UUID类可表现为短整形(16或32位)和长整形(128位)UUID。 他提供了分别利用String和16位或32位数值来创建类的构造函数,提供了一个可以比较两个UUID(如果两个都是128位)的方法,还有一个可以转换一个UUID为一个字符串的方法。 UUID实例是不可改变的(immutable),只有被UUID标示的服务可以被发现。 在Linux下你用一个命令uuidgen -t可以生成一个UUID值; 在Windows下则执行命令uuidgen 。UUID看起来就像如下的这个形式:2d266186-01fb-47c2-8d9f-10b8ec891363。当使用生成的UUID去创建一个UUID对象,你可以去掉连字符。 */ //发送音乐文件 void MainWindow::on_pushButton_SendData_clicked() { if(ConnectStat) { if(file.open(QIODevice::ReadOnly)) { SendStat=1; count=0; ui->plainTextEdit->insertPlainText("系统提示: 开始发送文件!\n"); ui->lineEdit_fileSizef->setText(QString::number(file.size())); ui->lineEdit_fileCount->setText(""); ui->progressBar_SendCount->setMaximum(file.size()); //设置进度条显示最大值 ui->progressBar_SendCount->setValue(0); //设置进度条的值 timer->start(FileSendTime); //启动定时器 } } } //连接蓝牙 void MainWindow::on_pushButton_ConnectDev_clicked() { QString text = ui->comboBox_BluetoothDevice->currentText(); int index = text.indexOf(' '); if(index == -1) return; QBluetoothAddress address(text.left(index)); QString connect_device="开始连接蓝牙设备:\n"; connect_device+=ui->comboBox_BluetoothDevice->currentText(); QMessageBox::information(this,tr("连接提示"),connect_device); //开始连接蓝牙设备 socket->connectToService(address, QBluetoothUuid(serviceUuid) ,QIODevice::ReadWrite); } //帮助提示 void MainWindow::on_pushButton_help_clicked() { QMessageBox::information(this,tr("帮助提示"),"本软件用于HC-05/06系列串口蓝牙连接!\n" "暂时不支持BLE4.0版本蓝牙!\n" "用于发送音乐文件数据,每次发送32字节,默认100ms发送间隔时间\n" "软件作者:DS小龙哥\n" "BUG反馈:1126626497@qq.com"); } //选择音乐文件 void MainWindow::on_pushButton_select_clicked() { QString filename=QFileDialog::getOpenFileName(this,"选择发送的音乐文件",MusicFileDir,tr("*.mp3 *.MP3 *.WAV")); //filename==选择文件的绝对路径 file.setFileName(filename); ui->lineEdit_MusicName->setText(filename); Def_MusicName=filename; //保存文件名称 } //清除 void MainWindow::on_pushButton_clear_clicked() { ui->plainTextEdit->setPlainText(""); } void MainWindow::on_pushButton_StopSend_clicked() { timer->stop(); //停止定时器 } //设置发送间隔时间 void MainWindow::on_pushButton_SendTime_clicked() { QString str=ui->lineEdit_Timer->text(); int time=str.toInt(); FileSendTime=time; //保存时间 if(time=0) { QMessageBox::warning(this,"警告提示","设置错误: 发送的间隔时间最小1ms\n",QMessageBox::Ok,QMessageBox::Ok); } else ui->plainTextEdit->insertPlainText(tr("设置发送间隔时间为%1ms\n").arg(time)); } //修改每包发送的数量 void MainWindow::on_pushButton_SendFileCnt_clicked() { QString str=ui->lineEdit_SendFileCnt->text(); int cnt=str.toInt(); if(cnt>4096 || cnt=0) { QMessageBox::warning(this,"警告提示","设置错误: 每包发送的数量范围: 1~4096\n",QMessageBox::Ok,QMessageBox::Ok); } else { FileSendCnt=cnt; ui->plainTextEdit->insertPlainText(tr("发送数量设置为%1字节.\n").arg(cnt)); } } //写成功的数量 void MainWindow::bluetoothbytesWritten(qint64 byte) { count+=byte; ui->lineEdit_fileCount->setText(QString::number(count)); ui->progressBar_SendCount->setValue(count); //设置进度条的值 } 如果需要完整的代码可以从这里下载:https://download.csdn.net/download/xiaolong1126626497/18621270 ```
  • [技术干货] 这么多年, Android 虚拟机到底做了些什么?[转载]
    在 Android 操作系统中,有一个非常重要的核心部分: Android Runtime。说到这个,我相信很多人都听到过 Dalvik、ART、JIT 以及 AOT。或许好多人也和我之前一样,并不了解这些名词,以及这些名词背后做了些什么事情。本文从笔者了解到的信息,记录了 Android Runtime 中设计的一些概念,以及应用。1. 虚拟机在了解上面提到的名词之前,我们需要先知道什么是虚拟机, 它和 Android 又有什么样的渊源。在 2017 年的 Google I/O 大会上,Google 正式宣布 Kotlin-fist,Kotlin 正式成为 Android 的主要开发语言。在这之前, Android 开发者们都是使用 Java 来进行 Android 应用程序开发的。 Kotlin 和 Java 都是基于 JVM 的 CLASS格式 实现的语言,开发者通过他们编写出来的源代码都会被编译成 CLASS 文件。在 Android 中,也有相似的流程。“一次编写,到处运行” 是 Java 的宣传口号,而 JVM 正是实现此目标的关键所在。 JVM 即 Java 虚拟机,它将物理机器中的资源(CPU、内存、输入输出等)进行抽象 ,实现相同的代码,可以在不同的硬件资源上进行执行。在 Android 开发中,我们使用 Java/Kotlin 写的代码,会先编译成 class 文件,在通过 dx 工具转换成 classes.dex,运行时,在 Android 系统中,也使用了虚拟机来执行 APK 文件中的代码的。2. Dalvik在 Android 5.0 以前,Android Runtime 叫 Dalvik, 用于 Android 运行 APK 的虚拟机,开发者写的所有代码都是通过它进行执行的。DVM 的基本思想与 JVM 大体相同,但是在早期,Android 的智能手机只有很小的内存,因此 DVM 被设计用于手机端时,进行了很多独有的优化措施,其中最主要的目标就是减少内存使用。DVM是基于寄存器架构(register-based) ,JVM 是基于堆栈的架构(stack-based)。因此, Android APP 在运行的时候,不会将所有的代码都进行编译成机器码,而当代码需要运行的时候,才会去进行小范围的编译,这种方式被叫做 JIT (Just In Time compilation)。这种方式有点像解释器模式,能够节约大量的内存出来,让应用程序能够正常执行。但是,这也对应用程序运行时的性能带来一定的影响。当然,为了提高性能,DVM 也会将经常用到的代码缓存下来,降低重复编译引起的性能消耗。随着 Android 手机的发展,内存也越来越大,内存已经不再是限制。并且,各大应用厂商开发的应用程序体积也越来越大,JIT 编译带来的性能瓶颈愈来愈明显。因此, 在 Android 5.0 过后,就不再使用 Dalvik 作为 Android 的运行环境了。3. ART在 Android 5.0 及以后,Google 使用 ART 替换了 Dalvik,使用新的 Android Runtime。 ART 在设计上与 Dalvik完全不同,它引入了 AOT (Ahead of Time compilation)模式,在应用程序安装的时候,AOT 的过程是使用系统自带的 dex2oat 工具将 APK 中的 dex 文件进行编译,将编译出来的机器码放入磁盘空间中,应用程序运行的时候,直接运行,极大的提高了性能,但也引起了新的问题,应用的安装时间会变得更长,也需要更大的磁盘空间来进行存储。 还依稀记得 2015 的时候,好多人还在为安装新的应用程序而把不常用的应用卸载掉。在国内,有很多插件化框架,通过插件化框架,可以从网络上动态下载代码来实现应用程序运行时更新。 在 RePlugin 中,下载下来的插件也是 APK 文件,在 RePlugin 的代码中,就有对 dex 文件进行编译的处理逻辑,感兴趣的同学可以去看看。这种 AOT 的模式,一直持续到 Android 6 的系统,在 Android 7.0 后,又进行了一次较大的升级。4. Hybrid: AOT + JIT从前面可以看到, AOT 和 JIT 的优缺点是反过来的,因此 Google 将这两种方式进行了结合,搞了一个混合编译的方案。ART 下的 JIT 架构如下:从整个流程可以看到, 未经过编译的代码,如果是非热点代码,会直接进行解释执行,如果是热点代码,会经过 JIT 编译成机器码,再进行执行。JIT编译而来的机器码是存储到内存中的,不是在硬盘上。所以,在应用重新启动时,所有的热点代码也都需要使用 JIT 重新编译成机器码。代码在整个 ART 虚拟机中的执行流程如下图所示:在上图中,可以看到,没有经过编译的函数调用,在首次执行的时候,会直接使用 JIT 解释执行,并且会对相应的代码执行进行采样,统计出热点代码,并将统计出来的信息存放到 /data/misc/profiles/cur/0/packageName/primary.prof 下,这个文件有什么用呢? 在上图中,可以看到了 JIT 的整个运行流程,但没有 .oat 文件编译生成的流程。既然是 AOT 与 JIT 混合模式,那肯定还是有 AOT 相关的流程, AOT 编译的守护进程会在设备空闲以及充电的时候,使用生成的 primary.prof 文件进行部分代码编译。5. Baseline Profiles在前面的流程中,已经可以知道 Profile 的配置信息,可以帮助 AOT 编译,提高应用程序性能。但是,应用程序需要在本地运行一段时间后,才能统计出热点代码。而 Baseline Profiles 的原理就是让开发者自己将热点代码统计出来生成配置文件,在 APP 运行的时候,将配置写入到指定位置,帮助 AOT 编译出热点代码的机器码。更好地提高使用效率。生成 profile按照官方文档,可以使用 Jetpack 当中的 Macrobenchmark,生成 Baseline Profile 配置文件,按如下代码就可以生成了:@ExperimentalBaselineProfilesApi@RunWith(AndroidJUnit4::class)class BaselineProfileGenerator {    @get:Rule val baselineProfileRule = BaselineProfileRule()    @Test    fun startup() =        baselineProfileRule.collectBaselineProfile(packageName = "com.example.app") {            pressHome()            // This block defines the app's critical user journey. Here we are interested in            // optimizing for app startup. But you can also navigate and scroll            // through your most important UI.            startActivityAndWait()        }}PS: 需要一台 Android 9.0 及以上,开启 userdebug 或者 root 过的设备。接入 ProfileInstallerdependencies {    implementation("androidx.profileinstaller:profileinstaller:1.2.0-beta01")}PS: com.android.tools.build:gradle 需要使用 7.1.0-alpha01 及以上的版本,官方推荐使用 7.3.0-beta01 以上的版本。将第一步生成的 proflie 文件重命名为 baseline-prof.txt 放入到 src/main 目录下,与 AndroidManifest.xml 文件统计。简单几步就能使用 Baseline Profile能力了。让应用程序运行性能更好。以上为 Android 操作系统中虚拟机的进化过程,以及开发者可以在加速 APP 运行可以做的事情。————————————————版权声明:本文为CSDN博主「罗昭成-csdn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/lovecluo/article/details/125546694
  • [技术干货] 基于TCP/IP协议的物联网安卓应用开发基础教程(Android Studio开发)
    ​ 第0章 简介(文末下载工程)    大家好,在上上上期发布的教程中,教大家如何利用Android Studio开发出一款接入华为云物联网平台并调用相关API完成设备属性查询、设备命令下发等功能,那个是采用的MQTT协议借助华为云物联网平台完成数据收发,本次教程带大家完成一款基于TCP/IP协议的物联网安卓应用开发,实现TCP连接与数据收发,下一期将在此APP上进行修改,并配置ESP8266接入APP完成内网控制物联网设备、查看设备属性信息等功能,首先还是给大家先看一下APP的最终效果:第1章 提前准备一、Android Studio IDE(※确保模拟器网络功能正常或使用真机※)二、 网络调试助手(下载地址https://www.pcsoft.com.cn/soft/194484.html)第2章 详细步骤一、 新建工程二、 TCP服务1、 新建TCP服务2、 TCP服务功能代码补充(1) 导入可能需要的类:import android.app.Service; import android.content.Intent; import android.os.IBinder; ​import android.os.Looper; import java.io.IOException; import java.io.InputStream ;import java.io.PrintStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress;(2) 修改服务代码(可左右滑动)public class TCPService extends Service { public static Socket socket; public static PrintStream output; boolean conn = false; String ip="192.168.1.15"; String port="8087"; public TCPService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { MyThread myThread = new MyThread(); new Thread(myThread).start(); //********在最终应用时取消注释下行,用于动态改变的IP和端口的连接******** //ip=intent.getStringExtra("ipaddress"); //port=intent.getStringExtra("port"); //System.out.println("新的TCP连接,新的IP:"+intent.getStringExtra("ipaddress")+"新的端口号:"+intent.getStringExtra("port")); //********可在其他activity中设置IP和端口号通过上面的方式进行获取******** return super.onStartCommand(intent, flags, startId); return super.onStartCommand(intent, flags, startId); } //socket连接线程 class MyThread implements Runnable{ @Override public void run(){ try { socket = new Socket(); SocketAddress socAddress = new InetSocketAddress(ip ,Integer.valueOf(port)); try{ socket.connect(socAddress, 5000); } catch (Exception e) { e.printStackTrace(); System.out.println("连接失败"); } if(socket.isConnected())System.out.println("连接成功"); InputStream inputstream = socket.getInputStream(); /* 获取输出流 */ output = new PrintStream(socket.getOutputStream(), true, "utf-8"); conn = true; byte buffer[] = new byte[100];//接收数组的长度 int len2 ; String receiveData; //非阻塞式连接 while(conn){ //接收网络数据 if( (len2 = inputstream.read(buffer)) != -1){ receiveData = new String(buffer, 0, len2); Intent CMDintent = new Intent(); CMDintent.setAction("com.example.communication.data"); CMDintent.putExtra("data", buffer);//buffer为数组,receivedata为文本 sendBroadcast(CMDintent); }else{ break; } } output.close(); socket.close(); Looper.loop(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); // Looper.prepare(); } } }; //发送方法((可以把参数改成Byte[]): public static void send(final byte[] arr) { new Thread(new Runnable() { public void run() { if (socket.isConnected()) { try { output.write(arr); } catch (IOException e) { e.printStackTrace(); } } } }).start(); }3、 MainActivity中启动服务(1) 导入可能需要的类  import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast;(2) 启动服务Intent intent=new Intent(this,TCPService.class); startService(intent);(3) 加入联网权限    在AndroidMainfest.xml文件中加入下面这句:<uses-permission android:name="android.permission.INTERNET" />4、 打开TCP Service    打开网络调试助手(1) 协议类型:“TCP Server”(2) 本机地址:不做修改,并且在APP的TCPService中的IP地址要与此一致(3) 端口选择:根据需要选择合适的端口,同样在APP的TCPService中的端口号要与此一致基本补充(4)点击打开5、 运行APP此时我们可以看到APP已经连接上了网络调试助手三、 界面配置​四、 手动输入IP与端口号进行连接 1.实现过程    为登陆按钮的ImageButton添加事件,获取IP地址和端口号输入框的值后启动连接,相比较于上一步的初始化连接测试,这次的IP地址和端口号是变化的,我们这使用putExtra方式将ip和端口号发送出去,在TCPService中接收  2.修改启动服务语句    此处我们将一开始测试的程序语句Intent intent=new Intent(this,TCPService.class); startService(intent);    改为Intent intent = new Intent(this, TCPService.class); intent.putExtra("ipaddress", ip); intent.putExtra("port", port); startService(intent);  3.添加到登陆按钮的事件代码中4.在TCPService.java中接收IP和端口号,并启动新的连接五、 数据的收发1. 数据的接收(1) 广播接收器我们通过广播接收器进行数据接收,首先在MainActivity中新建一个接收器:private class cmdReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { byte[] receive=intent.getByteArrayExtra("data"); System.out.println("接收到数据:"+recieveData); } }(2)注册接收器public cmdReceiver cmdReceiver; cmdReceiver =new cmdReceiver(); IntentFilter intentFilter=new IntentFilter(); intentFilter.addAction("com.example.communication.data"); registerReceiver(cmdReceiver,intentFilter);(3)网络调试助手进行数据发送由于我们在TCPService中定义了接收数据格式为100字节,大家根据自己需要进行调整。​(4)接收处理我们在广播接收器中读取接收的数据并显示:​2.数据发送    发送的时候我们可以调用TCPService.send(buff);方法,其中buff为字节数组,例如://发送十六进制数据 byte[] buff = {0x00,0x01,0x02,0x03}; TCPService.send(buff); //发送字符串 String str=”FUNIOT is very fun”; byte[] buffstr = str.getBytes(); TCPService.send(buffstr);五、 结语接下来我们就可以连接我们的设备,使用APP进行通信,完成数据上报或命令控制等其功能,关键代码已经在文章中给出,相信大家已经能够实现全部的功能,如果需要上述文章Android  Studio的工程文件,在公众号“IOT趣制作”回复关键字“TCP基础应用”即可。​   
  • [技术干货] Flutter 小技巧之有趣的动画技巧[转载]
    动画效果事实上 Flutter 里实现类似的动画效果很简单,甚至不需要自定义布局,只需要通过官方的内置控件就可以轻松实现。首先我们需要使用 AnimatedPositioned 和 AnimatedContainer :AnimatedPositioned 用于在 Stack 里实现位移动画效果AnimatedContainer 用于实现大小变化的动画效果接着我们定义一个 PositionItem ,将 AnimatedPositioned 和 AnimatedContainer 嵌套在一起,并且通过 PositionedItemData 用于改变它们的位置和大小。class PositionItem extends StatelessWidget {  final PositionedItemData data;  final Widget child;  const PositionItem(this.data, {required this.child});  @override  Widget build(BuildContext context) {    return new AnimatedPositioned(      duration: Duration(seconds: 1),      curve: Curves.fastOutSlowIn,      child: new AnimatedContainer(        duration: Duration(seconds: 1),        curve: Curves.fastOutSlowIn,        width: data.width,        height: data.height,        child: child,      ),      left: data.left,      top: data.top,    );  }}class PositionedItemData {  final double left;  final double top;  final double width;  final double height;  PositionedItemData({    required this.left,    required this.top,    required this.width,    required this.height,  });}之后我们只需要把 PositionItem 放到通过 Stack 下,然后通过 LayoutBuilder 获得 parent 的大小,根据 PositionedItemData 调整 PositionItem 的位置和大小,就可以轻松实现开始的动画效果。child: LayoutBuilder(  builder: (_, con) {    var f = getIndexPosition(currentIndex % 3, con.biggest);    var s = getIndexPosition((currentIndex + 1) % 3, con.biggest);    var t = getIndexPosition((currentIndex + 2) % 3, con.biggest);    return Stack(      fit: StackFit.expand,      children: [        PositionItem(f,            child: InkWell(              onTap: () {                print("red");              },              child: Container(color: Colors.redAccent),            )),        PositionItem(s,            child: InkWell(              onTap: () {                print("green");              },              child: Container(color: Colors.greenAccent),            )),        PositionItem(t,            child: InkWell(              onTap: () {                print("yello");              },              child: Container(color: Colors.yellowAccent),            )),      ],    );  },),如下图所示,只需要每次切换对应的 index ,便可以调整对应 Item 的大小和位置发生变化,从而触发 AnimatedPositioned 和 AnimatedContainer 产生动画效果,达到类似开始时动图的动画效果。计算大小    效果    完整代码可见: https://github.com/CarGuo/gsy_flutter_demo/blob/master/lib/widget/anim_switch_layout_demo_page.dart如果你对于实现原理没兴趣,那到这里就可以结束了,通过上面你已经知道了一个小技巧:改变 AnimatedPositioned 和 AnimatedContainer 的任意参数,就可以让它们产生动画效果,而它们的参数和 Positioned 与 Container 一模一样,所以使用起来可以无缝替换 Positioned 与 Container ,只需要简单配置额外的 duration 等参数。进阶学习那 AnimatedPositioned 和 AnimatedContainer 是如何实现动画效果 ?这里就要介绍一个抽象父类 ImplicitlyAnimatedWidget 。几乎所有 Animated 开头的控件都是继承于它,既然是用于动画 ,那么 ImplicitlyAnimatedWidget 就肯定是一个 StatefulWidget ,那么不出意外,它的实现逻辑主要在于 ImplicitlyAnimatedWidgetState ,而我们后续也会通过它来展开。首先我们回顾一下,一般在 Flutter 使用动画需要什么:AnimationController : 用于控制动画启动、暂停TickerProvider : 用于创建 AnimationController 所需的 vsync 参数,一般最常使用 SingleTickerProviderStateMixinAnimation : 用于处理动画的 value ,例如常见的 CurvedAnimation接收动画的对象:例如 FadeTransition简单来说,Flutter 里的动画是从 Ticker 开始,当我们在 State 里 with TickerProviderStateMixin 之后,就代表了具备执行动画的能力:每次 Flutter 在绘制帧的时候,Ticker 就会同步到执行 AnimationController 里的 _tick 方法,然后执行 notifyListeners ,改变 Animation 的 value,从而触发 State 的 setState 或者 RenderObject 的 markNeedsPaint 更新界面。举个例子,如下代码所示,可以看到实现一个简单动画效果所需的代码并不少,而且这部分代码重复度很高,所以针对这部分逻辑,官方提供了 ImplicitlyAnimatedWidget 模版。class _AnimatedOpacityState extends State<AnimatedOpacity>    with TickerProviderStateMixin {  late final AnimationController _controller = AnimationController(    duration: const Duration(seconds: 2),    vsync: this,  )..repeat(reverse: true);  late final Animation<double> _animation = CurvedAnimation(    parent: _controller,    curve: Curves.easeIn,  );  @override  void dispose() {    _controller.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    return Container(      color: Colors.white,      child: FadeTransition(        opacity: _animation,        child: const Padding(padding: EdgeInsets.all(8), child: FlutterLogo()),      ),    );  }}例如上面的 Fade 动画,换成 ImplicitlyAnimatedWidgetState 只需要实现 forEachTween 方法和 didUpdateTweens 方法即可,而不再需要关心 AnimationController 和 CurvedAnimation 等相关内容。class _AnimatedOpacityState extends ImplicitlyAnimatedWidgetState<AnimatedOpacity> {  Tween<double>? _opacity;  late Animation<double> _opacityAnimation;  @override  void forEachTween(TweenVisitor<dynamic> visitor) {    _opacity = visitor(_opacity, widget.opacity, (dynamic value) => Tween<double>(begin: value as double)) as Tween<double>?;  }  @override  void didUpdateTweens() {    _opacityAnimation = animation.drive(_opacity!);  }  @override  Widget build(BuildContext context) {    return FadeTransition(      opacity: _opacityAnimation,      alwaysIncludeSemantics: widget.alwaysIncludeSemantics,      child: widget.child,    );  }}那 ImplicitlyAnimatedWidgetState 是如何做到改变 opacity 就触发动画?关键还是在于实现的 forEachTween :当 opacity 被更新时,forEachTween 会被调用,这时候内部会通过 _shouldAnimateTween 判断值是否更改,如果目标值已更改,就执行基类里的 AnimationController.forward 开始动画。这里补充一个内容:FadeTransition 内部会对 _opacityAnimation 添加兼容,当 AnimationController 开始执行动画的时候,就会触发 _opacityAnimation 的监听,从而执行 markNeedsPaint ,而如下图所示, markNeedsPaint 最终会触发 RenderObject 的重绘。所以到这里,我们知道了:通过继承 ImplicitlyAnimatedWidget 和 ImplicitlyAnimatedWidgetState 我们可以更方便实现一些动画效果,Flutter 里的很多默认动画效果都是通过它实现。另外 ImplicitlyAnimatedWidget 模版里,除了 ImplicitlyAnimatedWidgetState ,官方还提供了另外一个子类 AnimatedWidgetBaseState。事实上 Flutter 里我们常用的 Animated 都是通过 ImplicitlyAnimatedWidget 模版实现,如下图所示是 Flutter 里常见的 Animated 分别继承的 State :ImplicitlyAnimatedWidgetState    AnimatedWidgetBaseState    关于这两个 State 的区别,简单来说可以理解为:ImplicitlyAnimatedWidgetState 里主要是配合各类 *Transition 控件使用,比如: AnimatedOpacity里使用了 FadeTransition 、AnimatedScale 里使用了 ScaleTransition ,因为 ImplicitlyAnimatedWidgetState 里没有使用 setState,而是通过触发 RenderObject 的 markNeedsPaint 更新界面。AnimatedWidgetBaseState 在原本 ImplicitlyAnimatedWidgetState 的基础上增加了自动 setState 的监听,所以可以做一些更灵活的动画,比如前面我们用过的 AnimatedPositioned 和 AnimatedContainer 。其实 AnimatedContainer 本身就是一个很具备代表性的实现,如果你去看它的源码,就可以看到它的实现很简单,只需要在 forEachTween 里实现参数对应的 Tween 实现即可。例如前面我们改变的 width 和 height ,其实就是改变了Container 的 BoxConstraints ,所以对应的实现也就是 BoxConstraintsTween ,而 BoxConstraintsTween 继承了 Tween ,主要是实现了 Tween 的 lerp 方法。在 Flutter 里 lerp 方法是用于实现插值:例如就是在动画过程中,在 beigin 和 end 两个 BoxConstraint 之间进行线性插值,其中 t 是动画时钟值下的变化值,例如:计算出 100x100 到 200x200 大小的过程中需要的一些中间过程的尺寸。如下代码所示,通过继承 AnimatedWidgetBaseState ,然后利用 ColorTween 的 lerp ,就可以很快实现如下文字的渐变效果。代码    效果    总结最后总结一下,本篇主要介绍了:利用 AnimatedPositioned 和 AnimatedContainer 快速实现切换动画效果介绍 ImplicitlyAnimatedWidget 和如何使用 ImplicitlyAnimatedWidgetState / AnimatedWidgetBaseState 简化实现动画的需求,并且快速实现自定义动画。那么,你还有知道什么使用 Flutter 动画的小技巧吗?原文链接:https://blog.csdn.net/ZuoYueLiang/article/details/125365460
  • [问题求助] 鲲鹏搭建Android模拟器
    【功能模块】【操作步骤&问题现象】1、问题描述: https://support.huaweicloud.com/dpmg-kunpengcps/kunpengandroid_CentOS_03_0003.html,华为云官方文档,根据该文档,我应该购买哪一款华为云服务器?2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [技术干货] 10天学会flutter DAY10 flutter 玩转 动画与打包[转载]
    动画​ Flutter中的动画系统基于Animation对象的,和之前的手势不同,它不是一个Widget,这是因为Animation对象本身和UI渲染没有任何关系。Animation是一个抽象类,就相当于一个定时器,它用于保存动画的插值和状态,并执行数值的变化。widget可以在build函数中读取Animation对象的当前值, 并且可以监听动画的状态改变。AnimationController​ AnimationController用于控制动画,它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。AnimationController会在动画的每一帧,就会生成一个新的值。默认情况下,AnimationController在给定的时间段内线性的生成从0.0到1.0(默认区间)的数字。AnimationController controller = AnimationController(  duration: const Duration(milliseconds: 2000), //动画时间 lowerBound: 10.0,    //生成数字的区间  upperBound: 20.0,    //10.0 - 20.0 vsync: this  //TickerProvider 动画驱动器提供者);Ticker​ Ticker的作用是添加屏幕刷新回调,每次屏幕刷新都会调用TickerCallback。使用Ticker来驱动动画会防止屏幕外动画(动画的UI不在当前屏幕时,如锁屏时)消耗不必要的资源。因为Flutter中屏幕刷新时会通知Ticker,锁屏后屏幕会停止刷新,所以Ticker就不会再触发。最简单的做法为将SingleTickerProviderStateMixin添加到State的定义中。import 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  bool forward = true;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      lowerBound: 10.0,      upperBound: 100.0,      // 提供 vsync 最简单的方式,就是直接混入 SingleTickerProviderStateMixin      // 如果有多个AnimationController,则使用TickerProviderStateMixin。      vsync: this,    );       //状态修改监听    controller      ..addStatusListener((AnimationStatus status) {        debugPrint("状态:$status");      })      ..addListener(() {        setState(() => {});      });    debugPrint("controller.value:${controller.value}");  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        Container(          width: controller.value,          height: controller.value,          color: Colors.blue,        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}动画状态监听:在forword结束之后状态为completed。在reverse结束之后状态为dismissedTween​ 默认情况下,AnimationController对象值为:double类型,范围是0.0到1.0 。如果我们需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。要使用Tween对象,需要调用其animate()方法,然后传入一个控制器对象,同时动画过程中产生的数值由Tween的lerp方法决定。import 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  bool forward = true;  Tween<Color> tween;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //使用Color    tween = ColorTween(begin: Colors.blue, end: Colors.yellow);    //添加动画值修改监听    tween.animate(controller)..addListener(() => setState(() {}));  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        Container(          width: 100,          height: 100,          //获取动画当前值          color: tween.evaluate(controller),        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}Curve​ 动画过程默认是线性的(匀速),如果需要非线形的,比如:加速的或者先加速后减速等。Flutter中可以通过Curve(曲线)来描述动画过程。import 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  Animation<double> animation;  bool forward = true;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //弹性    animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);    //使用Color    animation = Tween(begin: 10.0, end: 100.0).animate(animation)      ..addListener(() {        setState(() => {});      });  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        Container(          //不需要转换          width: animation.value,          height: animation.value,          //获取动画当前值          color: Colors.blue,        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}AnimatedWidget​ 通过上面的学习我们能够感受到Animation对象本身和UI渲染没有任何关系。而通过addListener()和setState() 来更新UI这一步其实是通用的,如果每个动画中都加这么一句是比较繁琐的。AnimatedWidget类封装了调用setState()的细节,简单来说就是自动调用setState()。​ Flutter中已经封装了很多动画,比如对widget进行缩放,可以直接使用ScaleTransitionimport 'package:flutter/material.dart';void main() => runApp(AnimationApp());class AnimationApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: "animation",      home: Scaffold(        appBar: AppBar(          title: Text('animation'),        ),        body: AnimWidget(),      ),    );  }}// 动画是有状态的class AnimWidget extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return _AnimWidgetState();  }}class _AnimWidgetState extends State<AnimWidget>    with SingleTickerProviderStateMixin {  AnimationController controller;  Animation<double> animation;  bool forward = true;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //弹性    animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);    //使用Color    animation = Tween(begin: 10.0, end: 100.0).animate(animation);  }  @override  Widget build(BuildContext context) {    return Column(      children: <Widget>[        ScaleTransition(          child:  Container(            width: 100,            height: 100,            color: Colors.blue,          ),          scale: controller,        ),        RaisedButton(          child: Text("播放"),          onPressed: () {            if (forward) {              controller.forward();            } else {              controller.reverse();            }            forward = !forward;          },        ),        RaisedButton(          child: Text("停止"),          onPressed: () {            controller.stop();          },        )      ],    );  }}Hero动画​ Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画。import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return new MaterialApp(      title: 'Flutter Demo',      home: Scaffold(          appBar: AppBar(            title: Text("主页"),          ),          body: Route1()),    );  }}// 路由Aclass Route1 extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Container(      alignment: Alignment.topCenter,      child: InkWell(        child: Hero(          tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同          child: CircleAvatar(            backgroundImage: AssetImage(              "assets/banner.jpeg",            ),          ),        ),        onTap: () {          Navigator.push(context, MaterialPageRoute(builder: (_) {            return Route2();          }));        },      ),    );  }}class Route2 extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Center(      child: Hero(          tag: "avatar", //唯一标记,前后两个路由页Hero的tag必须相同          child: Image.asset("assets/banner.jpeg")),    );  }}组合动画有些时候我们可能会需要执行一个动画序列执行一些复杂的动画。import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return new MaterialApp(      title: 'Flutter Demo',      home: Route(),    );  }}class Route extends StatefulWidget {  @override  State<StatefulWidget> createState() {    return RouteState();  }}class RouteState extends State<Route> with SingleTickerProviderStateMixin {  Animation<Color> color;  Animation<double> width;  AnimationController controller;  @override  void initState() {    super.initState();    controller = AnimationController(      // 动画的时长      duration: Duration(milliseconds: 2000),      // 提供 vsync 最简单的方式,就是直接继承 SingleTickerProviderStateMixin      vsync: this,    );    //高度动画    width = Tween<double>(      begin: 100.0,      end: 300.0,    ).animate(      CurvedAnimation(        parent: controller,        curve: Interval(          //间隔,前60%的动画时间 1200ms执行高度变化          0.0, 0.6,        ),      ),    );    color = ColorTween(      begin: Colors.green,      end: Colors.red,    ).animate(      CurvedAnimation(        parent: controller,        curve: Interval(          0.6, 1.0, //高度变化完成后 800ms 执行颜色编码        ),      ),    );  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("主页"),      ),      body: InkWell(        ///1、不用显式的去添加帧监听器,再调用setState()        ///2、缩小动画构建的范围,如果没有builder,setState()将会在父widget上下文调用,导致父widget的build方法重新调用,现在只会导致动画widget的build重新调用        child: AnimatedBuilder(            animation: controller,            builder: (context, child) {              return Container(                color: color.value,                width: width.value,                height: 100.0,              );            }),        onTap: () {          controller.forward().whenCompleteOrCancel(() => controller.reverse());        },      ),    );  }}打包​ Flutter在打Release包时候回使用AOT,因此在对一个Flutter测试时候务必使用Release来进行测试。打包命令:flutter build apk 。当然我们需要打包时,还需要配置一些比如签名的内容。配置这些内容和普通Android工程没有区别,都是在build.gradle中进行,只是Flutter工程AS没有提供GUI。​ 在Flutter工程的android/app下面的build.gradle可以修改包名、版本等信息,这就不用多说了。获得签名文件之后,将它复制到flutter的android目录:​ 然后在app的build.gradle中配置:signingConfigs {        release {            keyAlias 'enjoy'            keyPassword '123456'            // 因为是放到父级的根目录,使用rootProject            // 如果放在这个build.gradle的同级,直接使用file            storeFile rootProject.file('enjoy.jks')            storePassword '123456'        }    }    buildTypes {        release {            // TODO: Add your own signing config for the release build.            // Signing with the debug keys for now, so `flutter run --release` works.            signingConfig signingConfigs.release        }    }饼图https://github.com/google/chartsStack布局中的fit属性与Image的fit类似,表示内容的扩充情况。默认为StackFit.loose表示Stack与内容一样大。如果设置为StackFit.passthrough则表示Stack父Widget的约束会传给Stack内部非Positioned的子Widget。效果如代码中的StackFit.dart原文链接:https://blog.csdn.net/u010755471/article/details/124691809
  • [行业资讯] 谷歌I/O大会上的这8款新品,个人用户最为关注
    导语:谷歌在I/O开发者大会上发布了多款创新的硬件产品和软件服务。田姗姗 | 作者 砺石科技 | 出品5月12日,一年一度的谷歌I/O开发者大会在美国旧金山山景城举行。这是近年来谷歌内容最丰富的一次发布会,吸引了全球广大科技爱好者们的关注。今年大会采用了线下+线上的形式,现场有少数观众。在2个多小时的大会里,谷歌CEO皮查伊介绍了包括AR眼镜、平板电脑、降噪耳机等5款硬件产品,十多个涉及底层技术、AI应用、安卓系统等多个领域的更新。其中,砺石科技总结了8款个人用户最关注的产品与新功能。1-更实用的谷歌AR眼镜皮查伊在大会上演示了一段2分钟的视频,介绍了一个正在研发中的谷歌AR眼镜。视频里的主角是一对母女,母亲只会说普通话,而女儿说的是英文。母亲戴上AR眼镜后,AR眼镜便可以将女儿说的话实时记录并翻译成中文,以字幕的方式出现在母亲的视野中。另外,这款AR眼镜还能够翻译美国手语 (ASL)。与2013年谷歌发布的第一款AR眼镜相比,这款AR眼镜更加实用。皮查伊在介绍设计这款AR眼镜的实用场景时说,“语言是彼此联系的基础。然而,理解说不同语言的人,或者如果你是聋子或听力障碍者,试图跟上对话可能是一个真正的挑战”,“让我们看看我们在翻译和转录方面取得的进步,并将我们的早期原型产品之一交付给用户使用时会发生什么。”目前该AR眼镜的更多产品功能、定价以及发布日期等信息尚未确定。2-回归的平板电脑谷歌将于2023年推出一款平板电脑新产品Pixel Tablet。这款Pixel平板电脑将搭载 Android系统,并使用谷歌定制的、已在其智能手机Pixel 6中使用的Tensor处理器。目前,谷歌布局了智能手机、平板电脑、智能降噪耳机Pixel Buds与智能手表Pixel Watch等多款智能硬件设备,已经形成了一个多终端互联的智能硬件生态体系。3-智能手机上新谷歌在发布会上推出了智能手机新产品Pixel 6A,配备1200万像素主摄像头和广角摄像头,搭载与Pixel 6和Pixel 6 Pro相同的Tensor处理器,预装Android 13手机操作系统。售价449美元,将于7月21日开启预购,7月28日正式发售。同时,谷歌还预告了下一代旗舰手机Pixel 7和Pixel 7 Pro,但并没有公布价格和发布日期等细节。据悉,Pixel 7和Pixel 7 Pro将运行Android 13系统,并使用第二代谷歌定制的Tensor芯片。4-智能手表谷歌宣布今年秋季将发布被外界期待已久的智能手表产品Pixel Watch。这款手表可以在Google的Wear OS智能手表系统上运行,并包含一些Fitbit健康追踪功能。谷歌没有公布价格,但表示其将作为高端产品出售。5-高性价比的蓝牙降噪耳机今年谷歌推出了TWS(真无线蓝牙耳机)产品Pixel Buds Pro,其内部采用谷歌开发的6核心音频处理芯片,售价为199美元,比苹果同类型的蓝牙降噪耳机便宜了近一半。该产品最大特点在于支持了主动降噪功能,并搭载了音频处理系统;续航方面,单次使用时间为11小时,开启降噪时为7小时。6-新加入24种语言的谷歌翻译为了更好地服务全球的用户,谷歌在其翻译功能中新增了24种语言,包括一些少数种族或者原住民的语言,比如一种美洲土著使用的语言。谷歌还将机器学习引入了翻译功能,摆脱传统的对照式翻译的模式。7-更注重隐私保护Android 13谷歌公布了Android 13 Beta 2第二个公开测试版,增加了多项更注重用户隐私保护的新功能和用户控件。首先,在Android 13 Beta 2上,用户可以更好地控制共享的个人信息,以及更详细地控制应用程序可以访问的文件。用户还可以控制两个新类别的访问权限:照片和视频,以及音乐和音频。Android 13还推出了一个新选项:让用户可以选择要授予应用访问权限的确切照片或视频,不必共享整个媒体库。在Android 13 Beta 2系统中,语言偏好也可以进行个性化配置。用户将能够为每个应用程序选择不同的语言。例如,用户可能在社交媒体中喜欢使用一种语言,但经常使用另一种语言进行银行业务,Android 13可以让你像在现实生活中一样流畅地切换不同的语言。8-沉浸式地图谷歌推出了一种新的地图模式:沉浸式地图,应用了3D航拍、AI智能等技术,以此来确保导航、搜索等功能的准确性。在沉浸式地图模式下,用户在去一个地方之前就可以更真实地看清目的地的实时状态,可以从上方俯瞰全景,也可以下降到具体的建筑和街道察看细节,还可以实时查看地段的繁忙度和交通信息。据悉,沉浸式地图模式将在今年推出,并支持Android和iOS,但目前只适用于旧金山、纽约、洛杉矶、伦敦和东京等少数地区,更多地区将在后续推出。 另外,谷歌还公布了其地图应用的最新数据:已在全球范围内绘制超16亿座建筑物和6000万公里的道路。— END —       原文标题 : 谷歌I/O大会上的这8款新品,个人用户最为关注
  • [行业资讯] 从懂互联网到懂用户,谷歌这次都押了哪些宝?
    今年的 Google I/O 大会,亮点有哪些?谷歌 I/O 大会如约而至。北京时间 5 月 12 日凌晨1点,谷歌 I/O 2022 大会开幕式上,谷歌 CEO Sundar Pichai 发表了长达 2 小时的以“知识和计算”为关键词的主题演讲。这次演讲在勾勒谷歌长期发展愿景的同时,也在某种程度上描绘后疫情时代的互联网技术的演进方向。搜索再定义:Anyway、AnywhereGoogle 提出「Search reimagined. Any way and anywhere」(重新构想搜索。以任何方式和任何地方)的愿景。可以这样解读:搜索正在成为一个多传感器、多设备的命题,它既能理解“谁在搜索”,也能理解“他们真正在寻找什么”。它还将搜索体验扩展到了问题和答案之外。它让安卓系统更加关注环境和内容,这样手机就可以根据用户实现“千人千面”;它强调自然的互动,这样你就可以在不死记硬背命令的情况下获得信息;它正在构建所需的硬件生态系统,使所有这些设备在任何地方都能工作,并使软件与之匹配。谷歌于上个月推出了 Multisearch 功能,即当你在日常生活中,遇到根本就不认识,或者无法描述的东西时,可以直接以拍照和提问的方式在谷歌应用中进行搜索。此外还有“Scene Exploring”功能,将允许用户使用手机相机功能直接扫描超市的货物,然后查找到自己想要的产品。图源:谷歌谷歌还扩展了其多搜索功能,可以沿多个维度进行搜索。例如,你可以给谷歌一张你正在寻找的特定类型菜肴的图片,然后问它你在附近哪里可以找到。AR:真实世界里的理解与被理解信息技术领域有了一个新战线,增强现实技术(AR),它拥有推动现有技术继续发展的潜质。这个潜质不是技术本身,而是让我们更加关注这个真实的世界。谷歌提出了这样一种理念:我们基于现实世界进行创作设计,绝不脱离现实。AR 恰恰是能够帮助我们实现这种设计理念的新方法。Google Glass V2 图源:谷歌以语言为例,语言是人与人之间沟通的基础。然而,如果对方讲着另一种语言,或者会话的一方有听觉障碍时,沟通就变得困难重重。谷歌将最新技术应用在翻译和语言转录中,其在早期测试原型中呈现出来的效果赢得了现场观众一片掌声。:遍地开花“料”十足与通常情况一样,此次的谷歌 I/O 有很多人工智能方面的“料”。首当其冲的是宣布公开 LaMDA 2,Google 目前打造的最先进的对话 AI 模型,较之前版本相比,减少了不准确或冒犯性的回复,显著提升了对话质量。不但如此,谷歌正在将这项技术应用到搜索和其他产品中。再有,YouTube 视频可以自动生成章节和转录。通过 DeepMind 的多模式技术,YouTube 视频以更高的准确性自动生成章节。也能使用语音识别模型来转录视频。再比如,Google Docs 引入自动摘要功能。这一功能的落地,也标志着自然语言处理的一大飞跃。通过机器学习模型,Google Docs 可以自动解析单词并提炼出要点,且只需几秒的时间。值得一提的是,谷歌宣布了迄今以来自研的最大规模的语言模型 PaLM,该模型基于 5400 亿参数训练而成。将这种大规模模型与一种名为“思维提示链(chain-of- thought)”的新技术结合起来时,可以将需要多步解决的问题转化为一系列的中间步骤来处理,效果令人非常满意。思维提示链   图源:谷歌还有一些与人工智能相关的小插曲。谷歌宣布,其自动生成的翻译将出现在手机上的 YouTube上,你只需看看 Nest Hub Max,就可以开始与助手通话;你的手机也可以看到一个装满巧克力棒的架子,根据你要找的东西为你挑一个。如谷歌所描述的,“为你周围的世界提供了一个超级强大的Ctrl-F”。图源:谷歌Andriod 13:完善也是一种强大谷歌重新审视了 Andriod 13的计划,下一个版本的移动操作系统似乎在 Andriod 12 中引入的理念上走得更远。谷歌正在向更多的位置添加内容主题,允许用户将应用程序设置为使用不同的语言,并添加了一些安全和隐私功能。实用功能方面,值得注意的是 Android 13 支持运行 Windows了,据称已经有人在搭载了Android 13 开发者预览版的谷歌 Pixel 6上成功运行了 Win11 Arm 虚拟机,并且实现近乎原生的性能。除此之外,Android 13 还支持在锁屏界面添加 QR 扫描器、点击流转媒体、新增系统照片选择器等多项新的功能,并且还可以为单个 App 指定语言等,这些都是非常实用的。安卓系统现在内置了对标准的支持,这将使安装和控制新设备变得更加容易。谷歌扩展了对其向其他设备发送音频和视频的 Cast 协议的支持,并改进了其快速配对服务,以方便连接蓝牙设备。整体上看,Andriod 13 与 12 相比,没有太大的改动,而是沿着其理念继续在许多细节上做了进一步的优化处理,使安卓的生态更加完善与强大。新的测试版已经发布,安卓迷们可以尝鲜了。图源:谷歌
  • [行业资讯] Android 13 将默认采用华为开发的只读文件系统
    近日,据Esper的Mishaal Rahman曝料,Android 13正计划采用华为的EROFS(增强型只读文件系统)作为只读分区的默认文件系统。EROFS由华为开发,旨在为只读文件提供比其他只读 Linux 文件系统更好的性能和存储。此外,谷歌计划对Android 13启动设备的所有只读分区强制使用EROFS。转载于CSDN微信公众号
  • [行业资讯] 苹果官宣iPhone 14,大家不用再猜了!
    最近一段时间,网上对于iPhone 14系列机型的讨论猜测那是相当热烈,相关消息上热搜简直不要太轻松,就在前天,iPhone 14有望实现息屏显示功能还上了热搜榜第一名,这项苹果“极为先进”的功能,果粉应该是期待已久了。对于这么一个在安卓手机上已经烂大街,甚至更早的诺基亚手机上就已经存在的功能,苹果在2022年才有可能推出,简直让人无语到了极点,虽然还只是猜测,可但凡苹果还要点脸面,息屏显示功能就不应该再拖延了。不管之前之前对于iPhone 14系列机型爆料到了什么程度,始终都处在猜测阶段,只有等到苹果官方自己放消息,某些信息才能算是得到正式确认,近日,苹果泰国官方放出了一则时长15秒的apple pay宣传视频,其中透露了一项很重要的内容,iPhone 14 Pro/Max的感叹号屏被实锤了。小智查了下账号简介,确实是以Apple Inc.结尾,确认是苹果官方账号没什么问题,苹果通过影响力较小的泰国媒体渠道来放风,这也符合苹果一贯以来的做事风格,一些新消息不会第一时间交给大媒体,总是将影响力控制在一定范围内,通过普通用户转发来将消息进行有限的传播,即达到目的,又不过分张扬。这应该算是自刘海屏首次在iPhone X上出现后,苹果手机的最大改变了,和刘海屏相比,这一大一小两个挖孔确实占比面积要小一些,但多出的屏幕空间似乎也难以得到有效利用,要是苹果没有像当年强制要求适配刘海屏一样要求APP开发者适配双挖孔屏,那基本能肯定这就是一种过渡性设计风格,为的就是在将镜头全部放入屏下之前做技术过渡。还有另一个细节问题值得思考,在这则苹果泰国官方宣传视频中,只出现感叹号屏的iPhone 14 Pro和apple watch,两款标准版并未现身,个人猜测应该是和去年一样,没必要拿出来说。外观不用再猜,接下来更多考虑应该是功能配置,有消息称iPhone 14 Pro/Max上的高配屏刷新率可被控制在1Hz-120Hz之间(上一代是10Hz-120Hz),以便更精确控制电量使用,而标准版依然无缘120Hz刷新率。至于丑不丑的问题,也没得算了,双挖孔实锤,不喜欢的就只能去买标准版了。       原文标题 : 苹果官宣iPhone 14,大家不用再猜了!
  • [行业资讯] Rel-17:5G物联网普及即将迈入重要一步
     今年3月,Rel-17完成了功能冻结,预计今年6月,Rel-17 ASN.1冻结,届时,Rel-17标准真正完成。  根据终端从高到低支持的特性和功能,5G终端类型可划分为支持最高性能的eMBB/URLLC;支持较低复杂度和功耗的RedCap;以及支持最低复杂度和对时延不敏感的eMTC/NB-IoT。  其中,Rel-17 版本引入:对轻量级5G终端(RedCap)的支持,对eMTC/NB-IoT的功能增强,以及非地面通信网络支持的5G物联网。  (1)引入RedCap  Rel-17通过引入RedCap,让5G NR普及至更低复杂度的物联网终端,用以支持诸如工业传感器、监控摄像头、智能电网相关设备、高端可穿戴设备、高端物流跟踪设备,满足较低的复杂度、低成本和功耗等需求。  在带宽方面,针对RedCap,面向Sub7GHz,Rel-17将带宽缩窄至20MHz;面向毫米波,Rel-17 RedCap在毫米波频段下可支持100MHz带宽。  在天线方面,传统的5G NR终端通常需要配备四根接收天线,而Rel-17将RedCap终端的接收天线数缩减至一根或两根。  在节能方面,Rel-17可支持RedCap终端半双工工作状态,同时,还支持更低的发射功率和增强的节电模式,比如,在空闲状态,RedCap终端可在1万秒时间内不进行网络连接;即使在积极传送状态下,RedCap终端也可以做到10秒钟不与基站连接,从而减少不必要的能耗。  在信令开销方面,RedCap还可以支持有限移动性和切换,终端不必每时每刻与基站保持连接,因此,不需要针对终端的移动做过多测量,网络切换速度也可以慢一点,从而减少信令开销以及更好地优化资源管理。  (2)eMTC/NB-IoT增强  eMTC/NB-IoT作为4G时代支持的蜂窝物联网标准,在5G框架下得到了增强。  相比RedCap,eMTC/NB-IoT技术的终端,支持最低复杂度且对时延不敏感的应用。不过,国内以Cat.1+NB作为当下蜂窝物联网的主流市场。  Rel-17针对eMTC/NB-IoT向上功能增强。相比4G的eMTC/NB-IoT,Rel-17进一步提高了这两项技术支持的数据速率,从而支持更多的用例。  (3)5G物联网场景逐步拓宽  Rel-17引入了面向非地面网络或卫星通信的5G物联网,包括支持eMTC和NB-IoT,从而将4G、5G中的物联网技术引入5G非地面网络通信中,可满足森林防火、天气预测,以及海洋场景的通信需求,作为偏远地区网络覆盖不足的有效补充。  此外,无源物联网作为5G时代低成本的物联网应用,也可规模应用于物流等场景
总条数:181 到第
上滑加载中