التشغيل السريع لأجهزة Android

يوضّح هذا الدليل كيفية إعداد تطبيق Android بسيط يُرسل طلبات إلى YouTube Data API.

المتطلبات الأساسية

لتنفيذ هذه الخطوات السريعة، ستحتاج إلى ما يلي:

سيفترض دليل البدء السريع هذا أنّك تستخدم IDE في IDE Android Studio (بدلاً من أدوات حزمة تطوير البرامج (SDK) المستقلة) وأنّك تجيد العثور على الملفات وإنشائها وتعديلها ضمن مشروع في Studio.

الخطوة 1: الحصول على بصمة إصبع SHA1

في وحدة طرفية، شغِّل الأمر التالي Keytool utility للحصول على الملف المرجعي SHA1 الذي ستستخدمه لتفعيل واجهة برمجة التطبيقات.

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

  1. استخدِم هذا المعالج لإنشاء مشروع أو اختياره في Google Developers Console وتفعيل واجهة برمجة التطبيقات تلقائيًا. انقر على متابعة، ثم على الانتقال إلى بيانات الاعتماد.

  2. في صفحة إنشاء بيانات الاعتماد، انقر على الزر إلغاء.

  3. في أعلى الصفحة، انقر على علامة التبويب شاشة طلب الموافقة المتعلّقة ببروتوكول OAuth. اختَر عنوان بريد إلكتروني، وأدخِل اسم المنتج إذا لم يكن محدّدًا، ثم انقر على الزر حفظ.

  4. اختَر علامة التبويب بيانات الاعتماد، وانقر على الزر إنشاء بيانات اعتماد واختَر معرِّف عميل OAuth.

  5. اختَر نوع التطبيق Android.
  6. انسخ الملف المرجعي لشهادة SHA1 من الخطوة 1 إلى الحقل Signing-certificate fingerprint.
  7. في حقل اسم الحزمة ، أدخِل com.example.quickstart.
  8. انقر على الزر إنشاء.

الخطوة 3: إنشاء مشروع Android جديد

  1. افتح استوديو Android، وابدأ مشروعًا جديدًا في استوديو Android.
  2. في شاشة مشروع جديد، أدخِل اسم "البدء السريع" للتطبيق.
  3. اضبط نطاق الشركة على example.com وتأكَّد من أنّ اسم الحزمة الذي تم إنشاؤه تلقائيًا يتطابق مع الاسم الذي أدخلته في وحدة تحكّم المطوّر في الخطوة 2. انقر على التالي.
  4. في شاشة أجهزة Android المستهدَفة، ضَع علامة في مربّع الاختيار الهاتف والجهاز اللوحي واختَر الحد الأدنى لحزمة SDK من "واجهة برمجة التطبيقات 14: Android 4.0 (IceCreamSandwich)". لا تضَع علامة في مربّعات الاختيار الأخرى. انقر على التالي.
  5. في شاشة إضافة نشاط إلى الأجهزة الجوّالة، انقر على إضافة "ما مِن نشاط".
  6. انقر على إنهاء.

في هذه المرحلة، ينشئ استوديو Android المشروع ويفتحه.

الخطوة 4: إعداد المشروع

الشريط الجانبي "المشروع" هو قائمة قابلة للتوسيع لملفات المشاريع التلقائية التي أنشأها IDE Android Studio. في هذه القائمة، وسِّع قائمة نصوص Gradle البرمجية وافتحملف build.gradle المرتبط بوحدة "التطبيق" (وليس المشروع).

  1. افتح ملف build.gradle للتطبيق واستبدِل محتوياته بما يلي:
  2. في شريط الأدوات، اختَر الأدوات > Android > مزامنة المشروع مع ملفات Gradle. سيؤدي ذلك إلى الحصول على المكتبات التي يحتاجها مشروعك وإتاحتها.
  3. ابحث عن ملف 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. بعد النقر على المجلد، يمكنك اختيار ملف > جديد > فئة Java من شريط القوائم، أو يمكنك النقر بزر الماوس الأيمن على المجلد واختيار جديد > فئة Java. إذا طُلب منك اختيار دليل، اختَر .../app/src/main/java.

أدخِل اسم "MainActivity" للصف وانقر على موافق. استبدِل محتوى الملف الجديد بالرمز التالي.

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: تشغيل التطبيق

  1. لاختبار التطبيق، انقر على عنصر القائمة تشغيل > تشغيل التطبيق.
  2. سيُطلب منك اختيار جهاز متصل (إجراء يُنصح به) أو محاكي لتشغيل التطبيق عليه. إذا كنت تستخدم محاكيًا، تأكَّد من أنّه تم ضبطه لاستخدام إحدى صور نظام مع واجهات برمجة تطبيقات Google. إذا حاولت تشغيل ميزة "الإعداد السريع" على جهاز لم يتم تثبيت "خدمات Google Play" عليه حاليًا، ستعرض ميزة "الإعداد السريع" مربّع حوار يمكنك من خلاله تثبيتها.
  3. إذا كان التطبيق قيد التشغيل على جهاز محاكاة، يجب السماح له بالبدء بالكامل وبدء اتصاله بالشبكة.
  4. إذا كنت بصدد تشغيل المحاكي لأول مرة، قد تحتاج إلى فتح قفل الشاشة. بغض النظر عن ذلك، من المفترض أن يبدأ تطبيق "البدء السريع" تلقائيًا.
  5. في المرة الأولى التي تشغّل فيها التطبيق، سيُطلب منك تحديد حساب. أكمِل عملية تسجيل الدخول لاختيار حساب تريد الربط به.
  6. بعد اختيار حساب، سيطلب منك التطبيق تفويض الوصول إليه. انقر على حسنًا للموافقة.

ملاحظات

  • يتم تخزين معلومات التفويض مع التطبيق، لذا لا تطلب عمليات التنفيذ اللاحقة تفويضًا.

مراجع إضافية

تحديد المشاكل وحلّها

تطبيق Android غير مسجل

عندما يحتوي مربّع حوار OAuth على إدخال يشير إلى "تطبيق Android غير مسجَّل"، يعني ذلك أنّه لا يمكن للعثور على معرِّف عميل OAuth2 الذي أنشأته في الخطوة 2، وسيتم الرجوع إلى عميل تلقائي على Android. لن يتم ضبط العميل التلقائي لاستخدام واجهة برمجة التطبيقات هذه، لذا ستتعذّر الطلبات مع ظهور أخطاء مثل accessNotConfigured. وقد تشير هذه الأخطاء أيضًا إلى رقم المشروع التلقائي 608941808256.

لحلّ هذه المشكلة، تأكَّد من أنّ الملف المرجعي لشهادة SHA1 الذي استردته في الخطوة 1 وapplicationId المدرَجة في ملف build.gradle تتطابق تمامًا مع القيم التي ضبطتها في Google Developers Console.