במדריך הזה מוסבר איך להגדיר אפליקציית Android פשוטה ששולחת בקשות אל YouTube Data API.
דרישות מוקדמות
כדי להשתמש במדריך למתחילים הזה, צריך:
- Android Studio SDK 1.2 ואילך.
- חבילות Android SDK ל-API מגרסה 23 ואילך, כולל הגרסאות העדכניות של Google Repository, Android Support Library ו-Google Play Services.
- גישה לאינטרנט במכשיר הבדיקה.
- חשבון Google.
במדריך הזה נניח שאתם משתמשים בסביבת הפיתוח המשולבת (IDE) של Android Studio (ולא בכלי ה-SDK העצמאיים), ושאתם יודעים למצוא, ליצור ולערוך קבצים בפרויקט של Studio.
שלב 1: קבלת טביעת אצבע מסוג SHA1
בטרמינל, מריצים את הפקודה הבאה של כלי השירות Keytool כדי לקבל את טביעת האצבע של SHA1 שתשמש להפעלת ה-API.
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v
כשמתבקשים להזין סיסמה למאגר המפתחות, מזינים android.
הכלי Keytool מדפיס את טביעת האצבע למעטפת. לדוגמה:
$ 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
מעתיקים את טביעת האצבע של SHA1, שמסומנת בדוגמה שלמעלה.
שלב 2: מפעילים את YouTube Data API
-
משתמשים באשף הזה כדי ליצור או לבחור פרויקט ב-Google Developers Console ולהפעיל את ה-API באופן אוטומטי. לוחצים על המשך ואז על Go to credentials (מעבר אל פרטי הכניסה).
-
בדף יצירת פרטי כניסה, לוחצים על הלחצן ביטול.
-
בחלק העליון של הדף, לוחצים על הכרטיסייה מסך הסכמה ל-OAuth. בוחרים כתובת אימייל, מזינים שם מוצר אם הוא לא מוגדר כבר ולוחצים על הלחצן שמירה.
-
בכרטיסייה Credentials (פרטי כניסה), לוחצים על הלחצן Create credentials (יצירת פרטי כניסה) ובוחרים באפשרות OAuth client ID (מזהה לקוח OAuth).
- בוחרים את סוג האפליקציה Android.
- מעתיקים את טביעת האצבע של SHA1 משלב 1 לשדה טביעת אצבע של אישור חתימה.
- בשדה Package name
מזינים
com.example.quickstart
. - לוחצים על הלחצן יצירה.
שלב 3: יצירת פרויקט Android חדש
- פותחים את Android Studio ומתחילים פרויקט חדש ב-Android Studio.
- במסך New Project, נותנים לאפליקציה את השם Quickstart.
- מגדירים את הדומיין של החברה ל-example.com ומוודאים ששם החבילה שנוצר באופן אוטומטי זהה לזה שהזנתם ב-Developer Console בשלב 2. לוחצים על הבא.
- במסך Target Android Devices (מכשירי Android לטירגוט), מסמנים את תיבת הסימון Phone and Tablet (טלפון וטאבלט) ובוחרים באפשרות Minimum SDK (גרסת ה-SDK המינימלית) API 14: Android 4.0 (IceCreamSandwich). משאירים את שאר תיבות הסימון לא מסומנות. לוחצים על Next.
- במסך הוספת פעילות לנייד, לוחצים על הוספת פעילות לא זמינה.
- לוחצים על סיום.
בשלב הזה, Android Studio יוצר את הפרויקט ופותח אותו.
שלב 4: הכנת הפרויקט
סרגל הצד 'פרויקט' הוא רשימה שאפשר להרחיב של קובצי הפרויקט שנוצרו כברירת מחדל ב-Android Studio. ברשימה הזו, מרחיבים את רשימת סקריפטים של Gradle ופותחים את הקובץ build.gradle
שמשויך למודול app (לא לפרויקט).
- פותחים את קובץ האפליקציה
build.gradle
ומחליפים את התוכן שלו בתוכן הבא: - בסרגל הכלים, בוחרים באפשרות Tools > Android > Sync Project with Gradle Files. הפעולה הזו תרכוש את הספריות שהפרויקט שלכם צריך ותהפוך אותן לזמינות.
- מאתרים ופותחים את קובץ
src/main/AndroidManifest.xml
שמוגדר כברירת מחדל. בסרגל הצד של הפרויקט, הקובץ הזה מוטמע מתחת ל-app
ואז מתחת ל-manifests
. מחליפים את תוכן הקובץ בקוד הבא:<?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>
שלב 5: הגדרת הדוגמה
יוצרים מחלקת Java חדשה. כדי לעשות זאת, קודם בוחרים את התיקייה java
בסרגל הצד של הפרויקט. התיקייה הזו מופיעה בקבוצת app
הקבצים. אחרי שלוחצים על התיקייה, אפשר לבחור באפשרות .../app/src/main/java
.
נותנים לכיתה את השם MainActivity ולוחצים על OK. מחליפים את התוכן של הקובץ החדש בקוד הבא.
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."); } } } }
שלב 6: מפעילים את האפליקציה
- כדי לבדוק את האפליקציה, לוחצים על פריט התפריט הפעלה > הפעלת האפליקציה.
- תתבקשו לבחור מכשיר מחובר (מומלץ) או הדמיה כדי להריץ את האפליקציה. אם אתם מריצים את המערכת על אמולציה, ודאו שהיא מוגדרת להשתמש באחת מתמונות המערכת עם Google APIs. אם מנסים להריץ את ההפעלה המהירה במכשיר שאין בו כרגע Google Play Services, ההפעלה המהירה יוצרת תיבת דו-שיח שממנה אפשר להתקין את השירותים.
- אם מריצים את האפליקציה באמולטור, צריך לאפשר לו להתחיל באופן מלא וליצור חיבור לרשת.
- אם מפעילים את האמולטור בפעם הראשונה, יכול להיות שיהיה צורך לבטל את נעילת המסך שלו. בכל מקרה, אפליקציית ההפעלה המהירה אמורה להתחיל לפעול באופן אוטומטי.
- בפעם הראשונה שמפעילים את האפליקציה, מוצגת בקשה לציין חשבון. משלימים את תהליך הכניסה כדי לבחור חשבון לקשר.
- אחרי שבוחרים חשבון, האפליקציה מבקשת הרשאה לגישה. לוחצים על אישור כדי לתת הרשאה.
הערות
- פרטי ההרשאה מאוחסנים באפליקציה, כך שבהרצות הבאות לא תהיה בקשה להרשאה.
קריאה נוספת
- מסמכי העזרה של Google Developers Console
- מאמרי העזרה של Google APIs Client for Java
- מדריכי Android API
- Google Play Services
- מסמכי העזרה של YouTube Data API
פתרון בעיות
אפליקציית Android לא רשומה
אם בתיבת הדו-שיח של OAuth מופיעה ההודעה 'אפליקציית Android לא רשומה', המשמעות היא שלא ניתן למצוא את מזהה הלקוח של OAuth2 שיצרתם בשלב 2, ומערכת Android חוזרת ללקוח ברירת מחדל. לקוח ברירת המחדל לא יוגדר לשימוש ב-API הזה, ולכן הבקשות ייכשלו עם שגיאות כמו accessNotConfigured.
. יכול להיות שבהודעות השגיאה האלה יופיע גם מספר פרויקט ברירת המחדל 608941808256
.
כדי לפתור את הבעיה, צריך לוודא שטביעת האצבע של SHA1 שאוחזרה בשלב 1 ו-applicationId
שמופיע בקובץ build.gradle
זהים בדיוק לערכים שהגדרתם במסוף Google Developers.