All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.apkcloud.devicedetector.DeviceDetector Maven / Gradle / Ivy

Go to download

通用设备检测库将解析任何UserAgent并检测浏览器、操作系统、使用的设备(桌面、平板、移动、电视、车载、游戏机等等)、品牌和型号。

There is a newer version: 1.0.7
Show newest version
package io.github.apkcloud.devicedetector;

import io.github.apkcloud.devicedetector.parser.OperatingSystem;
import io.github.apkcloud.devicedetector.parser.VendorFragment;
import io.github.apkcloud.devicedetector.parser.bot.AbstractBotParser;
import io.github.apkcloud.devicedetector.parser.client.*;
import io.github.apkcloud.devicedetector.parser.device.*;
import io.github.apkcloud.devicedetector.entity.Bot;
import io.github.apkcloud.devicedetector.parser.bot.BotParser;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DeviceDetector {
    /**
     * 设备检测器的当前版本号
     */
    public final String VERSION = "6.1.3";

    /**
     * 常量,未知浏览器/操作系统的值
     */
    public final String UNKNOWN = "UNK";

    /**
     * 保存所有注册的客户端类型
     */
    protected List clientTypes = new ArrayList<>();

    /**
     * 用于解析的 UserAgent
     */
    protected String userAgent = "";

    /**
     * 已解析的 ClientHints
     */
    protected ClientHints clientHints = null;

    /**
     * 解析 UA 后的操作系统数据
     */
    protected Map os = new HashMap<>();

    /**
     * 解析 UA 后的客户端数据
     */
    protected Map client = null;

    /**
     * 解析 UA 后的设备类型
     */
    protected Integer device;

    /**
     * 解析 UA 后的设备品牌数据
     */
    protected String brand = "";

    /**
     * 解析 UA 后的设备型号数据
     */
    protected String model = "";

    /**
     * 如果解析 UA 的结果是机器人,则保留机器人信息
     * (在这种情况下,所有其他信息属性将保持为空)
     * 

* 如果 $discardBotInformation 设置为 true,则此属性将设置为 * 如果已解析的 UA 被识别为 bot,则为 true,附加信息将不可用 *

* 如果 $skipBotDetection 设置为 true,则不会执行机器人检测,而 isBot 会 * 一直是 false */ protected Bot bot = null; protected boolean discardBotInformation = false; protected boolean skipBotDetection = false; protected List clientParsers = new ArrayList<>(); protected List deviceParsers = new ArrayList<>(); public List botParsers = new ArrayList<>(); private boolean parsed = false; private static DeviceDetector deviceDetector = null; public DeviceDetector() { addClientParser(new FeedReader()); // 用于检测 Feed阅读器 的客户端解析器 addClientParser(new MobileApp()); // 用于检测 移动应用程序 的客户端解析器 addClientParser(new MediaPlayer()); // 用于检测 媒体播放器 的客户端解析器 addClientParser(new PIM()); // 用于检测 PIM(个人信息管理器)的客户端解析器 addClientParser(new Browser()); // 用于检测 浏览器 的客户端解析器 addClientParser(new Library()); // 用于检测 工具和软件 的客户端解析器 addDeviceParser(new HbbTv()); // 用于检测 HbbTV(混合广播宽带电视)的设备解析器 addDeviceParser(new ShellTv()); // 用于检测 ShellTV(未知)的设备解析器 addDeviceParser(new Notebook()); // 用于检测 来自Facebook的UserAgent中的笔记本电脑 的设备解析器 addDeviceParser(new Console()); // 用于检测 游戏机 的设备解析器 addDeviceParser(new CarBrowser()); // 用于检测 车载浏览器 的设备解析器 addDeviceParser(new Camera()); // 用于检测 相机 的设备解析器 addDeviceParser(new PortableMediaPlayer()); // 用于检测 便携式媒体播放器 的设备解析器 addDeviceParser(new Mobile()); // 用于检测 移动 的设备解析器 addBotParser(new BotParser()); // 用于检测 机器人 的解析器 } public DeviceDetector(String userAgent) { this(); if (!isNullOrEmpty(userAgent)) { setUserAgent(userAgent); } } public DeviceDetector(String userAgent, ClientHints clientHints) { this(userAgent); if (clientHints != null) { setClientHints(clientHints); } } public boolean callMethod(String methodName) throws NoSuchMethodException { for (Map.Entry device : AbstractDeviceParser.getAvailableDeviceTypes().entrySet()) { String deviceName = device.getKey(); Integer deviceType = device.getValue(); if (methodName.equalsIgnoreCase("is" + deviceName.replace(" ", ""))) { return Objects.equals(getDevice(), deviceType); } } for (String client : clientTypes) { if (methodName.equalsIgnoreCase("is" + client.replace(" ", ""))) { return Objects.equals(getClient("type"), client); } } throw new NoSuchMethodException("Method " + methodName + " not found"); } /** * 设置要解析的 UserAgent */ public void setUserAgent(String userAgent) { if (!this.userAgent.equals(userAgent)) { this.reset(); } this.userAgent = userAgent; } /** * 设置已解析的浏览器 ClientHints */ public void setClientHints(ClientHints clientHints) { if (this.clientHints != clientHints) { this.reset(); } this.clientHints = clientHints; } public void addClientParser(AbstractClientParser parser) { clientParsers.add(parser); clientTypes.add(parser.getName()); } public List getClientParsers() { return clientParsers; } public void addDeviceParser(AbstractDeviceParser parser) { deviceParsers.add(parser); } public List getDeviceParsers(){ return deviceParsers; } public void addBotParser(AbstractBotParser parser) { botParsers.add(parser); } public List getBotParsers() { return botParsers; } /** * 设置是否忽略额外的机器人信息 * 如果信息被忽略,则只能检查 UA 是否被检测为机器人。 * (忽略信息可以加快检测速度) */ public void discardBotInformation(boolean discard) { discardBotInformation = discard; } /** * 设置是否跳过机器人检测。 * 如果我们希望将机器人作为简单的客户端进行处理,则需要它。所以我们可以检测它是否是移动客户端, * 或桌面,或其他任何东西。默认情况下,不会为机器人检索所有这些信息。 */ public void skipBotDetection(boolean skip) { skipBotDetection = skip; } /** * 如果解析的 UA 被识别为 Bot,则返回 true */ public boolean isBot(){ return bot != null; } /** * 如果解析的 UA 被识别为支持触摸的设备,则返回 true *

* 注意:这仅适用于 Windows 8 平板电脑 * * @return boolean */ public boolean isTouchEnabled() { String regex = "Touch"; return matchUserAgent(regex); } /** * 如果解析的 UA 被检测为移动设备,则返回 true * * @return boolean */ public boolean isMobile() { // ClientHints 指示移动设备 if (clientHints != null && clientHints.isMobile()) { return true; } // 移动设备类型 List list = Arrays.asList( AbstractDeviceParser.DEVICE_TYPE_FEATURE_PHONE, AbstractDeviceParser.DEVICE_TYPE_SMARTPHONE, AbstractDeviceParser.DEVICE_TYPE_TABLET, AbstractDeviceParser.DEVICE_TYPE_PHABLET, AbstractDeviceParser.DEVICE_TYPE_CAMERA, AbstractDeviceParser.DEVICE_TYPE_PORTABLE_MEDIA_PAYER); if (list.contains(device)) { return true; } // 非移动设备类型 list = Arrays.asList( AbstractDeviceParser.DEVICE_TYPE_TV, AbstractDeviceParser.DEVICE_TYPE_SMART_DISPLAY, AbstractDeviceParser.DEVICE_TYPE_CONSOLE); if (list.contains(device)) { return false; } // 检查仅适用于移动设备的浏览器 if (usesMobileBrowser()) { return true; } String osName = getOs("name"); if (isNullOrEmpty(osName) || UNKNOWN.equals(osName)) { return false; } return !isBot() && !isDesktop(); } /** * 返回解析后的 UA 是否被识别为桌面设备。 * 桌面设备是指所有未知类型且运行桌面操作系统的设备。 * * @return boolean * @see OperatingSystem#desktopOsArray * */ public boolean isDesktop() { String osName = getOsAttribute("name"); if (isNullOrEmpty(osName) || UNKNOWN.equals(osName)) { return false; } // 检查仅适用于移动设备的浏览器 if (usesMobileBrowser()) { return false; } return OperatingSystem.isDesktopOs(osName); } public Map getOs() { return os; } /** * 返回从解析的 UA 中提取的操作系统数据 *

* 如果提供了 attr 参数,将只返回该属性的值 * * @param attr 要返回的属性(可选) * @return String */ public String getOs(String attr) { return getOsAttribute(attr); } public Map getClient() { return client; } /** * 返回从解析的 UA 中提取的客户端数据 *

* 如果指定了 attr,只返回该属性 * * @param attr 要返回的属性(可选) * @return String */ public String getClient(String attr) { return getClientAttribute(attr); } /** * 返回从解析的 UA 中提取的设备类型 * * @return Integer * @see AbstractDeviceParser#deviceTypes */ public Integer getDevice() { return device; } /** * 返回从解析的 UA 中提取的设备类型名称 * * @return String 设备类型名称 * @see AbstractDeviceParser#deviceTypes */ public String getDeviceTypeName() { if (getDevice() != null) { return AbstractDeviceParser.getDeviceTypeName(getDevice()); } return ""; } /** * 返回从解析的 UA 中提取的设备品牌 * * @return String * @see AbstractDeviceParser#deviceBrands * @deprecated 自 4.0 版本起 - 短代码可能会在下一个主要版本中删除 */ public String getBrand() { return AbstractDeviceParser.getShortCode(brand); } /** * 返回从解析的 UA 中提取的完整设备品牌名称 * * @return String * @see AbstractDeviceParser#deviceBrands */ public String getBrandName() { return brand; } /** * 返回从解析的 UA 中提取的设备型号 */ public String getModel() { return model; } /** * 返回设置用于解析的 UserAgent */ public String getUserAgent() { return userAgent; } /** * 返回设置用于解析的 ClientHints */ public ClientHints getClientHints() { return clientHints; } /** * 返回从解析的 UA 中提取的机器人信息 */ public Bot getBot() { return bot; } /** * 如果 userAgent 已经通过 parse() 方法解析,则返回 true */ public boolean isParsed() { return parsed; } /** * 触发当前 UserAgent 的解析 */ public void parse() throws Exception { if (isParsed()) { return; } parsed = true; // 跳过对空 UserAgent 或不包含任何字母的 UserAgent 的解析(如果未提供 ClientHints) if ((isNullOrEmpty(userAgent) || !Pattern.compile("([a-z])", Pattern.CASE_INSENSITIVE).matcher(userAgent).find()) && clientHints == null) { return; } parseBot(); if (isBot()) { return; } parseOs(); /* 解析客户端 客户端可能是浏览器、Feed阅读器、移动应用程序、媒体播放器或 带有可解析的 UA 访问的任何其他应用程序 */ parseClient(); parseDevice(); } /** * 解析 UserAgent 并返回检测到的数据 *

* 注意:该方法仅用于测试或非常小的应用程序

* 要从 DeviceDetector 获得快速结果,您需要自己实现,

* 那应该使用其中一种缓存机制。有关详细信息,请参阅 README.md。 * * @param ua 要解析的 UserAgent * @param clientHints 要解析的 ClientHints * @return {@code Map} * @deprecated * @apiNote 这个方法仅供内部使用,不应直接公开给外部调用。 * */ public static Map getInfoFromUserAgent(String ua, ClientHints clientHints) throws Exception { if (deviceDetector == null) { deviceDetector = new DeviceDetector(); } deviceDetector.setUserAgent(ua); deviceDetector.setClientHints(clientHints); deviceDetector.parse(); if (deviceDetector.isBot()) { Map botInfo = new HashMap<>(); botInfo.put("user_agent", deviceDetector.getUserAgent()); botInfo.put("bot", deviceDetector.getBot()); return botInfo; } /** @var array $client */ Map client = deviceDetector.getClient(); String browserFamily = "Unknown"; if (deviceDetector.callMethod("isBrowser") && client != null && client.containsKey("family") && null != client.get("family") ) { browserFamily = client.get("family"); } if (client != null) { client.remove("short_name"); client.remove("family"); } /** @var array $os */ Map os = deviceDetector.getOs(); String osFamily = os.getOrDefault("family", "Unknown"); os.remove("short_name"); os.remove("family"); String finalBrowserFamily = browserFamily; return new HashMap<>(){ { put("user_agent", deviceDetector.getUserAgent()); put("os", os); put("client", client); put("device", new LinkedHashMap<>(){ { put("type", deviceDetector.getDeviceTypeName()); put("brand", deviceDetector.getBrandName()); put("model", deviceDetector.getModel()); } }); put("os_family", osFamily); put("browser_family", finalBrowserFamily); } }; } public String getClientAttribute(String attr) { if (!client.containsKey(attr)) { return UNKNOWN; } return client.get(attr); } public String getOsAttribute(String attr) { if (!os.containsKey(attr)) { return UNKNOWN; } return os.get(attr); } /** * 判断解析的 UA 是否包含 'Android; Tablet;' 片段 */ protected boolean hasAndroidTableFragment() { String regex = "Android( [\\.0-9]+)?; Tablet;"; return matchUserAgent(regex); } /** * 判断解析的 UA 是否包含 'Android; Mobile;' 片段 */ protected boolean hasAndroidMobileFragment() { String regex = "Android( [\\.0-9]+)?; Mobile;"; return matchUserAgent(regex); } /** * 判断解析的 UA 是否包含 'Desktop x64;' 或 'Desktop x32;' 或 'Desktop WOW64' 片段 */ protected boolean hasDesktopFragment() { String regex = "Desktop (x(?:32|64)|WOW64);"; return matchUserAgent(regex); } /** * 判断解析的 UA 是否包含仅限移动设备使用的浏览器 */ protected boolean usesMobileBrowser() { return "browser".equals(getClient("type")) && Browser.isMobileOnlyBrowser(getClientAttribute("name")); } /** * 使用 Bot 解析器解析 UA 以获取机器人信息 */ protected void parseBot() throws Exception { if (skipBotDetection) { bot = null; return; } List parsers = getBotParsers(); for (AbstractBotParser parser : parsers) { parser.setUserAgent(getUserAgent()); parser.setClientHints(getClientHints()); if (discardBotInformation) { parser.discardDetails(); } Bot bot = parser.parse(); if (bot != null) { this.bot = bot; break; } } } /** * 尝试检测客户端(例如浏览器、移动应用程序等) */ protected void parseClient() throws Exception { List parsers = getClientParsers(); for (AbstractClientParser parser : parsers) { parser.setUserAgent(getUserAgent()); parser.setClientHints(getClientHints()); Map client = parser.parse(); if (!isNullOrEmpty(client)) { this.client = client; break; } } } /** * 尝试检测设备类型、型号和品牌 */ protected void parseDevice() throws Exception { List parsers = getDeviceParsers(); for (AbstractDeviceParser parser : parsers) { parser.setUserAgent(getUserAgent()); parser.setClientHints(getClientHints()); if (!isNullOrEmpty(parser.parse())) { device = parser.getDeviceType(); model = parser.getModel(); brand = parser.getBrand(); break; } } // 如果无法从 UserAgent 中解析出型号,则使用 ClientHints 中的型号(如果有的话) if (clientHints != null && isNullOrEmpty(model)) { model = clientHints.getModel(); } // 如果未分配品牌,则尝试通过已知供应商片段进行匹配 if (isNullOrEmpty(brand)) { VendorFragment vendorParser = new VendorFragment(getUserAgent()); brand = vendorParser.parse().getOrDefault("brand", ""); } String osName = getOsAttribute("name"); String osFamily = getOsAttribute("family"); String osVersion = getOsAttribute("version"); String clientName = getClientAttribute("name"); // 如果是伪造的 UA,则最好不要将其识别为 Apple 运行 Android OS if ("Android".equals(osName) && "Apple".equals(brand)) { device = null; brand = ""; model = ""; } // 假设所有运行 iOS / Mac O S的设备都是来自 Apple if (isNullOrEmpty(brand) && Arrays.asList("iPadOS", "tvOS", "watchOS", "iOS", "Mac").contains(osName)) { brand = "Apple"; } /* Chrome 在 Android 传递设备类型基于关键字 'Mobile' 如果存在关键字,则设备应该是智能手机;否则是平板电脑 参考链接:https://developer.chrome.com/multidevice/user-agent#chrome_for_android_user_agent 注意:我们在此处不检查浏览器(家族),因为可能有使用 Chrome 的移动应用程序,不会检测到浏览器, 但仍然可以检测到。所以我们检查 Chrome 的 UserAgent。 */ if (device == null && "Android".equals(osFamily) && matchUserAgent("Chrome/[\\.0-9]*")) { if (matchUserAgent("(?:Mobile|eliboM) Safari/")) { device = AbstractDeviceParser.DEVICE_TYPE_SMARTPHONE; } else if (matchUserAgent("(?!Mobile )Safari/")) { device = AbstractDeviceParser.DEVICE_TYPE_TABLET; } } /* 某些 UA 包含片段 'Pad/APad',我们将这些设备视为平板电脑 */ if (device != null && AbstractDeviceParser.DEVICE_TYPE_SMARTPHONE == device && matchUserAgent("Pad/APad")) { device = AbstractDeviceParser.DEVICE_TYPE_TABLET; } /* 某些 UA 包含片段 'Android; Tablet;' 或 'Opera Tablet',我们将这些设备视为平板电脑 */ if (device == null && (hasAndroidTableFragment() || matchUserAgent("Opera Tablet"))) { device = AbstractDeviceParser.DEVICE_TYPE_TABLET; } /* 某些 UserAgent 只包含片段 'Android; Mobile;',我们将这些设备视为智能手机 */ if (device == null && hasAndroidMobileFragment()) { device = AbstractDeviceParser.DEVICE_TYPE_SMARTPHONE; } /* Android 3.0 及以上版本仅专为智能手机设计。但由于 3.0(仅适用于平板电脑)发布得太晚,因此出现了一堆运行 2.x 的平板电脑 在 4.0 中,两棵树被合并,适用于智能手机和平板电脑 因此,我们预计所有运行 Android < 2 的设备都是智能手机 运行 Android 3.X 的设备是平板电脑。 Android 2.X 和 4.X+ 的设备类型未知 */ if (device == null && "Android".equals(osName) && !"".equals(osVersion)) { if (compareVersions(osVersion, "2.0") == -1) { device = AbstractDeviceParser.DEVICE_TYPE_SMARTPHONE; } else if (compareVersions(osVersion, "3.0") >= 0 && compareVersions(osVersion, "4.0") == -1) { device = AbstractDeviceParser.DEVICE_TYPE_TABLET; } } /* 所有检测到的功能手机运行 Android 的更有可能是智能手机 */ if (device != null && AbstractDeviceParser.DEVICE_TYPE_FEATURE_PHONE == device && "Android".equals(osFamily)) { device = AbstractDeviceParser.DEVICE_TYPE_SMARTPHONE; } /* 所有未知设备运行Java ME的更有可能是功能手机 */ if ("Java ME".equals(osName) && device == null) { device = AbstractDeviceParser.DEVICE_TYPE_FEATURE_PHONE; } /* 根据http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx Internet Explorer 10 引入了 "Touch" UA字符串标记。如果此标记出现在 UA 字符串的末尾,则计算机具有触摸功能,并且正在运行 Windows 8(或更高版本)。 该 UA 字符串将在运行 Windows 8 (RT) 的触摸设备上传输。 由于大多数触摸设备都是平板电脑,只有一小部分是台式机/笔记本电脑,所以我们假设所有运行 Windows 8 的触摸设备都是平板电脑。 */ if (device == null && ("Windows RT".equals(osName) || ("Windows".equals(osName) && compareVersions(osVersion, "8") >= 0)) && isTouchEnabled()) { device = AbstractDeviceParser.DEVICE_TYPE_TABLET; } /* 所有运行 Opera TV Store 的设备都被视为电视 */ if (matchUserAgent("Opera TV Store| OMI/")) { device = AbstractDeviceParser.DEVICE_TYPE_TV; } /* 所有包含 Andr0id 字符串的设备都被视为电视 */ if (matchUserAgent("Andr0id|Android TV|\\(lite\\) TV")) { device = AbstractDeviceParser.DEVICE_TYPE_TV; } /* 所有运行 Tizen TV 或 SmartTV 的设备都被视为电视 */ if (device == null && matchUserAgent("SmartTV|Tizen.+ TV .+$")) { device = AbstractDeviceParser.DEVICE_TYPE_TV; } /* 运行 Kylo 或 Espital TV 浏览器的设备都被视为电视 */ if (device == null && Arrays.asList("Kylo", "Espial TV Browser").contains(clientName)) { device = AbstractDeviceParser.DEVICE_TYPE_TV; } /* 所有包含 TV 片段的设备都被视为电视 */ if (device == null && matchUserAgent("\\(TV;")) { device = AbstractDeviceParser.DEVICE_TYPE_TV; } /* 如果ua字符串中包含桌面,则将设备类型设置为桌面 */ boolean hasDesktop = AbstractDeviceParser.DEVICE_TYPE_DESKTOP != device && userAgent.contains("Desktop") && hasDesktopFragment(); if (hasDesktop) { device = AbstractDeviceParser.DEVICE_TYPE_DESKTOP; } // 对于运行桌面操作系统且未检测为其他设备类型的所有设备,将设备类型设置为桌面 if (device != null || !isDesktop()) { return; } device = AbstractDeviceParser.DEVICE_TYPE_DESKTOP; } public static int compareVersions(String version1, String version2) { String[] v1Tokens = version1.split("\\."); String[] v2Tokens = version2.split("\\."); int length = Math.max(v1Tokens.length, v2Tokens.length); for (int i = 0; i < length; i++) { int v1Token = i < v1Tokens.length ? Integer.parseInt(v1Tokens[i]) : 0; int v2Token = i < v2Tokens.length ? Integer.parseInt(v2Tokens[i]) : 0; if (v1Token < v2Token) { return -1; } else if (v1Token > v2Token) { return 1; } } return 0; } /** * 尝试检测操作系统 */ protected void parseOs() throws Exception { OperatingSystem osParser = new OperatingSystem(); osParser.setUserAgent(this.getUserAgent()); osParser.setClientHints(this.getClientHints()); this.os = osParser.parse(); } protected boolean matchUserAgent(String regex) { String pattern = "(?:^|[^A-Z_-])(?:" + regex.replace("/", "\\/") + ")"; Pattern compiledPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); Matcher matcher = compiledPattern.matcher(this.getUserAgent()); return matcher.find(); } /** * 重置所有检测到的数据 */ protected void reset() { this.bot = null; this.client = null; this.device = null; this.os = null; this.brand = ""; this.model = ""; this.parsed = false; } public static boolean isNullOrEmpty(List list) { return list == null || list.isEmpty(); } public static boolean isNullOrEmpty(Map map) { return map == null || map.isEmpty(); } public static boolean isNullOrEmpty(String s) { return s == null || s.isEmpty(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy