Android 大版本升级变更截图方法总结

Android 大版本升级变更截图方法总结

  • 一、Android R (11) 平台
  • 二、Android S (12) 平台
  • 三、Android U (14) 平台

Android 原生的截屏功能是集成在 SystemUI 中,因此我们普通应用想要获取截图方法,就需要研读下 SystemUI 截屏部分的功能实现。

在这里插入图片描述

一、Android R (11) 平台

SystemUI中截屏时获取当前的屏幕截图方法如下

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotMoreAction.java

private Bitmap captureScreenshotBitmap() {mDisplay.getRealMetrics(mDisplayMetrics);float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};int rot = mDisplay.getRotation();Rect sourceCrop = new Rect(0, 0, (int) dims[0], (int) dims[1]);// Take the screenshotBitmap bitmap = SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot);Log.d(TAG, "capture screenshot bitmap");if (bitmap == null) {Log.d(TAG, "capture screenshot bitmap is null!");}return bitmap;
}

核心代码:SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot)

关于 SurfaceControl.screenshot 的具体实现查看源码如下

frameworks/base/core/java/android/view/SurfaceControl.java/*** @see SurfaceControl#screenshot(Rect, int, int, boolean, int)}* @hide*/
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {return screenshot(sourceCrop, width, height, false, rotation);/*** Copy the current screen contents into a hardware bitmap and return it.* Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap into* a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}** CAVEAT: Versions of screenshot that return a {@link Bitmap} can be extremely slow; avoid use* unless absolutely necessary; prefer the versions that use a {@link Surface} such as* {@link SurfaceControl#screenshot(IBinder, Surface)} or {@link GraphicBuffer} such as* {@link SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}.** @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}* @hide*/
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height,boolean useIdentityTransform, int rotation) {// TODO: should take the display as a parameterfinal IBinder displayToken = SurfaceControl.getInternalDisplayToken();if (displayToken == null) {Log.w(TAG, "Failed to take screenshot because internal display is disconnected");return null;}if (rotation == ROTATION_90 || rotation == ROTATION_270) {rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;}SurfaceControl.rotateCropForSF(sourceCrop, rotation);final ScreenshotGraphicBuffer buffer = screenshotToBuffer(displayToken, sourceCrop, width,height, useIdentityTransform, rotation);if (buffer == null) {Log.w(TAG, "Failed to take screenshot");return null;}return Bitmap.wrapHardwareBuffer(buffer.getGraphicBuffer(), buffer.getColorSpace());
}

看到注解@UnsupportedAppUsage,这个注解的存在旨在提醒开发者,某些 API 或代码元素可能在未来版本中发生变化,可能会有风险或不稳定。

@UnsupportedAppUsage 注解,用于标记不建议应用程序使用的 API。它通常用于标记已被弃用或将在未来版本中删除的 API。

作为普通应用,我们需要兼容多版本,所以在使用高targetSdkVersion时,此方法在SDK中就会找不到,因此我们需要使用反射来完成。在 Android R (11) 上可使用的截图工具方法如下:

private static Bitmap screenshotR(int width, int height, Display defaultDisplay) {Bitmap bmp = null;Rect sourceCrop = new Rect(0, 0, width, height);try {Class<?> demo = Class.forName("android.view.SurfaceControl");Method method = demo.getMethod("screenshot", Rect.class, int.class, int.class, int.class);bmp = (Bitmap) method.invoke(null, sourceCrop, (int) width, (int) height,defaultDisplay.getRotation());} catch (Exception e) {e.printStackTrace();}return bmp;
}

二、Android S (12) 平台

Android S (12)Android T (13) 平台截图方法无变化,SystemUI 中相比较于 R 平台,代码有变化,梳理下代码找到截屏时获取当前的屏幕截图方法如下

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java

private Bitmap captureScreenshot(Rect crop) {int width = crop.width();int height = crop.height();Bitmap screenshot = null;final Display display = getDefaultDisplay();final DisplayAddress address = display.getAddress();if (!(address instanceof DisplayAddress.Physical)) {Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "+ display);} else {final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(physicalAddress.getPhysicalDisplayId());final SurfaceControl.DisplayCaptureArgs captureArgs =new SurfaceControl.DisplayCaptureArgs.Builder(displayToken).setSourceCrop(crop).setSize(width, height).build();final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =SurfaceControl.captureDisplay(captureArgs);screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();}return screenshot;
}

核心代码:SurfaceControl.captureDisplay(captureArgs)

关于 SurfaceControl.captureDisplay 的具体实现需要查看源码,这里多了几个新类DisplayCaptureArgsScreenshotHardwareBuffer

frameworks/base/core/java/android/view/SurfaceControl.java/*** @param captureArgs Arguments about how to take the screenshot* @param captureListener A listener to receive the screenshot callback* @hide*/
public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,@NonNull ScreenCaptureListener captureListener) {return nativeCaptureDisplay(captureArgs, captureListener);
}/*** Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with* the content.** @hide*/
public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) {SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();int status = captureDisplay(captureArgs, screenCaptureListener);if (status != 0) {return null;}return screenCaptureListener.waitForScreenshot();
}

在 Android S 和 Android T 上可使用的截图工具方法如下:

private static Bitmap screenshotS(int width, int height, Display defaultDisplay) {Bitmap bmp;Rect sourceCrop = new Rect(0, 0, width, height);final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) defaultDisplay.getAddress();final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(physicalAddress.getPhysicalDisplayId());final SurfaceControl.DisplayCaptureArgs captureArgs =new SurfaceControl.DisplayCaptureArgs.Builder(displayToken).setSourceCrop(sourceCrop).setSize(width, height).build();final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =SurfaceControl.captureDisplay(captureArgs);bmp = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();return bmp;
}

上诉工具类中DisplayAddress.Physical和SurfaceControl.DisplayCaptureArgs为hide类,SurfaceControl.getPhysicalDisplayTokenSurfaceControl.captureDisplayhide方法,所以需要使用反射方法来实现,下述代码为反射的实现:

public static Bitmap screenshotS(int width, int height, Display defaultDisplay) {Bitmap bmp = null;Rect sourceCrop = new Rect(0, 0, width, height);try {Class<?> displayAddressClass = Class.forName("android.view.DisplayAddress$Physical");Object physicalAddress =  displayAddressClass.getMethod("getPhysicalDisplayId").invoke(defaultDisplay.getAddress());Class<?> surfaceControlClass = Class.forName("android.view.SurfaceControl");Method getPhysicalDisplayTokenMethod = surfaceControlClass.getMethod("getPhysicalDisplayToken", long.class);Object displayToken = getPhysicalDisplayTokenMethod.invoke(null,physicalAddress);Class<?> displayCaptureArgsBuilderClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs$Builder");Constructor<?> displayCaptureArgsBuilderConstructor = displayCaptureArgsBuilderClass.getDeclaredConstructor(IBinder.class);Object displayCaptureArgsBuilder = displayCaptureArgsBuilderConstructor.newInstance(displayToken);Method setSourceCropMethod = displayCaptureArgsBuilderClass.getMethod("setSourceCrop", Rect.class);Method setSizeMethod = displayCaptureArgsBuilderClass.getMethod("setSize", int.class, int.class);Method buildMethod = displayCaptureArgsBuilderClass.getMethod("build");setSourceCropMethod.invoke(displayCaptureArgsBuilder, sourceCrop);setSizeMethod.invoke(displayCaptureArgsBuilder, width, height);Object captureArgs = buildMethod.invoke(displayCaptureArgsBuilder);Method captureDisplayMethod = surfaceControlClass.getMethod("captureDisplay", captureArgs.getClass());Object screenshotBuffer = captureDisplayMethod.invoke(null, captureArgs);if (screenshotBuffer != null) {Class<?> screenshotHardwareBufferClass = Class.forName("android.view.SurfaceControl$ScreenshotHardwareBuffer");Method asBitmapMethod = screenshotHardwareBufferClass.getMethod("asBitmap");bmp = (Bitmap) asBitmapMethod.invoke(screenshotBuffer);}} catch (Exception e) {e.printStackTrace();}return bmp;
}

三、Android U (14) 平台

Android 14 平台上的 SystemUI 中的截图方法类是使用Kotlin编写

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt

override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {val captureArgs = CaptureArgs.Builder().setSourceCrop(crop).build()val syncScreenCapture = ScreenCapture.createSyncCaptureListener()windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture)val buffer = syncScreenCapture.getBuffer()return buffer?.asBitmap()
}

我这里将 Kotlin 转化为 Java 编写的代码

// 导包为隐藏方法,请使用反射重写此代码
import android.view.IWindowManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.CaptureArgs;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;IWindowManager windowManager = IWindowManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.WINDOW_SERVICE));
CaptureArgs captureArgs = new CaptureArgs.Builder().setSourceCrop(sourceCrop).build();
SynchronousScreenCaptureListener syncScreenCapture = ScreenCapture.createSyncCaptureListener();
windowManager.captureDisplay(defaultDisplay.getDisplayId(), captureArgs, syncScreenCapture);
ScreenshotHardwareBuffer buffer = syncScreenCapture.getBuffer();
if (buffer != null) {bitmap = buffer.asBitmap();
}

核心代码:windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture)

之前的方法都封装在了SurfaceControl中,最新的U平台将逻辑挪到了IWindowManager

frameworks/base/core/java/android/view/IWindowManager.aidl/*** Captures the entire display specified by the displayId using the args provided. If the args* are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.*/oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,in ScreenCapture.ScreenCaptureListener listener);// aidl 是接口,相关实现在 WindowManagerService 中
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,ScreenCapture.ScreenCaptureListener listener) {Slog.d(TAG, "captureDisplay");if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {throw new SecurityException("Requires READ_FRAME_BUFFER permission");}ScreenCapture.LayerCaptureArgs layerCaptureArgs = getCaptureArgs(displayId, captureArgs);ScreenCapture.captureLayers(layerCaptureArgs, listener);if (Binder.getCallingUid() != SYSTEM_UID) {// Release the SurfaceControl objects only if the caller is not in system server as no// parcelling occurs in this case.layerCaptureArgs.release();}
}

IWindowManagerWindowManager 是 Android 系统中的两个不同的类,它们有以下区别:

  1. 接口 vs 类:IWindowManager 是一个接口,定义了窗口管理器的方法和功能,而 WindowManager 是一个具体的实现类,用于实际管理窗口的显示和操作。

  2. 系统服务 vs 上下文获取:IWindowManager 通常是通过系统服务机制获取的,可以通过 ServiceManager.getService("window") 来获取 IWindowManager 的实例。而 WindowManager 是通过上下文(Context)的 getSystemService() 方法获取的,例如 context.getSystemService(Context.WINDOW_SERVICE)

  3. 系统级权限 vs 应用级权限:IWindowManager 通常被用于系统级别的窗口管理,例如修改窗口属性、调整窗口的位置和大小等,因此访问 IWindowManager 需要特定的系统级权限。相比之下,应用程序可以通过 WindowManager 类来管理自己的窗口,但受到应用程序权限的限制。

总的来说,IWindowManager 是用于系统级窗口管理的接口,而 WindowManager 是用于应用程序级窗口管理的类。在大多数情况下,应用程序开发者更常使用 WindowManager 类来管理应用程序的窗口。

在 Android U (14) 上可使用的截图工具方法如下:

private static Bitmap screenshotU(int width, int height, Display defaultDisplay) {Bitmap bmp = null;Rect sourceCrop = new Rect(0, 0, width, height);try {IWindowManager windowManager = IWindowManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.WINDOW_SERVICE));Class<?> screenCaptureClass = Class.forName("android.window.ScreenCapture");Class<?> captureArgsClass = Class.forName("android.window.ScreenCapture$CaptureArgs");Class<?> captureArgsBuilderClass = Class.forName("android.window.ScreenCapture$CaptureArgs$Builder");Class<?> screenCaptureListenerClass = Class.forName("android.window.ScreenCapture$ScreenCaptureListener");Class<?> synchronousScreenCaptureListenerClass = Class.forName("android.window.ScreenCapture$SynchronousScreenCaptureListener");Class<?> screenshotHardwareBufferClass = Class.forName("android.window.ScreenCapture$ScreenshotHardwareBuffer");Method setSourceCropMethod = captureArgsBuilderClass.getDeclaredMethod("setSourceCrop", Rect.class);Object captureArgsBuilder = captureArgsBuilderClass.newInstance();setSourceCropMethod.invoke(captureArgsBuilder, sourceCrop);Method buildMethod = captureArgsBuilderClass.getDeclaredMethod("build");Object captureArgs = buildMethod.invoke(captureArgsBuilder);Method createSyncCaptureListenerMethod = screenCaptureClass.getMethod("createSyncCaptureListener");Object syncScreenCapture = createSyncCaptureListenerMethod.invoke(null);Method captureDisplayMethod = windowManager.getClass().getMethod("captureDisplay", int.class, captureArgsClass, screenCaptureListenerClass);captureDisplayMethod.invoke(windowManager, defaultDisplay.getDisplayId(), captureArgs, syncScreenCapture);Method getBufferMethod = synchronousScreenCaptureListenerClass.getMethod("getBuffer");Object buffer = getBufferMethod.invoke(syncScreenCapture);if (buffer != null) {Method asBitmapMethod = screenshotHardwareBufferClass.getMethod("asBitmap");bmp = (Bitmap) asBitmapMethod.invoke(buffer);}} catch (Exception e) {e.printStackTrace();}return bmp;
}

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

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

