Hướng dẫn này giải thích cách thiết lập một ứng dụng Android đơn giản để gửi yêu cầu đến API Dữ liệu của YouTube.
Điều kiện tiên quyết
Để chạy hướng dẫn nhanh này, bạn cần có:
- Android Studio SDK 1.2 trở lên.
- Các gói SDK Android dành cho API 23 trở lên, bao gồm các phiên bản mới nhất của Kho lưu trữ của Google, Thư viện hỗ trợ Android và Dịch vụ Google Play.
- Quyền truy cập Internet trên thiết bị thử nghiệm.
- Tài khoản Google.
Phần hướng dẫn nhanh này sẽ giả định rằng bạn đang sử dụng IDE Android Studio (thay vì Bộ công cụ SDK độc lập) và có thể dễ dàng tìm, tạo và chỉnh sửa tệp trong một dự án Studio.
Bước 1: Thu thập vân tay số SHA1
Trong dòng lệnh, hãy chạy lệnh tiện ích Keytool sau đây để lấy vân tay SHA1 mà bạn sẽ sử dụng để bật API.
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v
Khi được yêu cầu nhập mật khẩu kho khoá, hãy nhập "android".
Keytool in vân tay vào shell. Ví dụ:
$ keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v Enter keystore password: Type "android" if using debug.keystore Alias name: androiddebugkey Creation date: Dec 4, 2014 Entry type: PrivateKeyEntry Certificate chain length: 1 Certificate[1]: Owner: CN=Android Debug, O=Android, C=US Issuer: CN=Android Debug, O=Android, C=US Serial number: 503bd581 Valid from: Mon Aug 27 13:16:01 PDT 2012 until: Wed Aug 20 13:16:01 PDT 2042 Certificate fingerprints: MD5: 1B:2B:2D:37:E1:CE:06:8B:A0:F0:73:05:3C:A3:63:DD SHA1: D8:AA:43:97:59:EE:C5:95:26:6A:07:EE:1C:37:8E:F4:F0:C8:05:C8 SHA256: F3:6F:98:51:9A:DF:C3:15:4E:48:4B:0F:91:E3:3C:6A:A0:97:DC:0A:3F:B2:D2:E1:FE:23:57:F5:EB:AC:13:30 Signature algorithm name: SHA1withRSA Version: 3
Sao chép vân tay SHA1 được nêu bật trong ví dụ trên.
Bước 2: Bật API Dữ liệu YouTube
-
Sử dụng trình hướng dẫn này để tạo hoặc chọn một dự án trong Google Developers Console và tự động bật API. Nhấp vào Tiếp tục, rồi nhấp vào Chuyển đến thông tin xác thực.
-
Trên trang Tạo thông tin xác thực, hãy nhấp vào nút Huỷ.
-
Ở đầu trang, hãy chọn thẻ Màn hình xin phép bằng OAuth. Chọn Địa chỉ email, nhập Tên sản phẩm nếu bạn chưa đặt tên, rồi nhấp vào nút Lưu.
-
Chọn thẻ Thông tin xác thực, nhấp vào nút Tạo thông tin xác thực rồi chọn Mã ứng dụng khách OAuth.
- Chọn loại ứng dụng Android.
- Sao chép dấu vân tay SHA1 từ Bước 1 vào trường Dấu vân tay chứng chỉ ký.
- Trong trường Package name (Tên gói), hãy nhập
com.example.quickstart
. - Nhấp vào nút Tạo.
Bước 3: Tạo một dự án Android mới
- Mở Android Studio rồi bắt đầu một Dự án Android Studio mới.
- Trong màn hình New Project (Dự án mới), hãy đặt tên ứng dụng là "Quickstart" (Khởi động nhanh).
- Đặt Miền công ty thành "example.com" và xác minh rằng tên gói được tạo tự động khớp với tên bạn đã nhập vào Developer Console ở Bước 2. Nhấp vào Next (Tiếp theo).
- Trên màn hình Target Android Devices (Thiết bị Android mục tiêu), hãy đánh dấu vào hộp đánh dấu Phone and Tablet (Điện thoại và máy tính bảng) rồi chọn Minimum SDK (SDK tối thiểu) là "API 14: Android 4.0 (IceCreamSandwich)". Bỏ đánh dấu các hộp đánh dấu khác. Nhấp vào Next (Tiếp theo).
- Trên màn hình Thêm hoạt động vào thiết bị di động, hãy nhấp vào Không thêm hoạt động nào.
- Nhấp vào Hoàn tất.
Tại thời điểm này, Android Studio sẽ tạo và mở dự án.
Bước 4: Chuẩn bị dự án
Thanh bên Dự án là một danh sách có thể mở rộng gồm các tệp dự án mặc định mà Android Studio đã tạo. Trong danh sách đó, hãy mở rộng danh sách tập lệnh Gradle và mở tệp build.gradle
liên kết với mô-đun "app" (không phải dự án).
- Mở tệp
build.gradle
của ứng dụng rồi thay nội dung của tệp đó bằng nội dung sau: - Trên thanh công cụ, hãy chọn Tools > Android > Sync Project with Gradle Files (Công cụ > Android > Đồng bộ hoá dự án với tệp Gradle). Thao tác này sẽ thu nạp và cung cấp các thư viện mà dự án của bạn cần.
- Tìm và mở tệp
src/main/AndroidManifest.xml
mặc định. Trong thanh bên Dự án, tệp này được lồng trongapp
rồi trongmanifests
. Thay thế nội dung của tệp bằng mã sau:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.quickstart"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="YouTube Data API Android Quickstart" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="YouTube Data API Android Quickstart" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Bước 5: Thiết lập mẫu
Tạo một lớp Java mới. Để thực hiện việc này, trước tiên, hãy chọn thư mục java
trong thanh bên Project (Dự án). Thư mục này xuất hiện trong nhóm tệp app
. Sau khi nhấp vào thư mục, bạn có thể chọn .../app/src/main/java
.
Đặt tên cho lớp là "MainActivity" rồi nhấp vào OK. Thay thế nội dung của tệp mới bằng mã sau.
package com.example.quickstart; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.google.api.client.extensions.android.http.AndroidHttp; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException; import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException; import com.google.api.client.http.HttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.ExponentialBackOff; import com.google.api.services.youtube.YouTubeScopes; import com.google.api.services.youtube.model.*; import android.Manifest; import android.accounts.AccountManager; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.text.TextUtils; import android.text.method.ScrollingMovementMethod; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import pub.devrel.easypermissions.AfterPermissionGranted; import pub.devrel.easypermissions.EasyPermissions; public class MainActivity extends Activity implements EasyPermissions.PermissionCallbacks { GoogleAccountCredential mCredential; private TextView mOutputText; private Button mCallApiButton; ProgressDialog mProgress; static final int REQUEST_ACCOUNT_PICKER = 1000; static final int REQUEST_AUTHORIZATION = 1001; static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002; static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003; private static final String BUTTON_TEXT = "Call YouTube Data API"; private static final String PREF_ACCOUNT_NAME = "accountName"; private static final String[] SCOPES = { YouTubeScopes.YOUTUBE_READONLY }; /** * Create the main activity. * @param savedInstanceState previously saved instance data. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout activityLayout = new LinearLayout(this); LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); activityLayout.setLayoutParams(lp); activityLayout.setOrientation(LinearLayout.VERTICAL); activityLayout.setPadding(16, 16, 16, 16); ViewGroup.LayoutParams tlp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mCallApiButton = new Button(this); mCallApiButton.setText(BUTTON_TEXT); mCallApiButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCallApiButton.setEnabled(false); mOutputText.setText(""); getResultsFromApi(); mCallApiButton.setEnabled(true); } }); activityLayout.addView(mCallApiButton); mOutputText = new TextView(this); mOutputText.setLayoutParams(tlp); mOutputText.setPadding(16, 16, 16, 16); mOutputText.setVerticalScrollBarEnabled(true); mOutputText.setMovementMethod(new ScrollingMovementMethod()); mOutputText.setText( "Click the \'" + BUTTON_TEXT +"\' button to test the API."); activityLayout.addView(mOutputText); mProgress = new ProgressDialog(this); mProgress.setMessage("Calling YouTube Data API ..."); setContentView(activityLayout); // Initialize credentials and service object. mCredential = GoogleAccountCredential.usingOAuth2( getApplicationContext(), Arrays.asList(SCOPES)) .setBackOff(new ExponentialBackOff()); } /** * Attempt to call the API, after verifying that all the preconditions are * satisfied. The preconditions are: Google Play Services installed, an * account was selected and the device currently has online access. If any * of the preconditions are not satisfied, the app will prompt the user as * appropriate. */ private void getResultsFromApi() { if (! isGooglePlayServicesAvailable()) { acquireGooglePlayServices(); } else if (mCredential.getSelectedAccountName() == null) { chooseAccount(); } else if (! isDeviceOnline()) { mOutputText.setText("No network connection available."); } else { new MakeRequestTask(mCredential).execute(); } } /** * Attempts to set the account used with the API credentials. If an account * name was previously saved it will use that one; otherwise an account * picker dialog will be shown to the user. Note that the setting the * account to use with the credentials object requires the app to have the * GET_ACCOUNTS permission, which is requested here if it is not already * present. The AfterPermissionGranted annotation indicates that this * function will be rerun automatically whenever the GET_ACCOUNTS permission * is granted. */ @AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS) private void chooseAccount() { if (EasyPermissions.hasPermissions( this, Manifest.permission.GET_ACCOUNTS)) { String accountName = getPreferences(Context.MODE_PRIVATE) .getString(PREF_ACCOUNT_NAME, null); if (accountName != null) { mCredential.setSelectedAccountName(accountName); getResultsFromApi(); } else { // Start a dialog from which the user can choose an account startActivityForResult( mCredential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER); } } else { // Request the GET_ACCOUNTS permission via a user dialog EasyPermissions.requestPermissions( this, "This app needs to access your Google account (via Contacts).", REQUEST_PERMISSION_GET_ACCOUNTS, Manifest.permission.GET_ACCOUNTS); } } /** * Called when an activity launched here (specifically, AccountPicker * and authorization) exits, giving you the requestCode you started it with, * the resultCode it returned, and any additional data from it. * @param requestCode code indicating which activity result is incoming. * @param resultCode code indicating the result of the incoming * activity result. * @param data Intent (containing result data) returned by incoming * activity result. */ @Override protected void onActivityResult( int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch(requestCode) { case REQUEST_GOOGLE_PLAY_SERVICES: if (resultCode != RESULT_OK) { mOutputText.setText( "This app requires Google Play Services. Please install " + "Google Play Services on your device and relaunch this app."); } else { getResultsFromApi(); } break; case REQUEST_ACCOUNT_PICKER: if (resultCode == RESULT_OK && data != null && data.getExtras() != null) { String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); if (accountName != null) { SharedPreferences settings = getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); editor.putString(PREF_ACCOUNT_NAME, accountName); editor.apply(); mCredential.setSelectedAccountName(accountName); getResultsFromApi(); } } break; case REQUEST_AUTHORIZATION: if (resultCode == RESULT_OK) { getResultsFromApi(); } break; } } /** * Respond to requests for permissions at runtime for API 23 and above. * @param requestCode The request code passed in * requestPermissions(android.app.Activity, String, int, String[]) * @param permissions The requested permissions. Never null. * @param grantResults The grant results for the corresponding permissions * which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null. */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); EasyPermissions.onRequestPermissionsResult( requestCode, permissions, grantResults, this); } /** * Callback for when a permission is granted using the EasyPermissions * library. * @param requestCode The request code associated with the requested * permission * @param list The requested permission list. Never null. */ @Override public void onPermissionsGranted(int requestCode, List<String> list) { // Do nothing. } /** * Callback for when a permission is denied using the EasyPermissions * library. * @param requestCode The request code associated with the requested * permission * @param list The requested permission list. Never null. */ @Override public void onPermissionsDenied(int requestCode, List<String> list) { // Do nothing. } /** * Checks whether the device currently has a network connection. * @return true if the device has a network connection, false otherwise. */ private boolean isDeviceOnline() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); } /** * Check that Google Play services APK is installed and up to date. * @return true if Google Play Services is available and up to * date on this device; false otherwise. */ private boolean isGooglePlayServicesAvailable() { GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); final int connectionStatusCode = apiAvailability.isGooglePlayServicesAvailable(this); return connectionStatusCode == ConnectionResult.SUCCESS; } /** * Attempt to resolve a missing, out-of-date, invalid or disabled Google * Play Services installation via a user dialog, if possible. */ private void acquireGooglePlayServices() { GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); final int connectionStatusCode = apiAvailability.isGooglePlayServicesAvailable(this); if (apiAvailability.isUserResolvableError(connectionStatusCode)) { showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode); } } /** * Display an error dialog showing that Google Play Services is missing * or out of date. * @param connectionStatusCode code describing the presence (or lack of) * Google Play Services on this device. */ void showGooglePlayServicesAvailabilityErrorDialog( final int connectionStatusCode) { GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); Dialog dialog = apiAvailability.getErrorDialog( MainActivity.this, connectionStatusCode, REQUEST_GOOGLE_PLAY_SERVICES); dialog.show(); } /** * An asynchronous task that handles the YouTube Data API call. * Placing the API calls in their own task ensures the UI stays responsive. */ private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> { private com.google.api.services.youtube.YouTube mService = null; private Exception mLastError = null; MakeRequestTask(GoogleAccountCredential credential) { HttpTransport transport = AndroidHttp.newCompatibleTransport(); JsonFactory jsonFactory = JacksonFactory.getDefaultInstance(); mService = new com.google.api.services.youtube.YouTube.Builder( transport, jsonFactory, credential) .setApplicationName("YouTube Data API Android Quickstart") .build(); } /** * Background task to call YouTube Data API. * @param params no parameters needed for this task. */ @Override protected List<String> doInBackground(Void... params) { try { return getDataFromApi(); } catch (Exception e) { mLastError = e; cancel(true); return null; } } /** * Fetch information about the "GoogleDevelopers" YouTube channel. * @return List of Strings containing information about the channel. * @throws IOException */ private List<String> getDataFromApi() throws IOException { // Get a list of up to 10 files. List<String> channelInfo = new ArrayList<String>(); ChannelListResponse result = mService.channels().list("snippet,contentDetails,statistics") .setForUsername("GoogleDevelopers") .execute(); List<Channel> channels = result.getItems(); if (channels != null) { Channel channel = channels.get(0); channelInfo.add("This channel's ID is " + channel.getId() + ". " + "Its title is '" + channel.getSnippet().getTitle() + ", " + "and it has " + channel.getStatistics().getViewCount() + " views."); } return channelInfo; } @Override protected void onPreExecute() { mOutputText.setText(""); mProgress.show(); } @Override protected void onPostExecute(List<String> output) { mProgress.hide(); if (output == null || output.size() == 0) { mOutputText.setText("No results returned."); } else { output.add(0, "Data retrieved using the YouTube Data API:"); mOutputText.setText(TextUtils.join("\n", output)); } } @Override protected void onCancelled() { mProgress.hide(); if (mLastError != null) { if (mLastError instanceof GooglePlayServicesAvailabilityIOException) { showGooglePlayServicesAvailabilityErrorDialog( ((GooglePlayServicesAvailabilityIOException) mLastError) .getConnectionStatusCode()); } else if (mLastError instanceof UserRecoverableAuthIOException) { startActivityForResult( ((UserRecoverableAuthIOException) mLastError).getIntent(), MainActivity.REQUEST_AUTHORIZATION); } else { mOutputText.setText("The following error occurred:\n" + mLastError.getMessage()); } } else { mOutputText.setText("Request cancelled."); } } } }
Bước 6: Chạy ứng dụng
- Để kiểm thử ứng dụng, hãy nhấp vào mục trình đơn Run (Chạy) > Run app (Chạy ứng dụng).
- Bạn sẽ được nhắc chọn một thiết bị đã kết nối (nên dùng) hoặc một trình mô phỏng để chạy ứng dụng. Nếu bạn chạy trên một bản mô phỏng, hãy đảm bảo rằng bản mô phỏng đó được định cấu hình để sử dụng một trong các hình ảnh hệ thống có API của Google. Nếu bạn cố gắng chạy phần hướng dẫn nhanh trên một thiết bị hiện chưa cài đặt Dịch vụ Google Play, thì phần hướng dẫn nhanh sẽ tạo một hộp thoại để bạn có thể cài đặt dịch vụ này.
- Nếu chạy trên trình mô phỏng, hãy cho phép trình mô phỏng khởi động hoàn toàn và thiết lập kết nối mạng.
- Nếu khởi động trình mô phỏng lần đầu tiên, bạn có thể cần mở khoá màn hình của trình mô phỏng. Dù sao đi nữa, ứng dụng khởi động nhanh sẽ tự động khởi động.
- Trong lần đầu tiên chạy ứng dụng, ứng dụng sẽ nhắc bạn chỉ định một tài khoản. Hoàn tất quy trình đăng nhập để chọn một tài khoản cần kết nối.
- Sau khi chọn một tài khoản, ứng dụng sẽ nhắc bạn cho phép truy cập. Nhấp vào OK để uỷ quyền.
Ghi chú
- Thông tin uỷ quyền được lưu trữ cùng với ứng dụng, vì vậy, các lần thực thi tiếp theo sẽ không nhắc uỷ quyền.
Tài liệu đọc thêm
- Tài liệu trợ giúp về Google Developers Console
- Tài liệu về Ứng dụng API của Google cho Java
- Hướng dẫn về API Android
- Dịch vụ Google Play
- Tài liệu tham khảo về YouTube Data API
Khắc phục sự cố
Ứng dụng Android chưa được đăng ký
Khi hộp thoại OAuth chứa mục "Ứng dụng Android chưa đăng ký", tức là không tìm thấy mã ứng dụng khách OAuth2 mà bạn đã tạo ở Bước 2 và Android sẽ quay lại ứng dụng khách mặc định. Ứng dụng mặc định sẽ không được định cấu hình để sử dụng API này, vì vậy, các yêu cầu sẽ không thành công với các lỗi như accessNotConfigured.
. Các lỗi này cũng có thể đề cập đến số dự án mặc định 608941808256
.
Để khắc phục vấn đề này, hãy đảm bảo vân tay SHA1 mà bạn truy xuất ở Bước 1 và applicationId
được liệt kê trong tệp build.gradle
khớp chính xác với các giá trị mà bạn đặt trong Google Developers Console.