整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Webview.apk-Google 官方的私有插件

Webview.apk-Google 官方的私有插件化方案

文由 Gemini Wen 创作


在 Android 跨入 5.0 版本之后,我们在使用 Android 手机的过程中,可能会发现一个奇特的现象,就是手机里的 WebView 是可以在应用商店升级,而不需要跟随系统的。

这一点在 iOS 中尚未实现,(iOS OTA 的历史也不是特别的悠久)。但是 webview.apk 不是一个普普通通的 apk,首先它没有图标,不算是点击启动的“App”。同时,更新这个 APK,会让所有使用 webview 的应用都得到更新,哪怕是 webview 中的 UI ,比如前进后退也一样,得到更新。

这一点是如何做到的呢?今天我们来分析下 webview 这个奇特的 APK。


Android 资源和资源ID

如果开发过 Android 的小伙伴,对 R 这个类是熟悉得不能再熟悉了,一个 R 类,里面所有的“字符串”我们都看得懂,但是一堆十六进制的数字,我们可能并不是非常的熟悉,比如看见一个 R 长这样:

public class R {
    public static class layout {
        public static final int activity_main=0x7f020000
    }
}

后面那串十六进制的数字,我们一般称之为资源 ID (resId),如果你对 R 更熟悉一点,更可以知道资源 id 其实是有规律的,它的规律大概是

0xPPTTEEEE

其中 PP 是 packageId,TT 是 typeId,EEEE 是按规律出来的实体ID(EntryId),今天我们要关注的是前四位。如果你曾经关注的话,你大概会知道,我们写出来的 App,一般 PP 值是 7F。

我们知道 android 针对不同机型以及不同场景,定义了许许多多 config,最经典的多语言场景:values/values-en/values-zh-CN 我们使用一个字符串资源可能使用的是相同的 ID,但是拿到的具体值是不同的。这个模型就是一个表模型 —— id 作为主键,查询到一行数据,再根据实际情况选择某一列,一行一列确定一个最终值:

这种模型对我们在不同场景下需要使用“同一含义”的资源提供了非常大的便捷。Android 中有一个类叫 AssetManager 就是负责读取 R 中的 id 值,最终到一个叫 resources.arsc 的表中找到具体资源的路径或者值返回给 App 的。


插件化中的资源固定

我们经常听见 Android 插件化方案里,有一个概念叫 固定ID,这是什么意思呢?我们假设一开始一个 App 访问的资源 id 是 0x7f0103,它是一张图片,这时候我们下发了新的插件包,在构建的过程中,新增了一个字符串,恰好这张图片在编译中进行了某种排序,排序的结果使得 oxPPTT 中的 string 的 TT 变成了 01,于是这个字符串的 id 又恰好变成了 0x7f0103。那么老代码再去访问这个资源的时候,访问 0x7f0103,这时候拿到的不再是图片,而是一个字符串,那么 App 的 Crash 就是灾难性的了。

因此,我们期望资源 id 一旦生成,就不要再动来动去了。但是这里又有一个非常显眼的问题:如果 packageId 永远是 7f,那么显然是不够用的,我们知道有一定的方案可以更改 packgeId,只要在不同业务包中使用不同的 packageId,这样能极大避免 id 碰撞的问题,为插件化使用外部资源提供了条件。

等等!我们在开头说到了 webview.apk 的更新 —— 代码,资源都可以更新。这听上去不就是插件化的一种吗?Google 应用开发者无感知的情况下,到底是怎么实现 webview 的插件化的呢?如果我们揭开了这一层神秘的面纱,我们是不是也可以用这个插件化的特性了呢?

答案当然是肯定的。


WebView APK 和 android 系统资源

我作为一个 Android 工具链开发,在开始好奇 webview 的时候,把 webview.apk 下载过来的第一时间,就是把它拖进 Android Studio,看一看这个 APK 到底有哪里不同。

仔细看,它资源的 packgeId 是 00!直觉告诉我,0 这个值很特殊。

我们再看下大名鼎鼎的 android sdk 中的 android.jar 提供的资源。

这里说个题外话,我们使用 android 系统资源,比如 @android:color/red 这样的方式,其实就是使用到了 android.jar 中提供的资源。我们可以把这个 android.jar 重命名成 android.apk,拖进 Android Studio 中进行查看。

我们看到,android.jar 中资源的 packageId 是 01。直觉告诉我,1 这个值也很特殊,(2 看上去就不那么特殊了)这个 01 的实现,其实靠猜也知道是怎么做的 —— 把 packageId 01 作为保留 id,android 系统中资源的 id 永久固定,那么所有 app 拿到的 0x01 开头的资源永远是确定的,比如,我们去查看 color/black 这个资源,查看上面那张表里的结果是 0x0106000c,那么我至少确定我这个版本所有 android 手机的 @android:color/black 这个资源的 id 全都是 0x0106000c。我们可以做一个 demo 为证,我编译一个xml文件:

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black">
</ImageView>

然后查看编译出来的结果

我们看见 android:background 的值变成了 @ref/0x0106000c。这个 apk 在 Android 手机上运行的时候,会在 AssetsManager 里面加载两个资源包,一个是自己的 App 资源包,一个是 android framework 资源包,这时候去找 0x0106000c 的时候,就会找到系统的资源里面去。

有一个 android.jar 是个特殊的 01 没问题,那如果系统中存在许多的 apk,他们的值分别是 2,3,4,5,…… 想想都觉得要天下大乱了,如果这是真的,他们怎么管理这些资源 packageId 呢?

带着这些好奇,我下载了 aapt 的源码,准备在真相世界里一探究竟。


AAPT 源码,告诉你一切

下载源码过程和编译过程就不讲了,为了调试方便,建议大家编译出一个没有优化的 aapt debug 版,内涵是使用-O0关闭优化,并使用 debug 模式编译即可,我使用的版本是 android 28.0.3 版本

我们首先可以先瞅一眼,R 下面值的定义为什么是 0xPPTTEEEE,这个定义在 ResourceType.h,同时我们发现了以下几行代码

#define Res_GETPACKAGE(id) ((id>>24)-1)
#define Res_GETTYPE(id) (((id>>16)&0xFF)-1)
#define Res_GETENTRY(id) (id&0xFFFF)

#define APP_PACKAGE_ID      0x7f
#define SYS_PACKAGE_ID      0x01

前三行是 id 的定义,后两行是特殊 packageId 实锤。好了,01 被认定是系统包资源,7f 被认定为 App 包资源。

我们知道,在 xml 中引用其他资源包的方式,是使用@开头的,所以,假设你需要使用 webview 中的资源的时候,你需要指定包名,其实我们在使用 android 提供的资源的时候也是这么做的,还记得 @android:color/black 吗? 其实 @android 中的 android 就是 android.jar 里面资源的包名,我们再看一眼 android.jar 的包格式,注意图中的 packageName:

知道这点以后,我们使用 webview 中的资源的方式就变成如下例子:

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@com.google.android.webview:drawable/icon_webview">
</ImageView>

我们执行下编译,发现报错了:

res/layout/layout_activity.xml:2: error: Error: Resource is not public. (at 'src'
with value '@com.google.android.webview:drawable/icon_webview').

如果你之前使用过 public.xml 这个文件的话(你可能在这见过它:https://developer.android.com/studio/projects/android-library.html#PrivateResources),那么这里我需要说明下 —— 不仅仅是 library 有 private 资源的概念,跨 apk 使用资源同样有 public 的概念。但是,这个 public 标记像 aar 一样,其实并不是严格限制的。

在使用 aar 私有资源的时候,我们只要能拼出全部名称,是可以强行使用的。同时,apk,其实也有办法强行引用到这个资源,这一点我也是通过查看源码的方式得到结论的,具体在 ResourceTypes.cpp 中,有相关的代码:

bool createIfNotFound=false;
const char16_t* resourceRefName;
int resourceNameLen;
if (len > 2 && s[1]=='+') {
    createIfNotFound=true;
    resourceRefName=s + 2;
    resourceNameLen=len - 2;
} else if (len > 2 && s[1]=='*') {
    enforcePrivate=false;
    resourceRefName=s + 2;
    resourceNameLen=len - 2;
} else {
    createIfNotFound=false;
    resourceRefName=s + 1;
    resourceNameLen=len - 1;
}
String16 package, type, name;
if (!expandResourceRef(resourceRefName,resourceNameLen, &package, &type, &name,
                        defType, defPackage, &errorMsg)) {
    if (accessor !=NULL) {
        accessor->reportError(accessorCookie, errorMsg);
    }
    return false;
}

uint32_t specFlags=0;
uint32_t rid=identifierForName(name.string(), name.size(), type.string(),
        type.size(), package.string(), package.size(), &specFlags);
if (rid !=0) {
    if (enforcePrivate) {
        if (accessor==NULL || accessor->getAssetsPackage() !=package) {
            if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC)==0) {
                if (accessor !=NULL) {
                    accessor->reportError(accessorCookie, "Resource is not public.");
                }
                return false;
            }
        }
    }
    // ...
}

我们查看上面相关的代码,知道只要关闭 enforcePrivate 这个开关即可,查看这一段逻辑,可以很轻松得到结论,只要这样写就行了:

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@*com.google.android.webview:drawable/icon_webview">
</ImageView>

注意 @ 和包名之间多了一个 *,这个星号,就是无视私有资源直接引用的意思,再一次使用 aapt 编译,资源编译成功。查看编译出来的文件

看我们的引用变成了 @dref/0x02060061 咦,packageId 怎么变成了 02,没关系,我们后面的篇章解开这个谜底。


DynamicRefTable

我们根据刚刚上面的源码往下看,继续看 stringToValue 这个函数,会看见这么一段代码

if (accessor) {
    rid=Res_MAKEID(
        accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
        Res_GETTYPE(rid), Res_GETENTRY(rid));
    if (kDebugTableNoisy) {
        ALOGI("Incl %s:%s/%s: 0x%08x\n",
                String8(package).string(), String8(type).string(),
                String8(name).string(), rid);
    }
}

uint32_t packageId=Res_GETPACKAGE(rid) + 1;
if (packageId !=APP_PACKAGE_ID && packageId !=SYS_PACKAGE_ID) {
    outValue->dataType=Res_value::TYPE_DYNAMIC_REFERENCE;
}
outValue->data=rid;

这段代码告诉我们几件事:

  1. 刚刚的 webview 的 packageId 是经过 remapp 后的
  2. 它的类型变成了 TYPE_DYNAMIC_REFERENCE

看英文翻译是“动态引用”的意思。我们使用aapt d --values resources out.apk命令把资源信息打印出来,可以发现

Package Groups (1)
Package Group 0 id=0x7f packageCount=1 name=test
  DynamicRefTable entryCount=1:
    0x02 -> com.google.android.webview

  Package 0 id=0x7f name=test
    type 1 configCount=1 entryCount=1
      spec resource 0x7f020000 test:layout/layout_activity: flags=0x00000000
      config (default):
        resource 0x7f020000 test:layout/layout_activity: t=0x03 d=0x00000000 (s=0x0008 r=0x00)
          (string16) "res/layout/layout_activity.xml"

这里有关的是一个 DynamicRefTable,看它里面的值,好像是 packageId 和 packageName 映射。也就是说,0x02 的 packageId 所在的资源,应该是在叫 com.google.android.webview 的包里的。

我们查询 TYPE_DYNAMIC_REFERENCE 和 DynamicRefTable 有关的代码,找到了这么一个函数,我们看下定义:

status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
    uint32_t res=*resId;
    size_t packageId=Res_GETPACKAGE(res) + 1;

    if (packageId==APP_PACKAGE_ID && !mAppAsLib) {
        // No lookup needs to be done, app package IDs are absolute.
        return NO_ERROR;
    }

    if (packageId==0 || (packageId==APP_PACKAGE_ID && mAppAsLib)) {
        // The package ID is 0x00. That means that a shared library is accessing
        // its own local resource.
        // Or if app resource is loaded as shared library, the resource which has
        // app package Id is local resources.
        // so we fix up those resources with the calling package ID.
        *resId=(0xFFFFFF & (*resId)) | (((uint32_t) mAssignedPackageId) << 24);
        return NO_ERROR;
    }

    // Do a proper lookup.
    uint8_t translatedId=mLookupTable[packageId];
    if (translatedId==0) {
        ALOGW("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
                (uint8_t)mAssignedPackageId, (uint8_t)packageId);
        for (size_t i=0; i < 256; i++) {
            if (mLookupTable[i] !=0) {
                ALOGW("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
            }
        }
        return UNKNOWN_ERROR;
    }

    *resId=(res & 0x00ffffff) | (((uint32_t) translatedId) << 24);
    return NO_ERROR;
}

得到几个结论:

  1. 如果 packageId 是 0x7f 的话,不转换,原来的 ID 还是原来的 ID
  2. 如果 packageId 是 0 或者 packageId 是 7f 且 mAppAsLib 是真的话,把 packgeId 换成 mAssignedPackageId
  3. 否则从 mLookupTable 这个表中做一个映射,换成 translatedId 返回。

条件一很明确,二的话应该是 webview.apk 访问自己的资源情况,暂时不管。条件三就是我们现在想要知道的场景了。

我对 mLookupTable 这个变量非常好奇,于是跟踪调用,查看定义,最终找到一些关键信息,在 AssetManager2 中找到相关代码,我们给它添加额外的注释说明

void AssetManager2::BuildDynamicRefTable() {
  package_groups_.clear();
  package_ids_.fill(0xff);

  // 0x01 is reserved for the android package.
  int next_package_id=0x02;
  const size_t apk_assets_count=apk_assets_.size();
  for (size_t i=0; i < apk_assets_count; i++) {
    const ApkAssets* apk_asset=apk_assets_[i];
    for (const std::unique_ptr<const LoadedPackage>& package :
         apk_asset->GetLoadedArsc()->GetPackages()) {
      // Get the package ID or assign one if a shared library.
      int package_id;
      if (package->IsDynamic()) {
        //在 LoadedArsc 中,发现如果 packageId==0,就被定义为 DynamicPackage
        package_id=next_package_id++;
      } else {
          //否则使用自己定义的 packageId (非0)
        package_id=package->GetPackageId();
      }

      // Add the mapping for package ID to index if not present.
      uint8_t idx=package_ids_[package_id];
      if (idx==0xff) {
        // 把这个 packageId 记录下来,并赋值进内存中和 package 绑定起来
        package_ids_[package_id]=idx=static_cast<uint8_t>(package_groups_.size());
        package_groups_.push_back({});
        package_groups_.back().dynamic_ref_table.mAssignedPackageId=package_id;
      }
      PackageGroup* package_group=&package_groups_[idx];

      // Add the package and to the set of packages with the same ID.
      package_group->packages_.push_back(package.get());
      package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));

      // 同时更改 DynamicRefTable 中 包名 和 packageId 的对应关系
      // Add the package name -> build time ID mappings.
      for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
        String16 package_name(entry.package_name.c_str(), entry.package_name.size());
        package_group->dynamic_ref_table.mEntries.replaceValueFor(
            package_name, static_cast<uint8_t>(entry.package_id));
      }
    }
  }


  // 使用 O(n^2) 的方式,把已经缓存的所有 DynamicRefTable 中的 包名 -> id 的关系全部重映射一遍

  // Now assign the runtime IDs so that we have a build-time to runtime ID map.
  const auto package_groups_end=package_groups_.end();
  for (auto iter=package_groups_.begin(); iter !=package_groups_end; ++iter) {
    const std::string& package_name=iter->packages_[0]->GetPackageName();
    for (auto iter2=package_groups_.begin(); iter2 !=package_groups_end; ++iter2) {
      iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
                                          iter->dynamic_ref_table.mAssignedPackageId);
    }
  }
}

上面的中文注释是我加的,这一段逻辑其实很简单,我们经过这样的处理,完成了 buildId -> runtimeId 的映射。也就是说,WebView 的 packageId 是在运行时动态计算生成的!

这样的的确确解决了 packageId 维护的问题,因为 pacakgeId 可以重置,我们只要维护 packageName 就行了。


总结

经过以上的调研,我们目前知道了Google 官方的“插件化资源”是如何实现的。但是这个方案也有一个弊端,就是在 5.0 以下的手机上会 crash,原因是 5.0 以下的系统并不认识 TYPE_DYNAMIC_REFERENCE 这个类型。因此如果你的 App 还需要支持 5.0 以下的应用的话,还需要经过一些修改才能实现:

  1. 依然需要手动管理 packageId。
  2. 把 aapt 中关于 dynamic reference 的地方改成 reference。

期待各大厂商在努力更新 Android 版本上能迈出更大的步伐,一旦 5.0 以下的手机绝迹,我相信我们的 Android App 生态也会变得更加美好。

在这里我也分享一份几位大佬一起收录整理的Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料

如果你有需要的话,可以私信我【资料】我发给你,欢迎大家来白嫖~

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗~

理 | 张红月

出品 | CSDN(ID:CSDNnews)

本周热门项目

TabNine:支持23种语言及5种主流编辑器AI补代码工具问世(下附链接)

  • https://github.com/zxqfl/TabNine

一位来自加拿大的大四学霸,开发了一款”Deep TabNine“(Github )代码补全工具,TabNine是基于GPT-2构建的代码补全工具,这是一种Transformer架构,原产自OpenAI,是个“逆天”语言模型。

TabNine支持23种编程语言(Python、Java、C/C++、Haskell……)、5种编辑器(VS Code、Sublime Text、Atom、Emacs、Vim)。值得称道的是,Deep TabNine不同于其它各种代码补全插件,它是根据程序员过去的习惯自动补全,并在后面给出几种选项的概率。如果有类似代码出现在之前的项目里,TabNine还会在补全候选框中直接给出地址,方便用户点击进去查阅。

阿波罗11号指令模块和登月模块源码荣登Github趋势日榜TOP 1(下附链接)

  • https://github.com/chrislgarry/Apollo-11

1969年,阿波罗 11号登月任务是历史上最具标志性的事件之一,象征着人类首次踏上月球,开启宇宙探索的新篇章。今年恰好是登月50周年,集聚了30多万名技术人员和14.5万行计算机代码Apollo-11号源码在Github上开源,荣登Github趋势日榜TOP 1。

该项目是阿波罗11号制导计算机(AGC)中的指令模块(Comanche055)和登月模块(Luminary099)的原始代码。项目的电子化过程是由Virtual AGC和MIT Museum共同完成。

上世纪60年代,MIT一起实验室的程序员们需要给登月计划开发飞行控制软件,但是当时并没有现在如此成熟的技术,他们必须自己打造一套系统。

于是,他们提出了一种存储计算机程序的新方法——线存储器,并创造了一种特殊版本的汇编语言。现在许多程序员听到“汇编语言”都有可能瑟瑟发抖,而MIT的程序员为阿波罗制导计算机(AGC)编写了许许多多这种晦涩难懂的代码。

百度网盘克星诞生

近期GitHub上有两款百度网盘不限速下载器的项目火了,有了这两个下载器,百度网盘的会员都不用买了。堪称是百度网盘最大的敌人,同学们要抓紧时间下载,可能过几天这个项目就要被删库跑路了。

这两个项目分别是BND(https://github.com/b3log/baidu-netdisk-downloaderx)和pan-light(https://github.com/peterq/pan-light)。BND是一款图形界面的百度网盘不限速下载器,支持Windows、Linux和Mac,分为BND1和BND2两个系列。而pan-light项目是一款不限速的百度网盘客户端,基于 Golang + Qt5 开发。本项意义在于探究 Golang 在图形界面客户端、Web 服务端、事件调度、WebSocket、P2P 长连接等方面的应用和实践。

学生党学编程,这份文档就够了(下附链接)

  • https://github.com/dipakkr/A-to-Z-Resources-for-Students

这份文档除了学生党,也非常适用于职场开发者。目前已超5000 Star,该资料主要包含以下内容:编程学习资源、黑客马拉松与其它活动、学生福利计划、开源项目、创业项目与孵化器、实习生资源、开发者线下聚会、技术大会、值得关注的技术人、值得关注的网站、附加链接、编码训练营、其它资源。编程资源有Python、机器学习、深度学习、Android、后端、前端Web开发、全站Web开发、数据结构、C/C++语言、Git、R、MongoDB等。对于想学编程的人来说,这真是一个宝藏!

2019年最新BAT大厂面试题总结(下附链接)

  • https://github.com/0voice/interview_internal_reference

这个项目的作者收集了2019年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。内容分为阿里篇、华为篇、百度篇、腾讯篇、美团篇、头条篇、滴滴篇、京东篇、MySQL篇、Redis篇、MongDB篇、ZooKeeper篇、Nginx篇、算法篇、内存篇、CPU篇、磁盘篇、网络通信篇、安全篇、并发篇。

本周热门内容

“10x工程师”引热议,真有这样的工程师存在吗?(戳标题查看完整内容)

推特上“10x工程师”话题异常火爆,引发的热议经久不散。这个话题由一位印度初创公司投资人 Shekhar Kirani 的一条推特引发,他写道;“如果你恰巧遇见了这种稀缺的工程师种类,千万要抓住。招揽一位 “10x 工程师”作为工程师团队的一员,你的创业公司的成功率将大大提高。”对此,有人赞同,甚至有人提出,当年乔布斯跟比尔·盖茨也说过,优秀的程序员至少是平庸程序员50-100倍效率。但也有人表达了反对意见,Mozilla的soapdog痛斥:“这完全是一坨狗屎。工程师所做的是运用他们辛苦所学,为问题提供有意义,可重复,可预测和可理解的解决方案,而你所说的全是性格缺陷。”

那么,你认为真有这样的“10倍工程师存在吗?”笔者在CSDN APP上做了一次PK,结果87%以上的用户认为这样的工程师大有人在,而且还不少。

图灵登上英国50英镑新钞!人工智能之父荣耀比肩英国女王(下附链接)

  • https://www.bankofengland.co.uk/banknotes/50-pound-note-nominations

今年是人工智能之父艾伦·图灵诞辰107年,英国央行英格兰银行宣布,图灵将成为英国50英镑新钞人物!以表彰其对今天人们生活方式产生的巨大影响。图灵奠定了计算机科学的基础,在二战时帮助破解了德国的加密系统,他还对逻辑和哲学作出了重大贡献,提出了人工智能概念。英格兰银行行长 Mark Carney 称,图灵的工作对我们今天的生活具有巨大的影响,作为计算机科学和人工智能之父,以及二战英雄,他是一个巨人,他的肩膀扛起了今天的许多人。纸币使用了 1951 年拍摄的图灵照片。

Google 已经取消中国搜索引擎项目

Paypal 联合创始人 Peter Thiel 在全美保守主义会议上发表演说,呼吁 FBI 和 CIA 应该调查 Google 是否叛国。他对 Google 提出的三个问题分别是:1.有多少外国情报机关渗透了Google 人工智能的曼哈顿计划?2. Google 的管理阶层是否认为他们自己被中国的情报机构全面渗透?3. Google 是否认为自己被全面渗透,所以才会做出类似于叛国的决定:与中国军方而非美国军方合作?因为他们所做的理性决定实在太糟、太短视,好似这项科技若没有从正规管道出台,还是会从后门被偷走。目前 Google 对此的回应是,它并没有与中国军方合作。特朗普随后宣布,将对 Google 与中国政府的关系展开调查。

而在美国参议院司法委员会的听证会上,Google 公共政策副总裁 Karan Bhatia 称该公司已经取消了审查版搜索引擎项目 Project Dragonfly。Project Dragonfly 是在去年 8 月被 The Intercept 曝光的,之后就遭到广泛的批评,Google 雇员也联合施压要求公司终止该项目。Google 早在 2010 年就退出了中国搜索市场,但通过 Project Dragonfly Google 想要重返中国市场重新推出搜索产品,并将会根据要求审查内容。Google CEO Sundar Pichai 去年底在国会听证会上作证称,该公司目前没有计划在中国发布搜索产品。

现实版“黑客帝国” 马斯克发布脑机接口系统

本周刷屏朋友圈的莫过于马斯克的最新发布了!这次没有发布火箭、卫星、超级高铁,而是为成立两年的初创公司 Neuralink 发布了首款脑机接口产品。马斯克表示,Neuralink团队已经成功地让一只猴子通过大脑控制电脑。他还透露,明年还有可能进行人类患者临床试验。整个方法,核心一共有三部分。

一是“线”(threads),直径4-6微米,比人的头发丝(约75微米)还要细很多。与其他脑机接口中使用的材料相比,不仅对大脑损害性更小,而且还能传输更多数据。分布在96个线程上的每个阵列中,能够拥有多达3072个电极。二是“缝线的机器”。这是一个神经外科机器人,每分钟能够植入六根线。整个过程,特别像缝纫机。第三,Neuralink还开发了一种定制芯片,来更好地读取,清理和放大来自大脑的信号。

中文repo“霸榜”GitHub Trending,国外开发者不开心了(戳标题查看完整内容)

近日,一位叫Balazs Saros 的国外开发者在Medium上发表了一篇名为"Chinese repos are ruining the Github trending page"的博文,翻译一下他的意思就是“中文 repo 正在破坏 GitHub Trending 的页面”。

Github 的 Trending 页面是发现有趣的新 repo 的好功能,也给了新项目获得更多注意力的绝佳机会。但现在,Balazs 表示自己越来越不愿意去看这个页面了,因为满屏充斥着非英语 repo,尤其是中文 repo,前 10 个里有 9 个都是中文 repo,为此他截了一张 GitHub Trending 页面的图作证。

CSDN社区精选

CSS3新特性总结及CSS组件、特效汇总(下附链接)

  • https://blog.csdn.net/chuangxin/article/details/96380283

所谓CSS组件就是按照约定的DOM结构+组件class,即可实现组件展示效果,与js无关。现在有很多前端UI,比如:Bootstrap,妹子UI,layui就有很多CSS组件,甚至目前流行的前端3剑客:angular, react, vue都有很多对应的css组件。比如:栅格布局;导航、面包线、选项卡;表单、按钮;徽章、引用块等。除此之外,实际项目中我们也会积累一些自主的css组件和特效。该系列博文共包含6篇,分别是CSS3选择器、边框、背景使用细节及案例演示、CSS3 字体@font-face详解、如何创建和修改woff字体文件及text-shadow等文本效果、CSS3 2D转换和3D转换 transform 变形使用详解、CSS3过度transition和动画animation @keyframes规则详解、多列columns column-count和flex布局、多列columns column-count和flex布局。

为什么Windows/iOS操作很流畅而Linux/Android却很卡顿呢? (下附链接)

  • https://blog.csdn.net/dog250/article/details/96362789

先说是不是,再问为什么。我就知道有人会这么说,然而那样就成了一篇议论文了,而我只是想写一篇随笔。所以,不管事实是不是那样,反正我就是觉得Windows、MacOS、iOS都很流畅,而Linux、Android却很卡。当然了,这里说的是GUI,如果考量点换成是Web服务的吞吐和时延,那估计结论要反过来了,不过那是客户端程序感觉到的事,作为人,who care!

我写这篇文章还有一个意思,那就是想牵引一个话题,如果我们想把Linux、Android(当然,Android内核也是Linux)优化到GUI不再卡顿,我们应该怎么做。

十分钟学会 Web 开发利器 Tornado(下附链接)

  • https://blog.csdn.net/xufive/article/details/96281879

Python 旗下,群英荟萃,豪杰并起。单是用于 Web 开发的,就有 Webpy、Web2py、Bottle、Pyramid|、Zope2、Flask、Tornado、Django 等等,不一而足。最近几年较为流行的,大概也就是Flask、Tornado 和 Django 了。

对于 Tornado,我有很深的情感。如果把 Web 开发框架比作程序员手中的冷兵器,我觉得 Flask 好比是花枪, 轻灵飘逸,舞之令人眼花缭乱;Django 像大戟,合矛戈为一体,可直刺,可横击,威力无比;Tornado 秀外而惠中,更像是剑。剑在中国传统武术中有着很高的地位,为兵器之神,被认为有君子之风。

史上最全的Android面试题集锦(下附链接)

  • https://blog.csdn.net/xiangzhihong8/article/details/96280254

在Android开发中,不管是插件化还是组件化,都是基于Android系统的类加载器ClassLoader来设计的。只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把所有Class文件进行合并、优化,然后再生成一个最终的class.dex,目的是把不同class文件重复的东西只需保留一份,在早期的Android应用开发中,如果不对Android应用进行分dex处理,那么最后一个应用的apk只会有一个dex文件。

技术栈中的爱马仕?Facebook发布全新JavaScript引擎:Hermes(戳标题查看完整内容)

最近,一个崭新的JavaScript引擎面世:Hermes,它是Facebook在Chain React 2019 大会上发布 & 用于在React Native应用提高性能的,本文将进行全面介绍。

Java 代码界 3% 的王者?看我是如何解错这 5 道题的(下附链接)

  • https://blog.csdn.net/qing_gee/article/details/96151818

前些日子,阿里妹发表了一篇文章《悬赏征集!5 道题征集代码界前 3% 的超级王者》——看到这个标题,我内心非常非常激动,因为终于可以证明自己技术很牛逼了。但遗憾的是,凭借 8 年的 Java 开发经验,我发现这五道题自己全解错了!惨痛的教训再次证明,我是那被秒杀的 97% 的工程师之一。

不过,好歹我这人脸皮特别厚,虽然全都做错了,但还是敢于坦然地面对自己。本文分享这 5 道题,并进行全面解析,想挑战的,可以过来围观!

计算机组成原理(下附链接)

  • https://blog.csdn.net/Jmilk

对于刚入门的开发者来说,这个系列的文章则是必备读品。目前作者已经完成3篇,分别是计算机组成原理——中央处理器、计算机组成原理——指令系统、计算机组成原理——存储系统。

忆贵州三年的教书编程岁月:不弛于空想,不骛于虚声(下附链接)

  • https://blog.csdn.net/Eastmount/article/details/95803921

他曾说过“回到贵州,只求一辈子都用心对待每一个学生,教他们些东西,写点代码;摸着良心,对得起每一个学生,足矣!哪怕被世界所抛弃,至少还有娜女神和好友们的支持,足矣!

一行 Python 代码能实现什么丧心病狂的功能?(下附链接)

  • https://blog.csdn.net/xufive/article/details/96475103

Python 高手们只用一行代码都能干些什么?当然,限定条件是不能引用自定义的模块,可以使用内置模块或通用的第三方模块。上网一搜,发现这个问题好像是 python 的专属问题,其他语言很难用一行代码做点什么。本文作者用一行Python代码打印迷宫,打印乘法口诀、表白爱情、打印各种小动物,心动了吧,快去围观吧!

CSDN课程精选

Flutter从入门到精通(下附链接)

  • https://edu.csdn.net/combo/detail/1221?utm_source=edm0

想要学习Flutter,案例+就业,这一个套餐就够了!

《21天通关Python》买课包邮送书!下附链接)

  • https://edu.csdn.net/bundled/detail/49?utm_source=edm0

CSDN活动精选

7.27华为云开发者沙龙杭州站(下附链接)

  • https://huiyi.csdn.net/activity/product/goods_list?project_id=4276

7.21 巨杉TechDay 第4期:云时代的数据库架构设计与演进(下附链接)

  • https://huiyi.csdn.net/activity/product/goods_list?project_id=4268

免费参加英特尔在线培训,参与调研更有好礼相赠!(下附链接)

  • https://click.hm.baidu.com/clk?f962d2b8779b04d926cda4ee1c6c8313

【End】

文为您介绍如何利用DataWorks数据集成将JSON数据从OSS迁移到MaxCompute,并使用MaxCompute内置字符串函数GET_JSON_OBJECT提取JSON信息。

数据上传OSS

将您的JSON文件重命名后缀为TXT文件,并上传到OSS。本文中使用的JSON文件示例如下。

{
 "store": {
 "book": [
 {
 "category": "reference",
 "author": "Nigel Rees",
 "title": "Sayings of the Century",
 "price": 8.95
 },
 {
 "category": "fiction",
 "author": "Evelyn Waugh",
 "title": "Sword of Honour",
 "price": 12.99
 },
 {
 "category": "fiction",
 "author": "J. R. R. Tolkien",
 "title": "The Lord of the Rings",
 "isbn": "0-395-19395-8",
 "price": 22.99
 }
 ],
 "bicycle": {
 "color": "red",
 "price": 19.95
 }
 },
 "expensive": 10
}