相关文章

linux 内核同步互斥技术之处理器内存屏障

处理器内存屏障用来解决处理器之间的内存访问乱序问题和处理器访问外围设备的乱序问题。 现代CPU的运算速度比现代内存系统的速度快得多&#xff0c;它们的速度差了几个数量级&#xff0c;那怎么办呢&#xff1f;硬件设计者想到了在内存和CPU之间加入一个速度足够快&#xff0…

Python IDE Pycharm服务器配置方法并结合内网穿透工具实现远程开发

文章目录 一、前期准备1. 检查IDE版本是否支持2. 服务器需要开通SSH服务 二、Pycharm本地链接服务器测试1. 配置服务器python解释器 三、使用内网穿透实现异地链接服务器开发1. 服务器安装Cpolar2. 创建远程连接公网地址 四、使用固定TCP地址远程开发 本文主要介绍如何使用Pych…

【音视频 | H.264】H.264视频编码及NALU详解

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

spark介绍及简单使用

简介 Spark是由加州大学伯克利分校AMPLab&#xff08;AMP实验室&#xff09;开发的开源大数据处理框架。起初&#xff0c;Hadoop MapReduce是大数据处理的主流框架&#xff0c;但其存在一些限制&#xff0c;如不适合迭代算法、高延迟等。为了解决这些问题&#xff0c;Spark在20…

【数据结构】复习题(一)

一、选择题 1.组成数据的基本单位是()。 A. 数据项 B.数据类型 C.数据元素 D.数据变量 2.设数据结构A{D,R},其中D&#xff5b;1,2,3,4},R{r},r{<1,2>,<2,3>,< 3,4>,<4,1>}&#xff0c;则数据结构A是()。 A.线性结构 B.树型结构 C.图型结构 D.集合 3.…

鸿蒙HarmonyOS开发用什么语言

1.网上流行一句有中国底蕴的话&#xff1a;鸿蒙系统方舟框架盘古大模型。都方舟框架了肯定主推的是ArkUI框架。其实还能使用C、Java和Js开发。 2.从API8开始&#xff0c;Java语言已经从鸿蒙开发剔除了&#xff0c;而官方推荐的是ArkTs.下图是ArkTS与TS、JS的关系。 ArkTs 是TS的…

Programming Abstractions in C阅读笔记:p235-p241

《Programming Abstractions in C》学习第66天&#xff0c;p235-p241总结。 一、技术总结 1.backtracking algorithm(回溯算法) (1)定义 p236, For many real-world problem, the solution process consits of working your way through a sequence of decision points in…

统信UOS|DNS server|02-部署DNS服务器

原文链接&#xff1a;统信UOS&#xff5c;DNS server&#xff5c;02-部署DNS服务器 hello&#xff0c;大家好啊&#xff01;继上次我们介绍了如何在统信UOS操作系统1060上搭建一个测试用的HTTP服务器之后&#xff0c;今天我们将继续我们的DNS服务器部署系列。这是第二篇文章&am…

Ubuntu18.04 上通过 jihu 镜像完成 ESP-IDF 编译环境搭建流程

