Google Glass 开发指南 https://developers.google.com/glass/

时间轴(Timeline)

你的用户的时间轴被分为时间表项目。您可以使用API​​接入和管理项目通过HTTP的REST操作时间表。(Your user's timeline is divided up into timeline items. Youcan use the API to insert and manage timeline items with REST operations overHTTP.)

注意:时间轴的产品最后七天在用户的玻璃和30天镜象API。(Note: Timelineitems last for seven days on a user's Glass and 30 days in the Mirror API.)

1.      插入一个简单的时间轴项目

1.      阅读时间表项目

2.      插入一个时间表与媒体项目

3.      访问附件

4.      捆绑卡

2.      用户交互菜单项

1.      使用内置的菜单项

2.      定义自定义菜单项

3.      允许用户固定时间表卡

插入一个简单的时间轴项目

要插入一个时间表项,POST一个 JSON表示的时间表项目的REST端点。

大多数字段都是可选的。最简单的形式,一个时间表项目只包含一个简短的文字消息。这是一个简单的时间表项目如何被插入。(To insert a timeline item, POST a JSON representation of atimeline item to theREST endpoint.

Mostof the fields are optional. In simplest form, a timeline item contains only ashort text message. This is how a simple timeline item is inserted.

原始HTTP的Java的

POST /mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization:Bearer{auth token}
Content-Type: application/json
Content-Length:26{"text":"Hello world"}
Java的
TimelineItem timelineItem =newTimelineItem();timelineItem.setText("Hello world");service.timeline().insert(timelineItem).execute();

在成功时,服务器会返回一个201响应代码创建创建项目的完整副本。对于上面的例子,可能看起来像一个成功的响应:(On success, the server will return a 201 Created responsecode with a full copy of the created item. For the example above, a successfulresponse might look like:)

原始的HTTP

HTTP/1.1201Created
Date:Tue,25Sep201223:30:11 GMT
Content-Type: application/json
Content-Length:303{"kind":"glass#timelineItem","id":"1234567890","selfLink":"https://www.googleapis.com/mirror/v1/timeline/1234567890","created":"2012-09-25T23:28:43.192Z","updated":"2012-09-25T23:28:43.192Z","etag":"\"G5BI0RWvj-0jWdBrdWrPZV7xPKw/t25selcGS3uDEVT6FB09hAG-QQ\"","text":"Hello world"
}

插入项目,将出现在用户的时间轴看起来像这样:(The inserted item that would appear in the user's timeline lookslike this:)

阅读时间表项目(Reading timeline items)

你的服务可以访问所有的时间表,它创建项目,以及所有共享项目时间表。以下是如何 为您服务是可见的项目列出时间表。(Your service can access all timeline items that it created,and all timeline items that were shared with it. Here's how to list the timelineitems that are visible to your service.)

原始HTTP的

GET /mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization:Bearer{auth token}

Java的
Timeline Item timelineItem =newTimelineItem();
service.timeline().list().execute();

您可以使用其他REST操作, 更新和 删除时间轴项目。(You can use other REST operations to get, update and delete timelineitems.)

插入一个时间表与媒体项目(Inserting a timeline item with media)

一张图片胜过千言万语,这是很多比你更可以融入一个时间表的项目。为此,您还可以包括图像和视频作为附件的时间表项目。(A picture is worth a thousand words, which is a lot more thanyou can fit into a timeline item. To this end, you can also include images andvideo as attachments to a timeline item.)

在一个较低的水平,使用HTTP多附件上传 。谷歌的API客户端库,使得这个易于使用的媒体上传。(At a low level, attachments are uploaded using HTTP Multipart. Google APIs client libraries make this easy using mediaupload.)

以下是如何插入一个时间表带有附件的项目。(Here's how to insert a timeline item with an attachment)

原始HTTP的Java的Python

POST /mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization:Bearer{auth token}
Content-Type: multipart/related; boundary="mymultipartboundary"
Content-Length:{length}--mymultipartboundary
Content-Type: application/json; charset=UTF-8{"text":"A solar eclipse of Saturn. Earth is also in this photo. Can you find it?"}
--mymultipartboundary
Content-Type: image/jpeg
Content-Transfer-Encoding: binary[binary image data]
--mymultipartboundary--
TimelineItem timelineItem =newTimelineItem();timelineItem.setText("Hello world");InputStreamContent mediaContent =newInputStreamContent(contentType, attachment);service.timeline().insert(timelineItem, mediaContent).execute();

时间轴项附加图像的玻璃装置上,看起来像这样:(A timeline item with an attached image looks something likethis on the glass device:)

访问附件(Accessing attachments)

时间轴带有附件的项目使他们在一个数组属性,恰当地命名为附件。附件的二进制数据,可以获取从URL中发现将 contentUrl属性或通过附件的端点。(Timeline items with attachments expose them in an array propertyaptly named attachments. The binary data for the attachment can be fetched from theURL found in the contentUrl property or via the attachments endpoint.)

注:内容保护的OAuth 2.0,就像其他调用的API端点。谷歌API客户端库提供访问附件使用媒体下载功能的二进制内容。(Note: Thecontent is protected by OAuth 2.0, just like other calls to the API endpoints.Google API client libraries provide access to the binary content of attachmentsusing the media download feature.)

原料HTTP的

GET /mirror/v1/timeline/{itemId}/attachments/{attachmentId} HTTP/1.1
Host: www.googleapis.com
Authorization:Bearer{auth token}
JAVA的
TimelineItem item = service.timeline().get(itemId).execute();String attachmentId = item.getAttachments().get(0).getId();service.attachments().get(itemId, attachmentId).executeAsInputStream();

捆绑卡(Bundling cards)

捆绑让您结合成捆的许多相关的卡。束杰出的页面卷曲正常时间轴卡的右上角。(Bundling allows you to combine many related cards into abundle. Bundles are distinguished from normal timeline cards by the page curlin the upper right corner.)

有两种方式捆绑内容:寻呼和线程。寻呼是通过指定值在 timelineItem.htmlPages[]属性。如果使用分页捆绑,元素都有着相同的timelineId,因此有相同的一组菜单项。这是非常有用的内容传送至玻璃不适合到一个单一的卡。(There are two ways to bundle content: paging and threading.Paging is done by specifying values in thetimelineItem.htmlPages[] property. If you use paging bundles, elements all share thesame timelineId and hence havethe same set of menu items. This is useful for sending content to Glass thatdoes not fit onto a single card.)

线程是通过与一个共同的bundleId的许多时间表卡关联 。要使用此技术,创造相同的值bundleId许多时间表项目。最近新增的项目将被盖卡。这种技术更可用于相关项目,如电子邮件线程。(Threading is done by associating many timeline cards with acommon bundleId. To use this technique, create many timeline items with thesame value for bundleId. The mostrecently added item will be the cover card. This technique is more useful forrelated items like email threads.)

用户交互菜单项(User interaction with menu items)

提供的内容仅仅是故事的一半。最有趣的服务用户所采取的行动作出回应。用户可以通过选择菜单项时间表卡。(Delivering content is only half of the story. Mostinteresting services also respond to actions taken by the user. Users can acton timeline cards by selecting menu items.)

菜单项有两种形式:内置菜单项和自定义菜​​单项。内置菜单项提供玻璃设备等硬件大声阅读的时间表卡所提供的特殊功能,导航到一个位置或者共享图像。(Menu items come in two flavors: built-in menu items andcustom menu items. Built-in menu items provide access to specialfunctionalities provided by the glass device hardware such as reading atimeline card aloud, navigating to a location or sharing an image.)

自定义菜单项可以让您的应用程序为您服务是特定的行为暴露,并提供了一​​个菜单图标匹配。(Custom menu items allow your application to expose behaviorthat is specific to your service and provide a menu icon to match)

使用内置的菜单项(Using built-in menu items)

您可以添加内置菜单项,通过填充菜单项数组,当你插入你的时间表项目 。要使用一个内置的菜单项,你只需要填入每个菜单项的 动作。(You can add built-in menu items to your timeline items bypopulating the menuItems array when you insert them. To use a built-in menu item, you onlyneed to populate the action of each menuItem.)

原始的HTTP

HTTP/1.1201Created
Date:Tue,25Sep201223:30:11 GMT
Content-Type: application/json
Content-Length:303{"text":"Hello world","menuItems":[{"action":"REPLY"}]
}

注:参考文档 包含可用的内建动作的详细说明。(Note: The reference documentation contains a detailed description of theavailable built in actions.)

定义自定义菜单项(Defining custom menu items)

内置动作可能不会永远是不够的。许多服务都需要暴露自己的特定的菜单项。这是自定义操作来发挥作用。(Built-in actions may not always be enough. Many services needto expose their own specific menu items. This is where custom actions come intoplay.)

创建一个自定义菜单项通过指定一个menuItem.action 习惯和 menuItem.id的。当用户触发一个自定义菜单项, 通知发送给服务人口与 menuItem.id。这可以让你确定源的通知。(Create acustom menu item by specifying a menuItem.action of CUSTOM and a menuItem.id. When your user triggers one of your custom menu items, a notification is sent to your service with the menuItem.id populated. This allows you to determine the source of thenotification.)

您还必须填充menuItem.menuValue的指定iconUrl和 显示名称将出现在玻璃设备。(You must alsopopulate menuItem.menuValue to specify an iconUrl and displayName that will appear on the glass device.)

原始的HTTP

HTTP/1.1201Created
Date:Tue,25Sep201223:30:11 GMT
Content-Type:application/json
Content-Length:303

{
  "text":"Helloworld",
  "menuItems":[
    {
      "action":"CUSTOM",
      "id":"complete"
      "values":[{
        "displayName":"Complete",
        "iconUrl":"http://example.com/icons/complete.png"
      }]
    }
  ]
}

注:为了获得最佳效果,请使用50像素的正方形透明背景的PNG图标图像。(Note: Forbest results, use a PNG icon image that is 50 pixels square with a transparentbackground.)

允许用户固定时间表卡(Allowing users to pin your timeline card)

您可以创建一个菜单项,可以让您的用户针时间表卡,它将永久显示在时间轴的卡主时钟卡的左侧。用户可以取消固定卡,通过使用相同的菜单项。(You can createa menu item that lets your users pin the timeline card, which permanentlydisplays the timeline card to the left of the main clock card. Users can unpinthe card as well, by using the same menu item)

钢钉菜单项是一个内置在菜单项,因此,所有你需要做的是提供一个菜单项TOGGLE_PINNED 行动。(The pinningmenu item is a built-in menu item, so all you need to do is provide the TOGGLE_PINNED action for a menuItem.)

原始的HTTP

HTTP/1.1201Created
Date:Tue,25Sep201223:30:11 GMT
Content-Type: application/json
Content-Length:303{"text":"You can pin or unpin this card.","menuItems":[{"action":"TOGGLE_PINNED"}...]
}

订阅(Subscriptions)

镜API允许你 订阅 ,当用户需要发送一个时间轴项目的具体行动, 或用户位置时,已经更新的通知。(The Mirror APIallows you to subscribe tonotifications that are sentwhen the user takes specific actions on a Timeline Item or when the user location has been updated.)

1.      接收通知

2.      通知类型

1.      共享图片

2.      回复

3.      删除

4.      选择自定义菜单项

5.      位置更新

接收通知(Receiving notifications)

从镜像API发送一个POST请求包含一个JSON请求主体的认购端点的通知。(A notificationfrom the Mirror API is sent as a POST request to thesubscribed endpoint containing a JSON request body.)

原始HTTP的

{
  "collection":"timeline",
  "itemId":"3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation":"UPDATE",
  "userToken":"harold_penguin",
  "verifyToken":"random_hash_to_verify_referer",
  "userActions":[
    {
      "type":"<TYPE>",
      "payload":"<PAYLOAD>"
    }
  ]
}

Java的
import com.google.api.client.json.JsonFactory;import com.google.api.client.json.jackson.JacksonFactory;import com.google.api.services.mirror.model.Notification;import java.io.IOException;import java.io.InputStream;// ...publicclassMyClass{  // ...  /**    * Parse a request body into a Notification object.    *    * @param requestBody The notification payload sent by the Mirror API.    * @return Parsed notification payload if successful, {@code null} otherwise.    */  staticNotification parseNotification(InputStream requestBody){    try{      JsonFactory jsonFactory =newJacksonFactory();      return jsonFactory.fromInputStream(requetBody,Notification.class);    }catch(IOException e){      System.out.println("An error occurred: "+ e);      returnnull;    }  }  // ...}

你的服务必须回应一个200 OK HTTP状态代码,如果没有发生错误的API 。如果您的服务响应错误代码,镜像API可能会尝试重新发送该通知为您服务。(Your servicemust respond to the API with a 200 OK HTTP statuscode if no error occurred. If your service responds with an error code, theMirror API might try to resend the notification to your service.)

注:连接超时10秒后。如果需要一个漫长的过程,马上做出反应,并在另一个线程中做的过程。(Note: Theconnection will time out after 10 seconds. If a long process is required,respond right away and do the process in another thread.)

通知类型(Notification types)

镜API发送一个不同的有效载荷为不同的事件通知。(The Mirror APIsends a different notification payload for different events.)

共享图片(Shared picture)

用户已经分享了一张图片与您的服务。(The user hasshared a picture with your service.)

{
  "collection":"timeline",
  "itemId":"3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation":"UPDATE",
  "userToken":"harold_penguin",
  "verifyToken":"random_hash_to_verify_referer",
  "userActions":[
    {
      "type":"SHARE"
    }
  ]
}

ITEMID属性设置该项目包含的照片作为附件的ID:(The itemId attribute is set to the ID of the itemcontaining the photo as an attachment:)

{
  "id":"3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "attachments":[
      {
          "contentType":"image/jpeg",
          "id":"<ATTACHMENT_ID>",
          "contentUrl":"https://www.googleapis.com/mirror/v1/attachments/3hidvm0xez6r8_dacdb3103b8b604_h8rpllg/<ATTACHMENT_ID>"
      }
  ],
  "recipients":[
      {
          "kind":"glass#contact",
          "source":"api:<SERVICE_ID>",
          "id":"<CONTACT_ID>",
          "displayName":"<CONTACT_DISPLAY_NAME>",
          "imageUrls":[
              "<CONTACT_ICON_URL>"
          ]
      }
  ]
}

注:与联系人共享内容的更多信息,请参阅联系。(Note: See Contacts for more information about sharingcontent with contacts.)

回复(Reply)

用户已经回答了你的时间表项目使用内置的回复 菜单项:(The user hasreplied to your timeline item using the built-in REPLY menu item:)

{
  "collection":"timeline",
  "itemId":"3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation":"INSERT",
  "userToken":"harold_penguin",
  "verifyToken":"random_hash_to_verify_referer",
  "userActions":[
    {
      "type":"REPLY"
    }
  ]
}

ITEMID属性设置该项目包含的ID:

·        inReplyTo属性设置为ID的时间轴项目,它是一个答复。

·        文本属性设置文本transcribtion的。

·        附件的属性设置到Web的音频播放器和语音记录的音频文件。

(

The itemId attribute is set to the ID of theitem containing:

·        inReplyTo attribute set to the ID of the timeline item it is a replyto.

·        text attribute set to the text transcribtion.

·        attachments attribute set to the web audio player and the audio fileof the voice recording.)

例如:

{"kind":"glass#timelineItem","id":"3hidvm0xez6r8_dacdb3103b8b604_h8rpllg","inReplyTo":"3236e5b0-b282-4e00-9d7b-6b80e2f47f3d","text":"This is a text reply","attachments":[{"contentType":"text/vnd.google.audio-player-url","contentUrl":"<AUDIO_PLAYER_URL>"},{"contentType":"text/vnd.google.audio-download-url","contentUrl":"<AUDIO_FILE_URL>"}]
}

删除

该用户已删除项目的时间表:(The user has deleted a timeline item:)

{
  "collection":"timeline",
  "itemId":"3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation":"DELETE",
  "userToken":"harold_penguin",
  "verifyToken":"random_hash_to_verify_referer",
  "userActions":[
    {
      "type":"DELETE"
    }
  ]
}

ITEMID属性设置的ID删除项。资料不再包含元数据以外的ID和 isDeleted财产的。(The itemId attribute is set to the ID of the deleted item. The item nolonger contains metadata other than its ID and theisDeleted property.)

注意:如果用户删除一个项目从他们的时间表,建议您删除这些内容从你的系统太。(Note: Ifthe user deletes an item from their timeline, it's recommended that you deletethis content from your systems too.)

选择自定义菜单项(Custom menu item selected)

用户选择了一个 自定义菜单项 设置由您的服务:(The user hasselected a custom menuitem set by yourservice:)

{
  "collection":"timeline",
  "itemId":"3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation":"UPDATE",
  "userToken":"harold_penguin",
  "userActions":[
    {
      "type":"CUSTOM",
      "payload":"PING"
    }
  ]
}

的ITEMID属性被设置为用户选择的菜单项的ID。(The itemId attribute is set to the ID of the menu item that the userselected.)

的userActions数组包含了这个项目的用户自定义操作的列表。您的服务应如何处理这些相应的行动。(The userActions array contains the list of custom actions that the user tookon this item. Your service should handle those actions accordingly.)

位置更新(Location update)

为当前用户提供一个新的位置是:(A new location is available for the current user:)

{
  "collection":"locations",
  "itemId":"latest",
  "operation":"UPDATE",
  "userToken":"harold_penguin",
  "verifyToken":"random_hash_to_verify_referer"
}

当你的玻璃器皿接收位置更新,发送一个请求到的glass.locations.get 端点检索最新的已知位置。你的玻璃器皿接收位置,每10分钟更新。(When yourGlassware receives a location update, send a request to the glass.locations.get endpoint to retrieve the latest known location. YourGlassware receives location updates every ten minutes.)

注:正在位置信息需要 https://www.googleapis.com/auth/glass.location的范围。

(Note: Retrieving   location  information    requires         the https://www.googleapis.com/auth/glass.location scope.)

位置(Location)

您可以使用镜像API观察用户的位置在时间轴项目,直接要求他们 最后已知位置,并订阅周期性位置更新。您也可以提供预渲染的时间表卡给镜像API坐标绘制的地图图像。(You can usethe Mirror API to observe the user's location in timeline items, request their last knownlocation directly, andsubscribe to periodic location updates. You can also deliver pre-rendered mapimages in timeline cards by giving the Mirror API the coordinates to draw.)

注:检索用户的位置,需要额外 https://www.googleapis.com/auth/glass.location范围。(Note: Retrievingusers' location requires the additional https://www.googleapis.com/auth/glass.location scope.)

检索最新的已知位置(Retrieving thelatest known location)

要检索最新的已知位置为当前用户,发送一个GET请求发送到REST端点:(To retrieve the latest known location for the current user, send a GET request to the REST endpoint:)

原始的HTTP

GET /mirror/v1/locations/ HTTP/1.1
Authorization:Bearer{auth token}

订阅的位置更新(Subscribing to location updates)

订阅时间表更新,您可以订阅订阅地点 集合地点的更新。(Similar to subscribing totimeline updates, you cansubscribe to location updates by subscribing to the locations collection.)

原始的HTTP

POST /mirror/v1/subscriptions HTTP/1.1
Authorization:Bearer{auth token}
Content-Type: application/json
Content-Length:{length}{"collection":"locations","userToken":"harold_penguin","verifyToken":"random_hash_to_verify_referer","callbackUrl":"https://example.com/notify/callback"
}

注:在这个时候,地点通知每隔10分钟发送。(Note: Atthis time, location notifications are sent every 10 minutes.)

在时间轴卡的渲染图(Rendering maps on timeline cards)

谷歌镜API可以使你和覆盖标记和线条图,以示重要场所和路径。使用玻璃:/ /地图 URI请求地图。下面是一个例子:(The GoogleMirror API can render maps for you and overlay markers and lines to signifyimportant places and paths. Use theglass://map URI to request a map. Here's an example:)

<imgsrc="glass://map?w=width&h=height&marker=0;latitude,longitude&marker=1;latitude,longitude&polyline=;latitude,longitude,latitude,longitude"
  width="width"
  height="height"/>

注:请务必指定的宽度和高度的图像以及的<img>标签。这可以防止回流被渲染的地图图像。

这里是一个所需的参数的描述:(Note: Alwaysspecify the width and height of the image in the <img> tagas well. This prevents reflows as the map image is being rendered.)

Here is a description of required parameters:

·        w -返回地图图像中像素的宽度

·        ħ -返回地图图像中像素的高度

另外需要在下面的列表中只有一个项目,但您可以指定所有的人:(Only one ofthe items in the following list are additionally required, but you can specifyall of them:)

·        中心和缩放地图渲染和缩放级别-中心(经度,纬度)。 有关更多信息,请参见缩放级别。(center and zoom - The center(latitude,longitude) of the map to render at and the zoom level. See Zoom Levels for more information.)

·        标记 -指定的引脚标记绘制在指定的坐标。标记参数采用标记(从0开始),纬度坐标和经度坐标的索引值。自动地图中心和缩放您创建的标记,如果你不明确指定中心和缩放周围。(marker - Specify the pin markers to draw at the specifiedcoordinates. The marker parameter takes an index value for the marker(startingat 0), the latitude coordinate, and the longitude coordinate. Themap automatically centers and zooms around the markers you create if you don'texplicitly specify center and zoom.)

·        折线 -指定折线坐标在地图上表示路径。每条线由折线中顶点的宽度和颜色。例如:折线 8个像素宽的红线之间(47.6,-122.34)和(47.62,-122.40)= 8,FFFF0000 47.6,-122.34,47.62,-122.40指定。地图会自动居中并缩放到适合的折线,如果你不明确指定中心和缩放。(polyline - Specify the polyline coordinates to represent a path on themap. Each polyline consists of a width and color followed by the vertices inthe polyline. For example: polyline=8,ffff0000;47.6,-122.34,47.62,-122.40 specifies an 8-pixel wide red line between (47.6,-122.34) and (47.62,-122.40). Themap is automatically centered and zoomed to fit the polyline if you don'texplicitly specify center and zoom.)

下面的示例显示如何显示一些文本,它是什么样子的地图图像的最佳实践:(The followingexample shows a best practice of how to display a map image with some text andwhat it looks like:)

<article><figure><imgsrc="glass://map?w=240&h=360&marker=0;42.369590,-71.107132&marker=1;42.36254,-71.08726&polyline=;42.36254,-71.08726,42.36297,-71.09364,42.36579,-71.09208,42.3697,-71.102,42.37105,-71.10104,42.37067,-71.1001,42.36561,-71.10406,42.36838,-71.10878,42.36968,-71.10703"height="360"width="240"></figure><section><divclass="text-auto-size"><pclass="yellow">12 minutes to home</p><p>Medium traffic on Broadway</p></div></section>
</article>

注: 您可以省略折线的颜色和宽度,在这个例子中一样。地图渲染,在这种情况下,使用默认的颜色和宽度。(Note: Youcan omit the color and width of the polyline like in this example. The map isrendered using the default color and width in this situation.)

往来(Contacts)

默认情况下,玻璃器皿有时间表的项目,它创建的访问。联系,结合股份 内置菜单项,允许用户共享时间表与其他玻璃器皿项目。这既包括时间表由其他玻璃器皿和时间表由设备本身创建项目创建的项目,如内置的摄像头所拍摄的照片。(By default,Glassware only has access to the timeline items that it created. Contacts,combined with the SHARE built-in menuitem, allow your users toshare timeline items with other Glassware. This includes both timeline itemscreated by other Glassware and timeline items created by the device itself,such as photos taken by the built-in camera.)

为了让用户分享你的玻璃器皿时间表项目,通过发布一个JSON表示接触到插入REST端点插入联系人 。(To allow theuser to share timeline items with your Glassware, insert a contact by POSTing a JSONrepresentation of a contactto the insert RESTendpoint.)

所有联系人必须指定一个ID,可识别接触到的玻璃器皿接到通知。您还必须指定一个 显示名和至少一个imageUrls,用于当接触,这将在玻璃的移动设备上显示。(All contactsmust specify an id, whichidentifies the contact to the Glassware receiving the notifications. You must also specify a displayName and at leastone imageUrls, whichwill be displayed on the Glass device when the contact is used.)

原始的HTTP

POST /mirror/v1/contacts HTTP/1.1
Authorization:Bearer{auth token}
Content-Type: application/json
Content-Length:{length}{"id":"harold""displayName":"Harold Penguin","iconUrl":"https://developers.google.com/glass/images/harold.jpg""priority":7
}

警告:一旦接触已插入您的服务,您的用户必须启用它在MyGlass。(Warning: Oncea contact has been inserted by your service, your user must enable it at MyGlass.)

 

注:为了获得最佳效果,请使用PNG图标图像是640×360像素,透明背景。(Note: Forbest results, use a PNG icon image that is 640 by 360 pixels with a transparentbackground.)

授权请求

1.      创建一个客户端ID和客户端密钥

2.      授权请求处理

1.      验证用户的身份

2.      交换一个访问令牌的授权码

3.      授权与存储的凭据

3.      使用OAuth 2.0凭据

1.      实例化一个服务对象

2.      发送授权请求,并检查撤销的凭据

4.      下一步

谷歌镜API的请求必须被授权使用OAuth 2.0认证。您应该使用服务器端的流量,当你的应用程序需要访问谷歌的API代表的用户,例如当用户处于脱机。这种方法需要从你的客户通过一次性授权代码到您的服务器,用于获取为您的服务器的访问和刷新令牌。(Requests tothe Google Mirror API must be authorized using OAuth 2.0 credentials. Youshould use server-side flow when your application needs to access Google APIson behalf of the user such as when the user is offline. This approach requirespassing a one-time authorization code from your client to your server that isused to acquire an access and refresh tokens for your server.)

创建一个客户端ID和客户端密钥(Create a client ID and clientsecret)

首先,你需要激活谷歌镜为您的应用程序的API。为此,您可以为您的API项目在 谷歌的API控制台。(First, youneed to activate the Google Mirror API for your app. You can do this for yourAPI project in the Google APIsConsole.)

1.      在谷歌的API控制台创建一个API项目 。(Create an APIproject in the Google APIs Console.)

2.      在你的API项目中选择“ 服务 “选项卡,并启用谷歌镜API。(Select the Services tab in your API project, and enablethe Google Mirror API.)

3.      选择在你的API项目的API访问 “选项卡,并单击” 创建的OAuth 2.0的客户端ID。(Select the API Access tab in your API project, and click Createan OAuth 2.0 client ID.)

4.      在品牌信息部分中,为您的应用提供一个名称(如“我的玻璃服务”),然后单击“ 下一步“。提供产品标识是可选的。(In the Branding Information section, provide a name for yourapplication (e.g. "My Glass service"), and click Next.Providing a product logo is optional.)

5.      在客户端ID设置部分,做到以下几点:(In the Client ID Settings section, do the following:)

a.      选择Web应用程序应用程序类型。(Select Web application for the Applicationtype.)

b.      点击更多选项旁边的标题,你的网站链接 或主机名。(Click the more options link next to the heading, Yoursite or hostname.)

c.       列出你的主机在授权重定向的URI和 JavaScript的起源领域。(List your hostname in the Authorized Redirect URIs and JavaScript origins fields.)

d.      单击“ 创建客户端ID。(Click Create Client ID.)

6.      在的API访问页面,找到部分 的Web应用程序的客户端ID,并注意客户端ID和 客户端密钥值。(In the API Access page, locate the section ClientID for Web applications andnote the Client ID and Client Secretvalues.)

授权请求处理(Handling authorization requests)

当用户第一次加载您的应用程序,它们都带有一个对话框,为您的应用程序授予权限来访问他们的玻璃谷歌帐户的请求的权限范围。这个初始授权后,用户只与权限“对话框中,如果您的应用程序的客户端ID改变或已经改变了所要求的范围。(When a userloads your application for the first time, they are presented with a dialog togrant permission for your application to access their Google Glass account withthe requested permission scopes. After this initial authorization, the user isonly presented with the permission dialog if your app's client ID changes orthe requested scopes have changed.)

验证用户的身份(Authenticate the user)

这个初始登录返回授权结果对象,它包含一个 授权码,如果成功的。(This initialsign-in returns an authorization result object that contains an authorizationcode if successful.)

交换一个访问令牌的授权码(Exchange the authorization code for an access token)

授权码是一码,您的服务器可以换取一个访问令牌。此访问令牌传递给谷歌镜API,您的应用程序访问用户的数据在有限的时间内授予。(Theauthorization code is a one-time code that your server can exchange for an accesstoken. This access token is passed to the Google Mirror API togrant your application access to user data for a limited time.)

如果你的应用程序需要脱机访问,您的应用程序第一次交流的授权码,它也收到刷新令牌,它用来接收一个新的访问令牌,一前一后的令牌已过期。你的应用程序存储供以后使用刷新令牌(通常在一个数据库服务器上)。(If yourapplication requires offline access, thefirst time your app exchanges the authorization code, it also receives a refreshtoken that it uses toreceive a new access token after a previous token has expired. Your applicationstores this refresh token (generally in a database on your server) for lateruse.)

重要提示:请务必将用户刷新令牌。如果你的应用程序需要一个新的刷新令牌,它必须与approval_prompt查询参数设置为强制发送请求。这将导致用户看到一个对话框,再次给予您的应用程序的权限。(Important: Alwaysstore user refresh tokens. If your application needs a new refresh token itmust sent a request with theapproval_prompt queryparameter set to force.This will cause the user to see a dialog to grant permission to yourapplication again.)

下面的代码示例演示了离线访问一个访问令牌的授权码交换和存储的刷新令牌。(The followingcode samples demonstrate exchanging an authorization code for an access tokenwith offline access andstoring the refresh token.)

JAVA:

Replace CLIENTSECRETS_LOCATION value withthe location of your client_secrets.json file.

Note: For moreinformation about the OAuth 2.0 package, see OAuth2 API.

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Userinfo;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

// ...

classMyClass{

// Path toclient_secrets.json which should contain a JSON document such as:
  //   {
  //    "web": {
  //      "client_id": "[[YOUR_CLIENT_ID]]",
  //      "client_secret": "[[YOUR_CLIENT_SECRET]]",
  //      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  //      "token_uri": "https://accounts.google.com/o/oauth2/token"
  //     }
  //   }
  privatestaticfinalStringCLIENTSECRETS_LOCATION ="client_secrets.json";

privatestaticfinalStringREDIRECT_URI ="<YOUR_REGISTERED_REDIRECT_URI>";
  privatestaticfinalList<String> SCOPES =Arrays.asList(
      "https://www.googleapis.com/auth/glass.timeline",
      "https://www.googleapis.com/auth/userinfo.profile");

privatestaticGoogleAuthorizationCodeFlow flow =null;

/**
   * Exception thrown when an error occurredwhile retrieving credentials.
   */
  publicstaticclassGetCredentialsExceptionextendsException{

protectedStringauthorizationUrl;

/**
     * Construct aGetCredentialsException.
     *
     * @param authorizationUrl Theauthorization URL to redirect the user to.
     */
    publicGetCredentialsException(StringauthorizationUrl){
      this.authorizationUrl= authorizationUrl;
    }

/**
     * Set the authorization URL.
     */
    publicvoidsetAuthorizationUrl(StringauthorizationUrl){
      this.authorizationUrl= authorizationUrl;
    }

/**
     * @return the authorizationUrl
     */
    publicStringgetAuthorizationUrl(){
      returnauthorizationUrl;
    }
  }

/**
   * Exception thrown when a code exchange hasfailed.
   */
  publicstaticclassCodeExchangeExceptionextendsGetCredentialsException{

/**
     * Construct a CodeExchangeException.
     *
     * @param authorizationUrl Theauthorization URL to redirect the user to.
     */
    publicCodeExchangeException(StringauthorizationUrl){
      super(authorizationUrl);
    }

}

/**
   * Exception thrown when no refresh token hasbeen found.
   */
  publicstaticclassNoRefreshTokenExceptionextendsGetCredentialsException{

/**
     * Construct aNoRefreshTokenException.
     *
     * @param authorizationUrl Theauthorization URL to redirect the user to.
     */
    publicNoRefreshTokenException(StringauthorizationUrl){
      super(authorizationUrl);
    }

}

/**
   * Exception thrown when no user ID could beretrieved.
   */
  privatestaticclassNoUserIdExceptionextendsException{
  }

/**
   * Retrieved stored credentials for theprovided user ID.
   *
   * @param userId User's ID.
   * @return Stored Credential if found, {@codenull} otherwise.
   */
  staticCredentialgetStoredCredentials(String userId){
    // TODO: Implement thismethod to work with your database. Instantiate a new
    // Credential instance withstored accessToken and refreshToken.
    thrownewUnsupportedOperationException();
  }

/**
   * Store OAuth 2.0 credentials in theapplication's database.
   *
   * @param userId User's ID.
   * @param credentials The OAuth 2.0credentials to store.
   */
  staticvoidstoreCredentials(String userId,Credentialcredentials){
    // TODO: Implement thismethod to work with your database.
    // Store thecredentials.getAccessToken() and credentials.getRefreshToken()
    // string values in yourdatabase.
    thrownewUnsupportedOperationException();
  }

/**
   * Build an authorization flow and store itas a static class attribute.
   *
   * @return GoogleAuthorizationCodeFlowinstance.
   * @throws IOException Unable to loadclient_secrets.json.
   */
  staticGoogleAuthorizationCodeFlow getFlow()throwsIOException{
    if(flow ==null){
      HttpTransporthttpTransport =newNetHttpTransport();
      JacksonFactoryjsonFactory =newJacksonFactory();
      GoogleClientSecretsclientSecrets =
          GoogleClientSecrets.load(jsonFactory,
              MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
      flow =
          newGoogleAuthorizationCodeFlow.Builder(httpTransport,jsonFactory, clientSecrets, SCOPES)
              .setAccessType("offline").setApprovalPrompt("force").build();
    }
    return flow;
  }

/**
   * Exchange an authorization code for OAuth2.0 credentials.
   *
   * @param authorizationCode Authorizationcode to exchange for OAuth 2.0
   *        credentials.
   * @return OAuth 2.0 credentials.
   * @throws CodeExchangeException An erroroccurred.
   */
  staticCredentialexchangeCode(StringauthorizationCode)
      throwsCodeExchangeException{
    try{
      GoogleAuthorizationCodeFlow flow = getFlow();
      GoogleTokenResponse response =
          flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
      return flow.createAndStoreCredential(response,null);
    }catch(IOException e){
      System.err.println("Anerror occurred: "+ e);
      thrownewCodeExchangeException(null);
    }
  }

/**
   * Send a request to the UserInfo API toretrieve the user's information.
   *
   * @param credentials OAuth 2.0 credentialsto authorize the request.
   * @return User's information.
   * @throws NoUserIdException An erroroccurred.
   */
  staticUserinfogetUserInfo(Credentialcredentials)
      throwsNoUserIdException{
    Oauth2userInfoService =
        newOauth2.Builder(newNetHttpTransport(),newJacksonFactory(),credentials).build();
    Userinfo userInfo =null;
    try{
      userInfo =userInfoService.userinfo().get().execute();
    }catch(IOException e){
      System.err.println("Anerror occurred: "+ e);
    }
    if(userInfo !=null&& userInfo.getId()!=null){
      return userInfo;
    }else{
      thrownewNoUserIdException();
    }
  }

/**
   * Retrieve the authorization URL.
   *
   * @param userId User's Google ID.
   * @param state State for the authorizationURL.
   * @return Authorization URL to redirect theuser to.
   * @throws IOException Unable to loadclient_secrets.json.
   */
  publicstaticStringgetAuthorizationUrl(String userId,String state)throwsIOException{
    GoogleAuthorizationCodeRequestUrlurlBuilder =
        getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI).setState(state);
    urlBuilder.set("user_id", userId);
    returnurlBuilder.build();
  }

/**
   * Retrieve credentials using the providedauthorization code.
   *
   * This function exchanges the authorizationcode for an access token and
   * queries the UserInfo API to retrieve theuser's Google ID. If a
   * refresh token has been retrieved alongwith an access token, it is stored
   * in the application database using theuser's Google ID as key. If no
   * refresh token has been retrieved, thefunction checks in the application
   * database for one and returns it if foundor throws a NoRefreshTokenException
   * with the authorization URL to redirect theuser to.
   *
   * @param authorizationCode Authorizationcode to use to retrieve an access
   *        token.
   * @param state State to set to theauthorization URL in case of error.
   * @return OAuth 2.0 credentials instancecontaining an access and refresh
   *         token.
   * @throws NoRefreshTokenException No refreshtoken could be retrieved from
   *         the availablesources.
   * @throws IOException Unable to loadclient_secrets.json.
   */
  publicstaticCredentialgetCredentials(StringauthorizationCode,String state)
      throwsCodeExchangeException,NoRefreshTokenException,IOException{
    String userId ="";
    try{
      Credentialcredentials = exchangeCode(authorizationCode);
      Userinfo userInfo =getUserInfo(credentials);
      userId = userInfo.getId();
      if(credentials.getRefreshToken()!=null){
        storeCredentials(userId,credentials);
        returncredentials;
      }else{
        credentials =getStoredCredentials(userId);
        if(credentials!=null&&credentials.getRefreshToken()!=null){
          returncredentials;
        }
      }
    }catch(CodeExchangeException e){
      e.printStackTrace();
      // Glassservices should try to retrieve the user and credentials for the current
      //session.
      // Ifnone is available, redirect the user to the authorization URL.
      e.setAuthorizationUrl(getAuthorizationUrl(userId, state));
      throw e;
    }catch(NoUserIdException e){
      e.printStackTrace();
    }
    // No refresh token has beenretrieved.
    StringauthorizationUrl = getAuthorizationUrl(userId, state);
    thrownewNoRefreshTokenException(authorizationUrl);
  }

}

还有Python、PHP、.NET、Ruby代码(详情请参考https://developers.google.com/glass/authorization)

 

授权与存储的凭据(Authorizing with stored credentials)

当用户访问您的应用程序成功后,首次授权流程,应用程序可以使用存储刷新令牌不提示最终用户授权请求。(When users visit your app after a successful first-timeauthorization flow, your application can use a stored refresh token toauthorize requests without prompting the end user.)

如果您已经通过验证的用户,您的应用程序可以retrive刷新令牌从它的数据库中,并存储在服务器端的会话令牌。如果刷新令牌被撤销或者无效,你会需要抓住这一点,并采取适当的行动。(If you have already authenticated the user, your applicationcan retrive the refresh token from its database and store the token in aserver-side session. If the refresh token is revoked or is otherwise invalid,you'll need to catch this and take appropriate action.)

使用OAuth 2.0凭据(Using OAuth 2.0 Credentials)

一旦已检索的OAuth 2.0认证前一节中所示,它们可以被用来授权谷歌镜API服务对象,并发送请求的API。(Once OAuth 2.0 credentials have been retrieved as shown inthe previous section, they can be used to authorize a Google Mirror API serviceobject and send requests to the API.)

下面的代码片段显示如何实例化和授权Google镜API服务的对象,并发送一个请求到谷歌镜API来检索一个时间轴项目的元数据。(The following code snippets show how to instantiate andauthorize a Google Mirror API service object and send a request to the GoogleMirror API to retrieve a timeline item'smetadata.)

实例化一个服务对象(Instantiate a service object)

此代码示例演示如何实例化一个服务对象,然后进行授权来进行API请求。(This code sample shows how to instantiate a service objectand then authorize it to make API requests.)

的Java的

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;import com.google.api.services.mirror.Mirror;
// ...publicclassMyClass{// .../*** Build a Mirror service object.** @param credentials OAuth 2.0 credentials.* @return Mirror service object.*/staticMirror buildService(GoogleCredential credentials){HttpTransport httpTransport =newNetHttpTransport();JacksonFactory jsonFactory =newJacksonFactory();returnnewMirror.Builder(httpTransport, jsonFactory, credentials).build();}// ...
}

发送授权请求,并检查撤销的凭据(Send authorized requests and check for revoked credentials)

下面的代码片段使用谷歌授权的镜子API服务实例,并发送一个授权,以谷歌镜API的GET请求来检索一个 时间轴项目的元数据。(The following code snippet uses an authorized Google MirrorAPI service instance and sends an authorized GET request to theGoogle Mirror API to retrieve a timeline item'smetadata)

如果发生错误,应由授权的URL将用户重定向到一个HTTP 401状态代码,代码检查。

更多谷歌镜API操作都记录在API参考。(If an error occurs, the code checks for an HTTP 401 status code, which should be handled by redirecting the userto the authorization URL.)

更多谷歌镜API操作都记录在API参考。(More Google Mirror API operations are documented in theAPI Reference.)

的Java

import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseException;import com.google.api.services.mirror.Mirror;
import com.google.api.services.mirror.model.TimelineItem;import java.io.IOException;
// ...publicclassMyClass{// .../*** Print a timeline item's metadata.** @param service Mirror service instance.* @param itemId ID of the timeline item to print metadata for.*/staticvoid printTimelineItem(Mirror service,String itemId){try{TimelineItem item = service.timeline().get(itemId).execute();System.out.println("Text: "+ item.getText());System.out.println("HTML: "+ item.getHtml());}catch(HttpResponseException e){if(e.getStatusCode()==401){// Credentials have been revoked.// TODO: Redirect the user to the authorization URL and/or//       remove the credentials from the database.thrownewUnsupportedOperationException();}}catch(IOException e){System.out.println("An error occurred: "+ e);}}// ...
}

下一步

您可以了解更多有关可用的API方法在 API参考,并检查一些工作代码,您可以查看我们的终端到终端的 示例应用程序。

You can learn more about available API methods in the API Reference, and you can review our end-to-end Example Apps to examine some working code.

关于媒体上传(About Media Upload)

谷歌镜API允许您创建一个新的时间表项目时插入附件。(The Google Mirror API allows you to insert an attachment whencreating a new timeline item.)

内容

1.       上传选项

1.       简单的上传

2.       多部分上传

3.       可恢复上传

2.       最佳实践

1.       指数退避

上传选项(Upload options)

谷歌镜API允许你上传某些类型的二进制数据,或媒体。指定的参考页上的数据,你可以上传任何媒体上传的方法,支持的具体特点:(The Google Mirror API allows you to upload certain types ofbinary data, or media. The specific characteristics of the data you can uploadare specified on the reference page for any method that supports media uploads:)

·        最大上传文件大小:你可以用这种方法存储的最大数据量。(Maximum upload file size: The maximum amount of data you can store with this method.)

·        接受了媒体的MIME类型:使用这种方法可以存储二进制数据的类型。(Accepted media MIME types: The types of binary data you can store using this method.)

有三种不同的方式,使上传请求,你指定你的方法用uploadType请求参数:(There are three different ways to make upload requests, andyou specify the method you are using with the uploadTyperequestparameter:)

·        简单的上传:uploadType=媒体。对于较小的文件,例如,5 MB或更少的快速传输。(Simple upload: uploadType=media. For quick transfer of smaller files, for example, 5 MB orless.)

·        多部分上传:uploadType“=多重。对于较小的文件和元数据的快速传输,传输文件以及描述它的元数据,都在一个单一的请求。(Multipartupload: uploadType=multipart. For quick transfer of smaller files and metadata; transfersthe file along with metadata that describes it, all in a single request.)

·        可恢复上传:uploadType=可恢复。对于可靠传输更大的文件,尤其是重要的。使用这种方法,您可以使用会话发起请求时,可以选择包括元数据。对于大多数应用程序使用,这是一个很好的策略,因为它也适用于较小的文件,在每次上传一个额外的HTTP请求的成本。(Resumableupload: uploadType=resumable. For reliable transfer, especially important with largerfiles. With this method, you use a session initiating request, which optionallycan include metadata. This is a good strategy to use for most applications,since it also works for smaller files at the cost of one additional HTTPrequest per upload.)

当你上传媒体时,您可以使用一个特殊的URI。事实上,支持媒体上传的方法有两个URI终点:(When you upload media, you use a special URI. In fact,methods that support media uploads have two URI endpoints:)

·        /上传URI,对于媒体的格式上传端点资源的URI是标准的“上传/”前缀。传输媒体数据本身时,使用此URI。示例:POST/ upload/mirror/v1/timeline。(The /uploadURI, for the media. The format ofthe upload endpoint is the standard resource URI with an “/upload” prefix. Usethis URI when transferring the media data itself. Example: POST/upload/mirror/v1/timeline.)

·        的是标准的资源的URI,元数据,如果资源中包含的任何数据信息,这些字段用于存储上载的文件的元数据描述。创建或更新元数据值时,您可以使用这个URI。示例: POST / mirror/v1/timeline。(The standardresource URI, for the metadata. If the resource contains any data fields, those fields areused to store metadata describing the uploaded file. You can use this URI whencreating or updating metadata values. Example:POST/mirror/v1/timeline.)

简单的上传(Simpleupload)

上传文件的最直接的方法是通过一个简单的上传请求。此选项是一个不错的选择:(The most straightforward method for uploading a fileis by making a simple upload request. This option is a good choice when:)

·        该文件是足够小,在其全部重新上传,如果连接失败。(The file issmall enough to upload again in its entirety if the connection fails.)

·        有没有元数据发送。这可能是真实的,如果你打算在一个单独的请求,发送此资源的元数据,或者没有元数据支持或提供。(There is nometadata to send. This might be true if you plan to send metadata for thisresource in a separate request, or if no metadata is supported or available.)

要使用简单的上传,做一个POSTPUT方法/上传 URI 请求,并添加查询参数 uploadType=媒体。例如:(To use simple upload, make a POST or PUT request to themethod's /upload URI and add the query parameteruploadType=media. For example:)

POST https://www.googleapis.com/upload/mirror/v1/timeline?uploadType=media

HTTP头时要使用一个简单的上传请求包括:(The HTTP headers to use when making a simple uploadrequest include:)

·        Content-Type。设置方法接受上传的媒体数据类型,在指定的API 参考之一。(Content-Type. Set to one of the method's accepted upload media datatypes, specified in the API reference.)

·        Content-Length。设置上传字节数。不是必需的,如果您使用的是分块传输编码。(Content-Length. Set to the number of bytes you are uploading. Not requiredif you are using chunkedtransfer encoding.)

例如:简单的上传(Example:Simple upload)

下面的例子显示了谷歌镜API使用一个简单的照片上传请求。(The following example shows the use of a simple photoupload request for the Google Mirror API.)

POST /upload/mirror/v1/timeline?uploadType=media HTTP/1.1
Host: www.googleapis.com
Content-Type: image/jpeg
Content-Length: number_of_bytes_in_JPEG_file
Authorization: your_auth_token
JPEG data

如果请求成功,服务器返回的HTTP 200 OK状态代码以及任何元数据:(If the request succeeds, the server returns the HTTP 200 OK status code along with any metadata:)

HTTP/1.1 200
Content-Type: application/json
{
  "text": "Hello world!"
}

多部分上传(Multipartupload)

如果你有元数据要上传的数据一起发送,你可以做一个单一的多重/相关请求。,媒体只用简单的请求,这是一个很好的选择,如果你发送的数据是足够小,在其全部重新上传,如果连接失败。(If you have metadata that you want to send along withthe data to upload, you can make a single multipart/related request. As with simple, media-only requests, this is a goodchoice if the data you are sending is small enough to upload again in itsentirety if the connection fails)

要使用多上传,做一个POSTPUT请求到方法/上传的 URI和添加查询参数 uploadType=多重的,例如:(To use multipart upload, make a POST or PUT request to themethod's /upload URI and add the query parameteruploadType=multipart, for example:)

POST https://www.googleapis.com/upload/mirror/v1/timeline?uploadType=multipart

顶级的HTTP标头时要使用一个多上传请求包括:(The top-level HTTP headers to use when making amultipart upload request include:)

·        Content-Type。设置多重/相关,包括你使用的边界字符串识别部分请求。(Content-Type. Set to multipart/related and include the boundarystring you're using to identify the parts of the request.)

·        Content-Length。设置在请求体中的总字节数。请求的媒体部分必须是低于此方法指定的最大文件大小。(Content-Length. Set to the total number of bytes in the request body. Themedia portion of the request must be less than the maximum file size specifiedfor this method.)

身体的请求被格式化为一个多重/相关内容类型[ RFC2387 ]和包含恰好两个部分。部分被确定边界字符串,并最终边界字符串后面是两个连字符。(The body of the request is formatted as a multipart/related content type [RFC2387] and contains exactly two parts. The parts are identified bya boundary string, and the final boundary string is followed by two hyphens.)

多重请求的每个部分都需要一个额外的内容类型标题:(Each part of the multipart request needs an additional Content-Type header:)

1.       元数据的一部分:必须先来的,公认的元数据格式,内容类型必须匹配。(Metadata part: Must come first, and Content-Type must match oneof the the accepted metadata formats.)

2.       媒体部分:必须拿出第二个,内容类型必须符合其中一个方法的接受了媒体的MIME类型。(Media part: Must comesecond, and Content-Type must match onethe method's accepted media MIME types)

接受的MIME类型和大小限制上传文件的每个方法的列表,请参见API 参考。(See the API reference for each method's list of accepted MIME types and size limitsfor uploaded files.)

注:要创建或更新元数据部分,没有上传相关的数据,只需发送POSTPUT请求标准资源端点:https://www.googleapis.com/mirror/v1/timeline(Note: To create or update the metadata portion only, withoutuploading the associated data, simply send a POST or PUTrequest to the standard resource endpoint: https://www.googleapis.com/mirror/v1/timeline)

例如:多重上传(Example:Multipart upload)

下面的例子显示了一个多为谷歌镜API的上传请求。(The example below shows a multipart upload request forthe Google Mirror API.)

POST /upload/mirror/v1/timeline?uploadType=multipart HTTP/1.1
Host: www.googleapis.com
Authorization: your_auth_token
Content-Type: multipart/related; boundary="foo_bar_baz"
Content-Length: number_of_bytes_in_entire_request_body
 
--foo_bar_baz
Content-Type: application/json; charset=UTF-8
 
{
  "text": "Hello world!"
}
 
--foo_bar_baz
Content-Type: image/jpeg
 
JPEG data
 
--foo_bar_baz--

如果请求成功,服务器返回的HTTP 200 OK状态代码以及任何元数据:(If the request succeeds, the server returns the HTTP 200 OK status codealong with any metadata:)

HTTP/1.1 200
Content-Type: application/json
 
{
  "text": "Hello world!"
}

可恢复上传(Resumableupload)

上传数据文件,你可以使用更可靠可恢复上传协议。该协议允许您恢复通信故障后的数据流中断的上传操作。这是特别有用的,如果传输大文件和网络中断或其他一些传输失败的可能性高,例如,当从移动客户端应用程序上传。它也可以减少你的带宽使用情况在发生网络故障,因为你没有重新启动从一开始就大文件上传。(To upload data files more reliably, you can use theresumable upload protocol. This protocol allows you to resume an uploadoperation after a communication failure has interrupted the flow of data. It isespecially useful if you are transferring large files and the likelihood of anetwork interruption or some other transmission failure is high, for example,when uploading from a mobile client app. It can also reduce your bandwidthusage in the event of network failures because you don't have to restart largefile uploads from the beginning.)

上传使用可恢复的步骤包括:(The steps for using resumable upload include:)

1.       启动可恢复届。车型的初始请求上传的URI,包括元数据,如果有的话。(Start aresumable session. Make aninitial request to the upload URI that includes the metadata, if any)

2.       保存可恢复届URI。保存会话URI将在初始请求的响应返回,你就可以使用它在这个环节中剩余的请求。(Save theresumable session URI. Save thesession URI returned in the response of the initial request; you'll use it forthe remaining requests in this session.)

3.       上传文件。发送媒体文件可恢复会话URI。(Upload thefile. Send the media file tothe resumable session URI.)

此外,应用程序使用可恢复上传需要有代码恢复中断的上传。如果上传中断,找出有多少数据被成功接收,然后继续上传从该点出发。(In addition, apps that use resumable upload need tohave code to resume an interruptedupload. If an upload isinterrupted, find out how much data was successfully received, and then resumethe upload starting from that point.)

第1步- 启动可恢复会话(Step1 - Start a resumable session)

要启动一个可恢复的上传,做一个POSTPUT请求方法的上传/ URI和添加查询的参数 uploadType=可恢复的,例如:(To initiate a resumable upload, make a POST or PUT request to themethod's /upload URI and add the query parameteruploadType=resumable, for example:)

POST https://www.googleapis.com/upload/mirror/v1/timeline?uploadType=resumable

这个发起请求,身体是空的,或者只包含元数据,你会转移你想要的文件上传在随后的请求的实际内容。(For this initiating request, the body is either emptyor it contains the metadata only; you'll transfer the actual contents of thefile you want to upload in subsequent requests.)

使用下面的HTTP头的初始请求:(Use the following HTTP headers with the initial request:)

·        X-上传的内容类型。设置媒体上载要传输的数据在随后的请求的MIME类型。(X-Upload-Content-Type. Set to the media MIME type of the upload data to betransferred in subsequent requests.)

·        X-上传的内容长度。设置为上载的数据的字节的数目在随后的请求中被转移的。如果长度未知,在申请的时候,你可以省略这个头。(X-Upload-Content-Length. Set to the number of bytes of upload data to be transferredin subsequent requests.  If the length is unknown at the time of thisrequest, you can omit this header)

·        如果提供元数据的内容类型。根据元数据的数据类型设置。(If providingmetadata: Content-Type. Setaccording to the metadata's data type)

·        Content-Length。设置为在体内提供了此初始请求的字节数。不是必需的,如果您使用的是分块传输编码。(Content-Length. Set to the number of bytes provided in the body of thisinitial request. Not required if you are usingchunkedtransfer encoding)

接受了媒体的MIME类型和上传文件的大小限制为每个方法的列表,请参见API 参考。(See the API reference for each method's list of accepted media MIME types and sizelimits for uploaded files.)

例如:可恢复的会话发起请求(Example: Resumable session initiation request)

下面的例子演示如何启动对谷歌镜API的可恢复会话。(The following example shows how to initiate aresumable session for the Google Mirror API.)

POST /upload/mirror/v1/timeline?uploadType=resumable HTTP/1.1
Host: www.googleapis.com
Authorization: your_auth_token
Content-Length: 38
Content-Type: application/json; charset=UTF-8
X-Upload-Content-Type: image/jpeg
X-Upload-Content-Length: 2000000
 
{
  "text": "Hello world!"
}

注:对于初始可恢复没有元数据的更新请求,请求空不离身,并设置为0 Content-Length头。(Note: Foran initial resumable update request without metadata, leave the body of therequest empty, and set the Content-Length header to 0.)

下一节介绍了如何处理响应。(The next section describes how to handle the response.)

第2步- 保存可恢复会话URI(Step2 - Save the resumable session URI)

如果会话发起请求成功,API服务器响应一个200 OK HTTP状态代码。此外,它提供了一个位置头指定你的可恢复会话URI。地点头,在下面的例子所示,包括upload_id查询参数部分,给人独特的上传ID用于本次会议。(If the session initiation request succeeds, the APIserver responds with a 200 OK HTTP statuscode. In addition, it provides aLocation header that specifies your resumable session URI. The Location header, shown in the example below, includes anupload_id queryparameter portion that gives the unique upload ID to use for this session.)

例如:可恢复的会话发起响应(Example: Resumable session initiation response)

下面是在步骤1中的请求的响应:(Here is the response to the request in Step 1:)

HTTP/1.1 200 OK
Location: https://www.googleapis.com/upload/mirror/v1/timeline?uploadType=resumable&upload_id=xa298sd_sdlkj2
Content-Length: 0

位置标题所示,在上面的例子响应的价值,你会使用会话URIHTTP端点做实际的文件上传或上传状态查询。(The value of the Location header, asshown in the above example response, is the session URI you'll use as the HTTPendpoint for doing the actual file upload or querying the upload status.)

复制并保存会话的URI,所以你可以使用它的后续请求。(Copy and save the session URI so you can use it forsubsequent requests.)

第3步- 上传文件(Step3 - Upload the file)

要上传的文件,上传URI 发送一个PUT请求,你在上一步中得到。上载请求的格式是:(To upload the file, send a PUT request to the upload URI that you obtained in the previousstep. The format of the upload request is:)

PUT {session_uri}

可恢复的文件上传请求时使用的HTTP标头包含内容长度。您上传的这一要求,这是一般的上传文件大小的字节数。(The HTTP headers to use when making the resumable fileupload requests includes Content-Length. Set this to the number of bytes you are uploading in thisrequest, which is generally the upload file size.)

例如:可恢复的文件上传请求(Example: Resumable file upload request)

这里是一个可恢复的请求上传整个文件2,000,000字节照片当前的例子。(Here is a resumable request to upload the entire2,000,000 byte photo file for the current example.)

PUT https://www.googleapis.com/upload/mirror/v1/timeline?uploadType=resumable&upload_id=xa298sd_sdlkj2 HTTP/1.1
Authorization: {your_auth_token}
Content-Length: 2000000
Content-Type: image/jpeg
 
bytes 0-1999999

如果请求成功,服务器响应一个HTTP201创建,以及任何与此资源相关的元数据。如果可恢复会话初始请求一直是PUT,更新现有的资源,成功响应   200 OK,以及任何与此资源相关的元数据。(If the request succeeds, the server responds with an HTTP 201Created, along with any metadata associated with thisresource. If the initial request of the resumable session had been a PUT, to update an existing resource, the success response wouldbe  200 OK, along with any metadata associated with this resource.)

如果被中断或上传请求,如果你收到一个HTTP503服务不可用或任何其他5xx来自服务器的响应,恢复中断的上传程序。  (If the upload request is interrupted or if you receivean HTTP 503 Service Unavailable or any other 5xx response fromthe server, follow the procedure outlined in resume aninterrupted upload.  )


上传文件块(Uploading the file in chunks)

可恢复上传,你可以打破成块的文件,并发送了一系列要求上传每个块的顺序。这不是首选的方法,因为有相关的性能代价的​​额外要求,一般不需要。但是,您可能需要使用块,以减少任何单个请求中的数据传输量。这是有帮助的,当有一个固定的时间限制个人的请求,是真的为某些类别的谷歌应用程序引擎请求。它也可以让你做的事情一样提供传统的浏览器不支持上传进度默认情况下,上传进度指示。(With resumable uploads, you can break a file intochunks and send a series of requests to upload each chunk in sequence. This isnot the preferred approach since there are performance costs associated withthe additional requests, and it is generally not needed. However, you mightneed to use chunking to reduce the amount of data transferred in any singlerequest. This is helpful when there is a fixed time limit for individualrequests, as is true for certain classes of Google App Engine requests. It alsolets you do things like providing upload progress indications for legacybrowsers that don't have upload progress support by default.)

展开更多信息

恢复中断的上传(Resumean interrupted upload)

如果上传请求接收响应或之前被终止,如果你收到一个HTTP 503服务不可用来自服务器的响应,那么你需要恢复中断的上传。要做到这一点:(If an upload request is terminated before receiving aresponse or if you receive an HTTP 503 Service Unavailable response from the server, then you need to resume the interruptedupload. To do this:)

1.       请求状态查询上载的当前状态,通过发出一个空的PUT请求上载的URI。对于这个请求,HTTP头的内容范围应包括头表示,目前在文件中的位置是未知的。例如,设置的内容范围/2000000 *如果你的文件总长度为2,000,000。如果你不知道该文件的完整大小,* / *设置的内容范围。(Requeststatus. Query thecurrent status of the upload by issuing an empty PUT request to the upload URI. For this request, the HTTP headersshould include a Content-Range headerindicating that the current position in the file is unknown.  For example,set the Content-Range to */2000000 if your total file length is 2,000,000. If you don't know thefull size of the file, set the Content-Range to */*.)

注:您可以要求块之间的状态,不只是如果上传中断。这是非常有用的,例如,如果你想显示上传进度指示旧版浏览器。(Note: You can request the status between chunks, not just if theupload is interrupted. This is useful, for example, if you want to show uploadprogress indications for legacy browsers.)

2.       上传字节数,处理状态查询的响应。服务器使用范围在其指定的字节迄今已收到的响应头。例如,范围0-299999表示标题已被收到的第一个30字节的文件。(Get number of bytes uploaded. Process the response from the status query. The server usesthe Range header in itsresponse to specify which bytes it has received so far.  For example, a Range header of 0-299999 indicates thatthe first 300,000 bytes of the file have been received.)

3.       剩余的数据上传。最后,既然你知道在哪里恢复请求,发送剩余的数据或当前块。请注意,您需要把剩余的数据在任何情况下作为一个单独的块,所以你需要发送的内容范围头恢复时上传。(Uploadremaining data. Finally, nowthat you know where to resume the request, send the remaining data or currentchunk. Note that you need to treat the remaining data as a separate chunk ineither case, so you need to send theContent-Range header when you resume the upload.)

例如:恢复中断的上传(Example: Resuming an interrupted upload)

1)请上传状态。(Request the upload status.)

以下请求使用内容范围的标题表明,2,000,000字节的文件中的当前位置是未知的。(The following request uses the Content-Range header to indicate that the current position in the 2,000,000byte file is unknown.)

PUT {session_uri} HTTP/1.1
Authorization: your_auth_token
Content-Length: 0
Content-Range: bytes */2000000

2)提取物上载了从响应中的字节的数量。(Extract the number of bytes uploaded so far from theresponse.)

服务器的响应使用范围头,以表明它到目前为止已收到第43个字节的文件。使用范围的上限值头以确定从哪里开始恢复上传。(The server's response uses the Range header to indicate that it has received the first 43 bytes ofthe file so far. Use the upper value of the Range header todetermine where to start the resumed upload.)

HTTP/1.1 308 Resume Incomplete
Content-Length: 0
Range: 0-42

注:这是可能的,如果上传完成状态响应可能是201创建200 OK。这可能发生,如果发生连接所有字节后上传的,但在此之前从服务器收到客户端的响应。(Note: Itis possible that the status response could be 201Created or 200 OK if the upload is complete. This could happen if theconnection broke after all bytes were uploaded but before the client received aresponse from the server.)

3)继续上传,从离开的地方。(Resume the upload from the point where it left off.)

恢复下面的请求通过发送该文件的剩余的字节,从字节43开始上载。(The following request resumes the upload by sendingthe remaining bytes of the file, starting at byte 43.)

PUT {session_uri} HTTP/1.1
Authorization: your_auth_token
Content-Length: 1999957
Content-Range: bytes 43-1999999/2000000
 
bytes 43-1999999

最佳实践

当上传的媒体,它是有帮助的认识是错误处理相关的一些最佳实践。(When uploading media, it is helpful to be aware ofsome best practices related to error handling.)

·        恢复或重试失败的上传,由于连接中断或任何5xx错误,包括:(Resume orretry uploads that fail due to connection interruptions or any 5xx errors, including:)

o    500 Internal Server Error

o    502 Bad Gateway

o    503 Service Unavailable

o    504 Gateway Timeout

 

o    500内部服务器错误

o    502坏网关

o    503服务不可用

o    504网关超时

·        使用指数退避策略,如果任何5XX服务器时会返回错误恢复或重试上传请求。这些错误可能发生,如果服务器挤破头。指数退避期间请求重或网络通信量高,可以有助于缓解这类问题。(Use an exponentialbackoff strategy ifany 5xx server erroris returned when resuming or retrying upload requests. These errors can occurif a server is getting overloaded. Exponential backoff can help alleviate thesekinds of problems during periods of high volume of requests or heavy networktraffic.)

·        其他种类的请求不应该被处理的指数退避,但你仍然可以重试一些。当这些请求重试,限制重试他们的时候,你的数量。例如,你的代码可能会限制到十重试或更少,然后再报告错误。(Other kinds ofrequests should not be handled by exponential backoff but you can still retry anumber of them. When retrying these requests, limit the number of times youretry them. For example your code could limit to ten retries or less beforereporting an error.)

·        处理404未找到错误做可恢复上传时,开始从一开始整个上传。(Handle 404 Not Found errors when doing resumable uploads by starting the entireupload over from the beginning.)

指数退避(Exponentialbackoff)

指数回退是一个标准的网络应用程序的错误处理策略在客户定期重试失败的请求在一个越来越多的时间。如果请求或高容量的网络通信负担沉重导致服务器返回的错误,,指数退避可能是一个很好的策略来处理这些错误。相反,它不是一个有关处理无关的网络卷或响应时间错误,如无效的授权证书或文件未找到错误的战略。(Exponential backoff is a standard error handlingstrategy for network applications in which the client periodically retries afailed request over an increasing amount of time. If a high volume of requestsor heavy network traffic causes the server to return errors, exponentialbackoff may be a good strategy for handling those errors. Conversely, it is nota relevant strategy for dealing with errors unrelated to network volume orresponse times, such as invalid authorization credentials or file not founderrors.)

使用得当,指数退避增加的带宽使用效率,减少需要得到一个成功的响应的请求的数目,在并发环境的请求的吞吐量最大化。(Used properly, exponential backoff increases theefficiency of bandwidth usage, reduces the number of requests required to get asuccessful response, and maximizes the throughput of requests in concurrentenvironments.)

实施简单的指数退避的流程如下:(The flow for implementing simple exponential backoffis as follows:)

1.       提出请求的API。

2.       接收HTTP503响应,这表明你应该重新尝试请求。

3.       等待1第二+random_number_milliseconds的,再重试要求。

4.       接收HTTP503响应,这表明你应该重新尝试请求。

5.       等待2秒+random_number_milliseconds,再重试要求。

6.       接收HTTP503响应,这表明你应该重新尝试请求。

7.       等待4秒+random_number_milliseconds,再重试要求。

8.       接收HTTP503响应,这表明你应该重新尝试请求。

9.       等待8秒+random_number_milliseconds,再重试要求。

10.    接收HTTP503响应,这表明你应该重新尝试请求。

11.    等待16秒+random_number_milliseconds,再重试要求。

12.    停止。报告或记录错误。

· 
Make a request to the API.

·  Receive an HTTP 503 response, which indicates you should retry the request.

·  Wait 1 second + random_number_milliseconds and retry the request.

·  Receive an HTTP 503 response, which indicates you should retry the request.

·  Wait 2 seconds + random_number_milliseconds, and retry the request.

·  Receive an HTTP 503 response, which indicates you should retry the request.

·  Wait 4 seconds + random_number_milliseconds, and retry the request.

·  Receive an HTTP 503 response, which indicates you should retry the request.

·  Wait 8 seconds + random_number_milliseconds, and retry the request.

·  Receive an HTTP 503 response, which indicates you should retry the request.

·  Wait 16 seconds + random_number_milliseconds, and retry the request.

·  Stop. Report or log an error.

在上述流程中,random_number_milliseconds是一个随机数小于或等于1000毫秒。这是必要的,因为引入一个小的随机延迟有助于更均匀地分配负载,避免乱窜服务器的可能性。每个等待后,必须重新定义的价值random_number_milliseconds的。(In the above flow, random_number_milliseconds is arandom number of milliseconds less than or equal to 1000. This is necessary,since introducing a small random delay helps distribute the load more evenlyand avoid the possibility of stampeding the server. The value ofrandom_number_milliseconds must be redefined after each wait.)

注:等待始终是(2 ^ n)的+random_number_milliseconds,其中n是一个单调递增的整数,最初定义为0。整数n递增1,在每次迭代时(每个请求)。(Note: Thewait is always (2 ^ n) + random_number_milliseconds, where n is a monotonicallyincreasing integer initially defined as 0. The integer n is incremented by 1for each iteration (each request).)

当n为5时,该算法被设置为终止。这个上限可以防止客户端无限重试,结果在大约32秒的总延迟请求前被认为是“不可恢复的错误。” 较大的最大重试次数是好的,尤其是如果长时间上传正在进行中,只要是合理的东西,比方说,不到一分钟,一定要重试延迟封顶。(The algorithm is set to terminate when n is 5. Thisceiling prevents clients from retrying infinitely, and results in a total delayof around 32 seconds before a request is deemed "an unrecoverableerror." A larger maximum number of retries is fine, especially if a longupload is in progress; just be sure to cap the retry delay at somethingreasonable, say, less than one minute.)

Android修炼之道——GoogleGlass开发指南相关推荐

  1. Android P适配以太网功能开发指南

            Android P适配以太网功能开发指南 Android网络框架分析系列文章目录: Android P适配以太网功能开发指南 Android以太网框架情景分析之启动简介 Android ...

  2. [读书笔记] 《修炼之道:.NET 开发要点精讲》

    <修炼之道:.NET 开发要点精讲> 目录 <修炼之道:.NET 开发要点精讲> 第 1 章 另辟蹊径:解读.NET 1.7 本章思考 > 位置 465 第 2 章 高屋 ...

  3. android学习-有道词典开发实例

    最近学习android程序开发,在网看上到一个关于android手机开发有道词典的例子.但是,并不能正常运行,现在个人改进版本源代码和思路献上之供学习之用. 第一步,申请API key,申请地址:ht ...

  4. Android端实时音视频开发指南

    简介 yun2win-sdk-Android提供Android端实时音视频完整解决方案,方便客户快速集成实时音视频功能. SDK 提供的能力如下: 发起 加入 AVClient Channel AVM ...

  5. Android微信分享功能集成开发指南(这一篇就够了)

    前言 本文介绍的只是微信开发中的一个功能,分享.看到网上很多关于微信分享的博客.帖子,说实话,没几篇写的全的,很多都是复制粘贴,介绍的也不全,缺少代码的分析,关键性的代码总是漏一句两句,看着就很难受. ...

  6. Android修炼之道—Talker聊天小工具

    这两天,参照陌陌的聊天界面,帮同学毕业设计做了一个聊天的小项目--Talker. 主要结合了JPush完成消息的推送. 单纯的聊天功能,写起来并不轻松,陌陌里面有大量的自定义控件,而且整个文件代码的架 ...

  7. 敏捷开发修炼之道 (一)高效软件开发之道、态度决定一切

    第1章:敏捷 - 高效软件开发之道 在软件开发领域里,在项目研发过程中出现的需求变化和挑战就是你在冲浪时要应对的海浪 - 它们从不停止并且永远变化,像波浪一样.在不同的业务领域和应用下,软件项目具有不 ...

  8. Web前端开发敲门砖 ——《Web前端工程师修炼之道》

    随着多设备.浏览器和Web标准的演变革命,前端正在成为兼顾逻辑.性能.交互.体验的综合性岗位. 前端开发入门又相对容易,必须掌握的HTML+CSS+JS非常容易学习,如果你能再了解一定后端知识,对业务 ...

  9. 读书笔记:编写高质量代码--web前端开发修炼之道(二:5章)

    读书笔记:编写高质量代码--web前端开发修炼之道 这本书看得断断续续,不连贯,笔记也是有些马虎了,想了解这本书内容的童鞋可以借鉴我的这篇笔记,希望对大家有帮助. 笔记有点长,所以分为一,二两个部分: ...

最新文章

  1. [公告] TechNet / MSDN 经理人博客上周移机整合暂断
  2. R eentrantLock的源码分析
  3. 王者荣耀服务器维护bug,8月23日王者荣耀ios版更新一直显示维护是什么情况?更新出现bug 附处理方法...
  4. Linux常用命令拾遗
  5. 诺基亚五摄手机国行版终于来了:下午见!
  6. Installation of Ubuntu source-insight
  7. 什么是事务ACID原则?(建议收藏)
  8. rost反剽窃检测系统_剽窃上瘾了?该戒戒了
  9. Intellij IDEA--修改JDK版本
  10. 手写原笔迹输入_OneNote/YouNote原笔迹手写_原道 W8S_平板电脑评测-中关村在线
  11. httpclient3 自动登陆淘宝, 开心网
  12. 使用opencv批量裁剪保存图片
  13. win10 eclipse适配笔记本4K屏幕
  14. 高性能服务器dyna软件重启动,LSDYNA重启动教程 ppt课件.ppt
  15. 从“中央厨房”看媒体深度融合
  16. Debian 配置RTL8723BU连接wifi网络
  17. ​Vue 3 这个坑我踩了,你们一定要小心
  18. 某基于DEDECMS5.5网站的安全检测初步报告
  19. css元素发光效果图,纯CSS3实现圆圈动态发光特效动画的示例代码
  20. python ipaddress模块使用

热门文章

  1. Unity3D普通开发人员,主程分别需要掌握的技能
  2. vs2013由修改模式改为输入模式。
  3. 找电子书一个网站就够:数字图书馆 Zlibrary最新可用网址,持续更新
  4. 海康威视:AI 芯片很难被管制,海外市场值得期待
  5. 小型水库雨水情测报和安全监测解决方案
  6. 两栏布局与三栏布局(圣杯布局与双飞翼布局)
  7. 扫雷游戏的实现C++
  8. 传奇人物《周兴和》书连载20 内外交困搞发明
  9. 小功率机械摩擦式无级变速器结构设计说明书毕业设计全套
  10. 【调剂】上海应用技术大学2020年硕士研究生招生考试调剂信息