将applog.txt文件上传到OSS,本文中OSS Bucket位于华东2区。

使用DataWorks导入数据到MaxCompute

  1. 新增OSS数据源
  2. 进入DataWorks数据集成控制台,新增OSS类型数据源。

  1. 具体参数如下所示,测试数据源连通性通过即可点击完成。Endpoint地址请参见OSS各区域的外网、内网地址,本例中为http://oss-cn-shanghai.aliyuncs.comhttp://oss-cn-shanghai-internal.aliyuncs.com(由于本文中OSS和DataWorks项目处于同一个region中,本文选用后者,通过内网连接)。

  1. 新建数据同步任务
  2. 在DataWorks上新建数据同步类型节点。

  1. 新建的同时,在DataWorks新建一个建表任务,用于存放JSON数据,本例中新建表名为mqdata。

  1. 表参数可以通过图形化界面完成。本例中mqdata表仅有一列,类型为string,列名为MQ data。

  1. 完成上述新建后,您可以在图形化界面配置数据同步任务参数,如下图所示。选择目标数据源名称为odps_first,选择目标表为刚建立的mqdata。数据来源类型为OSS,Object前缀可填写文件路径及名称。列分隔符使用TXT文件中不存在的字符即可,本文中使用 ^(对于OSS中的TXT格式数据源,Dataworks支持多字符分隔符,所以您可以使用例如 %&%#^$$^%这样很难出现的字符作为列分隔符,保证分割为一列)。

  1. 映射方式选择默认的同行映射即可。

  1. 点击左上方的切换脚本按钮,切换为脚本模式。修改fileFormat参数为: "fileFormat":"binary"。该步骤可以保证OSS中的JSON文件同步到MaxCompute之后存在同一行数据中,即为一个字段。其他参数保持不变,脚本模式代码示例如下。
{
 "type": "job",
 "steps": [
 {
 "stepType": "oss",
 "parameter": {
 "fieldDelimiterOrigin": "^",
 "nullFormat": "",
 "compress": "",
 "datasource": "OSS_userlog",
 "column": [
 {
 "name": 0,
 "type": "string",
 "index": 0
 }
 ],
 "skipHeader": "false",
 "encoding": "UTF-8",
 "fieldDelimiter": "^",
 "fileFormat": "binary",
 "object": [
 "applog.txt"
 ]
 },
 "name": "Reader",
 "category": "reader"
 },
 {
 "stepType": "odps",
 "parameter": {
 "partition": "",
 "isCompress": false,
 "truncate": true,
 "datasource": "odps_first",
 "column": [
 "mqdata"
 ],
 "emptyAsNull": false,
 "table": "mqdata"
 },
 "name": "Writer",
 "category": "writer"
 }
 ],
 "version": "2.0",
 "order": {
 "hops": [
 {
 "from": "Reader",
 "to": "Writer"
 }
 ]
 },
 "setting": {
 "errorLimit": {
 "record": ""
 },
 "speed": {
 "concurrent": 2,
 "throttle": false,
 "dmu": 1
 }
 }
}
  1. 完成上述配置后,点击运行接即可。运行成功日志示例如下所示。

获取JSON字段信息

在您的业务流程中新建一个ODPS SQL节点。

您可以首先输入 SELECT*from mqdata;语句,查看当前mqdata表中数据。当然这一步及后续步骤,您也可以直接在MaxCompute客户端中输入命令运行。

确认导入表中的数据结果无误后,您可以使用MaxCompute内建字符串函数GET_JSON_OBJECT获取您想要的JSON数据。本例中使用 SELECT GET_JSON_OBJECT(mqdata.MQdata,'$.expensive') FROM mqdata;获取JSON文件中的 expensive值。如下图所示,可以看到已成功获取数据。

作者:付帅