为了解决国内开发者从 github 克隆 esp 相关仓库慢的问题&#xff0c;已将 esp-idf 和部分重要仓库及其关联的子模块镜像到了 jihu&#xff0c;这些仓库将自动从原始仓库进行同步。此篇博客用来阐述 Ubuntu18.04 上通过 jihu 镜像完成 ESP-IDF 编译环境搭建流程。 注&#xff1…

IDEA shorten command line介绍和JAR manifest 导致mybatis找不到接口类处理

如果类路径太长&#xff0c;或者有许多VM参数&#xff0c;程序就无法启动。原因是大多数操作系统都有命令行长度限制。在这种情况下&#xff0c;IntelliJIDEA将试图缩短类路径。最好选中 classpath file模式。 shorten command line 选项提供三种选项缩短类路径。 none&#x…

HCIP —— BGP 基础实验

实验拓扑&#xff1a; 实验要求&#xff1a; 1.所有设备上均有环回接口 2.R1属于AS 100 &#xff0c;R2-R4 属于AS 200 &#xff0c;R5 属于AS 300 3.R2 - R4 属于同一个area &#xff0c;运行OSPF。 4.全网通过运行BGP实现网络互通。 实验步骤&#xff1a; 1.配置 IP地址…

时序预测 | Python实现LSTM-Attention电力需求预测

时序预测 | Python实现LSTM-Attention电力需求预测 目录 时序预测 | Python实现LSTM-Attention电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前最先进的行…

UCloud + 宝塔 + PHP = 个人网站

UCloud 宝塔 PHP 个人网站 文章目录 1.概要2.UCloud使用教程&#xff08;租用云端服务器&#xff09;3.宝塔使用教程&#xff08;免费服务器运维面板&#xff09;4.总结 1.概要 今天主要是想教大家如何将在网络上白嫖到源码&#xff08;特指PHP源码!!!&#xff09;搭建运行…

大创项目推荐 深度学习 opencv python 公式识别(图像识别 机器视觉)

文章目录 0 前言1 课题说明2 效果展示3 具体实现4 关键代码实现5 算法综合效果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的数学公式识别算法实现 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学…

嵌入式中串口输入

学习目标 掌握串口初始化流程掌握串口接收逻辑了解中断接收逻辑熟练掌握串口开发流程学习内容 需求 串口接收PC机发送的数据。 串口数据接收 串口初始化 static void USART_config() {uint32_t usartx_tx_rcu = RCU_GPIOA;uint32_t usartx_tx_port = GPIOA;uint32_t usartx…

RabbitMQ入门指南(一):初识与安装

专栏导航 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、消息队列介绍 1.同步调用和异步调用 2.常见消息队列介绍 二、RabbitMQ简介及其安装步骤 1.RabbitMQ简介 2.RabbitMQ安装步骤&#xff08;使用Docker&#xff09; (1) 创建网络 (2) 使用Docker来…

Apache RocketMQ 5.0 腾讯云落地实践

Apache RocketMQ 发展历程回顾 RocketMQ 最早诞生于淘宝的在线电商交易场景&#xff0c;经过了历年双十一大促流量洪峰的打磨&#xff0c;2016年捐献给 Apache 社区&#xff0c;成为 Apache 社区的顶级项目&#xff0c;并在国内外电商&#xff0c;金融&#xff0c;互联网等各行…

【每日OJ—有效的括号(栈)】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 1、有效的括号题目&#xff1a; 1.1方法讲解&#xff1a; 1.2代码实现&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#…

本地运行大语言模型并可视化(Ollama+big-AGI方案)

目前有两种方案支持本地部署&#xff0c;两种方案都是基于llamacpp。其中 Ollama 目前只支持 Mac&#xff0c;LM Studio目前支持 Mac 和 Windows。 LM Studio&#xff1a;https://lmstudio.ai/ Ollama&#xff1a;https://ollama.ai/download 本文以 Ollama 为例 step1 首先下…

九牧:科技卫浴,长期主义

“没有做错什么&#xff0c;但却输给了时代”&#xff0c;这是人们给当年手机巨头诺基亚的注解。 谁也没有想到&#xff0c;曾在手机行业称雄的诺基亚&#xff0c;最终败给了时代。当年&#xff0c;在2G向3G、4G跨越的时候&#xff0c;苹果、微软的iOS和安卓系统将手机从简单的…