Oktatóanyag: Felhasználók bejelentkezése és a Microsoft Graph API meghívása Android-alkalmazásból
Ebben az oktatóanyagban létrehoz egy Android-alkalmazást, amely integrálható a Microsoft Entra-azonosítóval a felhasználók bejelentkezéséhez és a Microsoft Graph API meghívásához szükséges hozzáférési jogkivonat beszerzéséhez.
Amikor elvégezte ezt az oktatóanyagot, az alkalmazás elfogadja a személyes Microsoft-fiókok (beleértve outlook.com, live.com és mások) és a Microsoft Entra-azonosítót használó bármely cég vagy szervezet munkahelyi vagy iskolai fiókjait.
Ebben az oktatóanyagban:
- Android-alkalmazásprojekt létrehozása az Android Studióban
- Az alkalmazás regisztrálása a Microsoft Entra Felügyeleti központban
- Kód hozzáadása a felhasználói bejelentkezés és kijelentkezés támogatásához
- Kód hozzáadása a Microsoft Graph API meghívásához
- Az alkalmazás tesztelése
Előfeltételek
Az oktatóanyag működése
Az oktatóanyagban szereplő alkalmazás bejelentkezik a felhasználókba, és adatokat kap a nevükben. Ezek az adatok egy védett API-n (Microsoft Graph API) keresztül érhetők el, amely engedélyezést igényel, és a Microsoft Identitásplatform védi.
Ez a minta az Androidhoz készült Microsoft Authentication Library (MSAL) használatával implementálja a hitelesítést: com.microsoft.identity.client.
Projekt létrehozása
Ha még nem rendelkezik Android-alkalmazással, az alábbi lépéseket követve hozzon létre egy új projektet.
- Nyissa meg az Android Studiót, és válassza az Új Android Studio-projekt indítása lehetőséget.
- Válassza az Alapszintű tevékenység lehetőséget, majd a Tovább gombot.
- Adja meg az alkalmazás nevét, például az MSALAndroidappot.
- Jegyezze fel a későbbi lépésekben használandó csomagnevet.
- Módosítsa a nyelvet a Kotlinról Java-ra.
- Állítsa a Minimális SDK API-szintet API 16-os vagy magasabbra, és válassza a Befejezés lehetőséget.
Alkalmazás regisztrálása a Microsoft Entra-azonosítóval
Tipp.
A cikkben szereplő lépések a portáltól függően kissé eltérhetnek.
Jelentkezzen be a Microsoft Entra felügyeleti központba legalább alkalmazásfejlesztőként.
Ha több bérlőhöz is hozzáfér, a felső menü Beállítások ikonjával válthat arra a bérlőre, amelyben regisztrálni szeretné az alkalmazást a Könyvtárak + előfizetések menüből.
Keresse meg az identitásalkalmazásokat>> Alkalmazásregisztrációk.
Új regisztráció kiválasztása.
Adja meg az alkalmazás nevét. Előfordulhat, hogy az alkalmazás felhasználói látják ezt a nevet, és később módosíthatja.
Támogatott fióktípusok esetén válassza a Fiókok lehetőséget bármely szervezeti könyvtárban (Bármely Microsoft Entra címtár – Több-bérlős) és személyes Microsoft-fiókokban (pl. Skype, Xbox). A különböző fióktípusokkal kapcsolatos információkért válassza a Súgó kiválasztása lehetőséget.
Válassza ki a pénztárgépet.
A Kezelés területen válassza a Hitelesítés>hozzáadása platform>Androidhoz lehetőséget.
Adja meg a projekt csomagnevét. Ha letöltötte a mintakódot, ez az érték .
com.azuresamples.msalandroidapp
Az Android-alkalmazás konfigurálása panel Aláírás kivonat szakaszában válassza a Fejlesztési aláírás kivonat létrehozása lehetőséget, és másolja a KeyTool parancsot a parancssorba.
- KeyTool.exe a Java Development Kit (JDK) részeként van telepítve. A KeyTool parancs végrehajtásához telepítenie kell az OpenSSL eszközt is. További információkért tekintse meg az Android dokumentációját a kulcsok generálására vonatkozóan.
Adja meg a KeyTool által létrehozott aláíráskivonatot .
Válassza a Konfigurálás lehetőséget, és mentse az Android konfigurációs panelen megjelenő MSAL-konfigurációt, hogy később beírhassa azt az alkalmazás konfigurálásakor.
Válassza a Kész lehetőséget.
Az alkalmazás konfigurálása
Az Android Studio projektpaneljén lépjen az app\src\main\res elemre.
Kattintson a jobb gombbal a res elemre, és válassza az Új>könyvtár lehetőséget. Adja meg
raw
az új könyvtárnevet, és válassza az OK gombot.Az app>src>main>res>raw fájljában hozzon létre egy új JSON-fájlt, amelyet meghív,
auth_config_single_account.json
és illessze be a korábban mentett MSAL-konfigurációt.Az átirányítási URI alatt illessze be a következőt:
"account_mode" : "SINGLE",
A konfigurációs fájlnak a következő példához kell hasonlítania:
{ "client_id": "00001111-aaaa-bbbb-3333-cccc4444", "authorization_user_agent": "WEBVIEW", "redirect_uri": "msauth://com.azuresamples.msalandroidapp/00001111%cccc4444%3D", "broker_redirect_uri_registered": true, "account_mode": "SINGLE", "authorities": [ { "type": "AAD", "audience": { "type": "AzureADandPersonalMicrosoftAccount", "tenant_id": "common" } } ] }
Mivel ez az oktatóanyag csak azt mutatja be, hogyan konfigurálhat egy alkalmazást egyetlen fiók módban, tekintse meg az egy vagy több fiókos módot , és konfigurálja az alkalmazást további információkért.
A "WEBVIEW" használatát javasoljuk. Ha a "authorization_user_agent" böngészőként szeretné konfigurálni az alkalmazásban, az alábbi frissítéseket kell elvégeznie. a) Frissítse a auth_config_single_account.json a "authorization_user_agent": "Browser" kifejezéssel. b) AndroidManifest.xml frissítése. Az alkalmazásban lépjen az app>src>fő>AndroidManifest.xml, és adja hozzá a
BrowserTabActivity
tevékenységet az<application>
elem gyermekeként. Ez a bejegyzés lehetővé teszi, hogy a Microsoft Entra ID visszahívja az alkalmazást a hitelesítés befejezése után:<!--Intent filter to capture System Browser or Authenticator calling back to our app after sign-in--> <activity android:name="com.microsoft.identity.client.BrowserTabActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="msauth" android:host="Enter_the_Package_Name" android:path="/Enter_the_Signature_Hash" /> </intent-filter> </activity>
- A Csomagnévvel cserélje le
android:host=.
az értéket. Úgy kell kinéznie, mintcom.azuresamples.msalandroidapp
. - Az aláírás kivonatával cserélje le
android:path=
az értéket. Győződjön meg arról, hogy az aláírási kivonat elején van egy bevezető/
. Úgy kell kinéznie, mint/aB1cD2eF3gH4+iJ5kL6-mN7oP8q=
.
Ezeket az értékeket az alkalmazásregisztráció Hitelesítés paneljén is megtalálhatja.
- A Csomagnévvel cserélje le
MSAL és kapcsolódó kódtárak hozzáadása a projekthez
Az Android Studio projektablakában lépjen az alkalmazás>build.gradle lapjára, és adja hozzá a következő kódtárakat a függőségek szakaszhoz:
implementation 'com.microsoft.identity.client:msal:5.0.0' implementation 'com.android.volley:volley:1.2.1'
Az Android Studio projektablakában nyissa meg a settings.gradle fájlt, és deklarálja a következő maven-adattárat a dependencyResolutionManagement>adattárak szakaszban:
maven { url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1' }
Válassza a Szinkronizálás most lehetőséget az értesítési sávon.
A szükséges töredék létrehozása és frissítése
Az app>src>main>java>com.example(az alkalmazás neve). Hozza létre a következő Android-töredékeket:
- MSGraphRequestWrapper
- OnFragmentInteractionListener
- SingleAccountModeFragment
Nyissa meg MSGraphRequestWrapper.java , és cserélje le a kódot a következő kódrészletre a Microsoft Graph API meghívásához az MSAL által biztosított jogkivonat használatával:
package com.azuresamples.msalandroidapp; import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; import com.android.volley.DefaultRetryPolicy; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response; import com.android.volley.toolbox.JsonObjectRequest; import com.android.volley.toolbox.Volley; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; public class MSGraphRequestWrapper { private static final String TAG = MSGraphRequestWrapper.class.getSimpleName(); // See: https://docs.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints public static final String MS_GRAPH_ROOT_ENDPOINT = "https://graph.microsoft.com/"; /** * Use Volley to make an HTTP request with * 1) a given MSGraph resource URL * 2) an access token * to obtain MSGraph data. **/ public static void callGraphAPIUsingVolley(@NonNull final Context context, @NonNull final String graphResourceUrl, @NonNull final String accessToken, @NonNull final Response.Listener<JSONObject> responseListener, @NonNull final Response.ErrorListener errorListener) { Log.d(TAG, "Starting volley request to graph"); /* Make sure we have a token to send to graph */ if (accessToken == null || accessToken.length() == 0) { return; } RequestQueue queue = Volley.newRequestQueue(context); JSONObject parameters = new JSONObject(); try { parameters.put("key", "value"); } catch (Exception e) { Log.d(TAG, "Failed to put parameters: " + e.toString()); } JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, graphResourceUrl, parameters, responseListener, errorListener) { @Override public Map<String, String> getHeaders() { Map<String, String> headers = new HashMap<>(); headers.put("Authorization", "Bearer " + accessToken); return headers; } }; Log.d(TAG, "Adding HTTP GET to Queue, Request: " + request.toString()); request.setRetryPolicy(new DefaultRetryPolicy( 3000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); queue.add(request); } }
Nyissa meg OnFragmentInteractionListener.java , és cserélje le a kódot a következő kódrészletre a különböző töredékek közötti kommunikáció engedélyezéséhez:
package com.azuresamples.msalandroidapp; /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated * to the activity and potentially other fragments contained in that * activity. * <p> * See the Android Training lesson <a href= * "http://developer.android.com/training/basics/fragments/communicating.html" * >Communicating with Other Fragments</a> for more information. */ public interface OnFragmentInteractionListener { }
Nyissa meg a SingleAccountModeFragment.java , és cserélje le a kódot a következő kódrészletre egy egyfiókos alkalmazás inicializálásához, egy felhasználói fiók betöltéséhez és egy jogkivonat lekéréséhez a Microsoft Graph API meghívásához:
package com.azuresamples.msalandroidapp; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.android.volley.Response; import com.android.volley.VolleyError; import com.microsoft.identity.client.AuthenticationCallback; import com.microsoft.identity.client.IAccount; import com.microsoft.identity.client.IAuthenticationResult; import com.microsoft.identity.client.IPublicClientApplication; import com.microsoft.identity.client.ISingleAccountPublicClientApplication; import com.microsoft.identity.client.PublicClientApplication; import com.microsoft.identity.client.SilentAuthenticationCallback; import com.microsoft.identity.client.exception.MsalClientException; import com.microsoft.identity.client.exception.MsalException; import com.microsoft.identity.client.exception.MsalServiceException; import com.microsoft.identity.client.exception.MsalUiRequiredException; import org.json.JSONObject; /** * Implementation sample for 'Single account' mode. * <p> * If your app only supports one account being signed-in at a time, this is for you. * This requires "account_mode" to be set as "SINGLE" in the configuration file. * (Please see res/raw/auth_config_single_account.json for more info). * <p> * Please note that switching mode (between 'single' and 'multiple' might cause a loss of data. */ public class SingleAccountModeFragment extends Fragment { private static final String TAG = SingleAccountModeFragment.class.getSimpleName(); /* UI & Debugging Variables */ Button signInButton; Button signOutButton; Button callGraphApiInteractiveButton; Button callGraphApiSilentButton; TextView scopeTextView; TextView graphResourceTextView; TextView logTextView; TextView currentUserTextView; TextView deviceModeTextView; /* Azure AD Variables */ private ISingleAccountPublicClientApplication mSingleAccountApp; private IAccount mAccount; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment final View view = inflater.inflate(R.layout.fragment_single_account_mode, container, false); initializeUI(view); // Creates a PublicClientApplication object with res/raw/auth_config_single_account.json PublicClientApplication.createSingleAccountPublicClientApplication(getContext(), R.raw.auth_config_single_account, new IPublicClientApplication.ISingleAccountApplicationCreatedListener() { @Override public void onCreated(ISingleAccountPublicClientApplication application) { /** * This test app assumes that the app is only going to support one account. * This requires "account_mode" : "SINGLE" in the config json file. **/ mSingleAccountApp = application; loadAccount(); } @Override public void onError(MsalException exception) { displayError(exception); } }); return view; } /** * Initializes UI variables and callbacks. */ private void initializeUI(@NonNull final View view) { signInButton = view.findViewById(R.id.btn_signIn); signOutButton = view.findViewById(R.id.btn_removeAccount); callGraphApiInteractiveButton = view.findViewById(R.id.btn_callGraphInteractively); callGraphApiSilentButton = view.findViewById(R.id.btn_callGraphSilently); scopeTextView = view.findViewById(R.id.scope); graphResourceTextView = view.findViewById(R.id.msgraph_url); logTextView = view.findViewById(R.id.txt_log); currentUserTextView = view.findViewById(R.id.current_user); deviceModeTextView = view.findViewById(R.id.device_mode); final String defaultGraphResourceUrl = MSGraphRequestWrapper.MS_GRAPH_ROOT_ENDPOINT + "v1.0/me"; graphResourceTextView.setText(defaultGraphResourceUrl); signInButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mSingleAccountApp == null) { return; } mSingleAccountApp.signIn(getActivity(), null, getScopes(), getAuthInteractiveCallback()); } }); signOutButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mSingleAccountApp == null) { return; } /** * Removes the signed-in account and cached tokens from this app (or device, if the device is in shared mode). */ mSingleAccountApp.signOut(new ISingleAccountPublicClientApplication.SignOutCallback() { @Override public void onSignOut() { mAccount = null; updateUI(); showToastOnSignOut(); } @Override public void onError(@NonNull MsalException exception) { displayError(exception); } }); } }); callGraphApiInteractiveButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mSingleAccountApp == null) { return; } /** * If acquireTokenSilent() returns an error that requires an interaction (MsalUiRequiredException), * invoke acquireToken() to have the user resolve the interrupt interactively. * * Some example scenarios are * - password change * - the resource you're acquiring a token for has a stricter set of requirement than your Single Sign-On refresh token. * - you're introducing a new scope which the user has never consented for. */ mSingleAccountApp.acquireToken(getActivity(), getScopes(), getAuthInteractiveCallback()); } }); callGraphApiSilentButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mSingleAccountApp == null) { return; } /** * Once you've signed the user in, * you can perform acquireTokenSilent to obtain resources without interrupting the user. */ mSingleAccountApp.acquireTokenSilentAsync(getScopes(), mAccount.getAuthority(), getAuthSilentCallback()); } }); } @Override public void onResume() { super.onResume(); /** * The account may have been removed from the device (if broker is in use). * * In shared device mode, the account might be signed in/out by other apps while this app is not in focus. * Therefore, we want to update the account state by invoking loadAccount() here. */ loadAccount(); } /** * Extracts a scope array from a text field, * i.e. from "User.Read User.ReadWrite" to ["user.read", "user.readwrite"] */ private String[] getScopes() { return scopeTextView.getText().toString().toLowerCase().split(" "); } /** * Load the currently signed-in account, if there's any. */ private void loadAccount() { if (mSingleAccountApp == null) { return; } mSingleAccountApp.getCurrentAccountAsync(new ISingleAccountPublicClientApplication.CurrentAccountCallback() { @Override public void onAccountLoaded(@Nullable IAccount activeAccount) { // You can use the account data to update your UI or your app database. mAccount = activeAccount; updateUI(); } @Override public void onAccountChanged(@Nullable IAccount priorAccount, @Nullable IAccount currentAccount) { if (currentAccount == null) { // Perform a cleanup task as the signed-in account changed. showToastOnSignOut(); } } @Override public void onError(@NonNull MsalException exception) { displayError(exception); } }); } /** * Callback used in for silent acquireToken calls. */ private SilentAuthenticationCallback getAuthSilentCallback() { return new SilentAuthenticationCallback() { @Override public void onSuccess(IAuthenticationResult authenticationResult) { Log.d(TAG, "Successfully authenticated"); /* Successfully got a token, use it to call a protected resource - MSGraph */ callGraphAPI(authenticationResult); } @Override public void onError(MsalException exception) { /* Failed to acquireToken */ Log.d(TAG, "Authentication failed: " + exception.toString()); displayError(exception); if (exception instanceof MsalClientException) { /* Exception inside MSAL, more info inside MsalError.java */ } else if (exception instanceof MsalServiceException) { /* Exception when communicating with the STS, likely config issue */ } else if (exception instanceof MsalUiRequiredException) { /* Tokens expired or no session, retry with interactive */ } } }; } /** * Callback used for interactive request. * If succeeds we use the access token to call the Microsoft Graph. * Does not check cache. */ private AuthenticationCallback getAuthInteractiveCallback() { return new AuthenticationCallback() { @Override public void onSuccess(IAuthenticationResult authenticationResult) { /* Successfully got a token, use it to call a protected resource - MSGraph */ Log.d(TAG, "Successfully authenticated"); Log.d(TAG, "ID Token: " + authenticationResult.getAccount().getClaims().get("id_token")); /* Update account */ mAccount = authenticationResult.getAccount(); updateUI(); /* call graph */ callGraphAPI(authenticationResult); } @Override public void onError(MsalException exception) { /* Failed to acquireToken */ Log.d(TAG, "Authentication failed: " + exception.toString()); displayError(exception); if (exception instanceof MsalClientException) { /* Exception inside MSAL, more info inside MsalError.java */ } else if (exception instanceof MsalServiceException) { /* Exception when communicating with the STS, likely config issue */ } } @Override public void onCancel() { /* User canceled the authentication */ Log.d(TAG, "User cancelled login."); } }; } /** * Make an HTTP request to obtain MSGraph data */ private void callGraphAPI(final IAuthenticationResult authenticationResult) { MSGraphRequestWrapper.callGraphAPIUsingVolley( getContext(), graphResourceTextView.getText().toString(), authenticationResult.getAccessToken(), new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { /* Successfully called graph, process data and send to UI */ Log.d(TAG, "Response: " + response.toString()); displayGraphResult(response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.d(TAG, "Error: " + error.toString()); displayError(error); } }); } // // Helper methods manage UI updates // ================================ // displayGraphResult() - Display the graph response // displayError() - Display the graph response // updateSignedInUI() - Updates UI when the user is signed in // updateSignedOutUI() - Updates UI when app sign out succeeds // /** * Display the graph response */ private void displayGraphResult(@NonNull final JSONObject graphResponse) { logTextView.setText(graphResponse.toString()); } /** * Display the error message */ private void displayError(@NonNull final Exception exception) { logTextView.setText(exception.toString()); } /** * Updates UI based on the current account. */ private void updateUI() { if (mAccount != null) { signInButton.setEnabled(false); signOutButton.setEnabled(true); callGraphApiInteractiveButton.setEnabled(true); callGraphApiSilentButton.setEnabled(true); currentUserTextView.setText(mAccount.getUsername()); } else { signInButton.setEnabled(true); signOutButton.setEnabled(false); callGraphApiInteractiveButton.setEnabled(false); callGraphApiSilentButton.setEnabled(false); currentUserTextView.setText("None"); } deviceModeTextView.setText(mSingleAccountApp.isSharedDevice() ? "Shared" : "Non-shared"); } /** * Updates UI when app sign out succeeds */ private void showToastOnSignOut() { final String signOutText = "Signed Out."; currentUserTextView.setText(""); Toast.makeText(getContext(), signOutText, Toast.LENGTH_SHORT) .show(); } }
Nyissa meg MainActivity.java , és cserélje le a kódot a következő kódrészletre a felhasználói felület kezeléséhez.
package com.azuresamples.msalandroidapp; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.view.GravityCompat; import android.view.MenuItem; import android.view.View; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import com.google.android.material.navigation.NavigationView; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, OnFragmentInteractionListener{ enum AppFragment { SingleAccount } private AppFragment mCurrentFragment; private ConstraintLayout mContentMain; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContentMain = findViewById(R.id.content_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); navigationView.setNavigationItemSelectedListener(this); //Set default fragment navigationView.setCheckedItem(R.id.nav_single_account); setCurrentFragment(AppFragment.SingleAccount); } @Override public boolean onNavigationItemSelected(final MenuItem item) { final DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.addDrawerListener(new DrawerLayout.DrawerListener() { @Override public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { } @Override public void onDrawerOpened(@NonNull View drawerView) { } @Override public void onDrawerClosed(@NonNull View drawerView) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.nav_single_account) { setCurrentFragment(AppFragment.SingleAccount); } drawer.removeDrawerListener(this); } @Override public void onDrawerStateChanged(int newState) { } }); drawer.closeDrawer(GravityCompat.START); return true; } private void setCurrentFragment(final AppFragment newFragment){ if (newFragment == mCurrentFragment) { return; } mCurrentFragment = newFragment; setHeaderString(mCurrentFragment); displayFragment(mCurrentFragment); } private void setHeaderString(final AppFragment fragment){ switch (fragment) { case SingleAccount: getSupportActionBar().setTitle("Single Account Mode"); return; } } private void displayFragment(final AppFragment fragment){ switch (fragment) { case SingleAccount: attachFragment(new com.azuresamples.msalandroidapp.SingleAccountModeFragment()); return; } } private void attachFragment(final Fragment fragment) { getSupportFragmentManager() .beginTransaction() .setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .replace(mContentMain.getId(),fragment) .commit(); } }
Feljegyzés
Győződjön meg arról, hogy a csomag nevét úgy frissíti, hogy megfeleljen az Android-projektcsomag nevének.
Elrendezés
Az elrendezés olyan fájl, amely meghatározza a felhasználói felület vizuális szerkezetét és megjelenését, meghatározva a felhasználói felület összetevőinek elrendezését. XML-ben van megírva. A következő XML-minták érhetők el, ha az oktatóanyagból szeretné modellíteni a felhasználói felületet:
Az alkalmazás>src>fő>res elrendezésében>>activity_main.xml. A gombok és szövegmezők megjelenítéséhez cserélje le a activity_main.xml tartalmát a következő kódrészletre:
<?xml version="1.0" encoding="utf-8"?> <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <include layout="@layout/app_bar_main" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.google.android.material.navigation.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header_main" app:menu="@menu/activity_main_drawer" /> </androidx.drawerlayout.widget.DrawerLayout>
Az alkalmazás>src>fő>res elrendezésében>>app_bar_main.xml. Ha nincs app_bar_main.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </com.google.android.material.appbar.AppBarLayout> <include layout="@layout/content_main" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Az alkalmazás>src>fő>res elrendezésében>>content_main.xml. Ha nincs content_main.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content_main" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/app_bar_main"> </androidx.constraintlayout.widget.ConstraintLayout>
Az alkalmazás>src>fő>res elrendezésében>>fragment_m_s_graph_request_wrapper.xml. Ha nincs fragment_m_s_graph_request_wrapper.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MSGraphRequestWrapper"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" /> </FrameLayout>
Az alkalmazás>src>fő>res elrendezésében>>fragment_on_interaction_listener.xml. Ha nincs fragment_on_interaction_listener.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".OnFragmentInteractionListener"> <!-- TODO: Update blank fragment layout --> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/hello_blank_fragment" /> </FrameLayout>
Az alkalmazás>src>fő>res elrendezésében>>fragment_single_account_mode.xml. Ha nincs fragment_single_account_mode.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".SingleAccountModeFragment"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".SingleAccountModeFragment"> <LinearLayout android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingBottom="@dimen/activity_vertical_margin"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:layout_gravity="center_vertical" android:textStyle="bold" android:text="Scope" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:layout_weight="7"> <EditText android:id="@+id/scope" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="user.read" android:textSize="12sp" /> <TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:paddingLeft="5dp" android:text="Type in scopes delimited by space" android:textSize="10sp" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:layout_gravity="center_vertical" android:textStyle="bold" android:text="MSGraph Resource URL" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:layout_weight="7"> <EditText android:id="@+id/msgraph_url" android:layout_height="wrap_content" android:layout_width="match_parent" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:textStyle="bold" android:text="Signed-in user" /> <TextView android:id="@+id/current_user" android:layout_width="0dp" android:layout_height="wrap_content" android:paddingLeft="5dp" android:layout_weight="7" android:text="None" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:textStyle="bold" android:text="Device mode" /> <TextView android:id="@+id/device_mode" android:layout_width="0dp" android:layout_height="wrap_content" android:paddingLeft="5dp" android:layout_weight="7" android:text="None" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="5dp" android:paddingBottom="5dp" android:weightSum="10"> <Button android:id="@+id/btn_signIn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="5" android:gravity="center" android:text="Sign In"/> <Button android:id="@+id/btn_removeAccount" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="5" android:gravity="center" android:text="Sign Out" android:enabled="false"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal"> <Button android:id="@+id/btn_callGraphInteractively" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="5" android:text="Get Graph Data Interactively" android:enabled="false"/> <Button android:id="@+id/btn_callGraphSilently" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="5" android:text="Get Graph Data Silently" android:enabled="false"/> </LinearLayout> <TextView android:id="@+id/txt_log" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginTop="20dp" android:layout_weight="0.8" android:text="Output goes here..." /> </LinearLayout> </LinearLayout> </FrameLayout>
Az alkalmazás>src>fő>res elrendezésében>>nav_header_main.xml. Ha nincs nav_header_main.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="@dimen/nav_header_height" android:background="@drawable/side_nav_bar" android:gravity="bottom" android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:theme="@style/ThemeOverlay.AppCompat.Dark"> <ImageView android:id="@+id/imageView" android:layout_width="66dp" android:layout_height="72dp" android:contentDescription="@string/nav_header_desc" android:paddingTop="@dimen/nav_header_vertical_spacing" app:srcCompat="@drawable/microsoft_logo" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/nav_header_vertical_spacing" android:text="Azure Samples" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="MSAL Android" /> </LinearLayout>
Az alkalmazás>src>fő>res>menüjében>activity_main_drawer.xml. Ha nincs activity_main_drawer.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:showIn="navigation_view"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_single_account" android:icon="@drawable/ic_single_account_24dp" android:title="Single Account Mode" /> </group> </menu>
Az alkalmazás>src>fő>res értékei>dimens.xml.> Cserélje le a dimens.xml tartalmát a következő kódrészletre:
<resources> <dimen name="fab_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="nav_header_height">176dp</dimen> <dimen name="nav_header_vertical_spacing">8dp</dimen> </resources>
Az alkalmazás>src>fő>res értékei>colors.xml.> Cserélje le a colors.xml tartalmát a következő kódrészletre:
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="purple_200">#FFBB86FC</color> <color name="purple_500">#FF6200EE</color> <color name="purple_700">#FF3700B3</color> <color name="teal_200">#FF03DAC5</color> <color name="teal_700">#FF018786</color> <color name="black">#FF000000</color> <color name="white">#FFFFFFFF</color> <color name="colorPrimary">#008577</color> <color name="colorPrimaryDark">#00574B</color> <color name="colorAccent">#D81B60</color> </resources>
Az alkalmazás>src>fő>res értékei>strings.xml.> Cserélje le a strings.xml tartalmát a következő kódrészletre:
<resources> <string name="app_name">MSALAndroidapp</string> <string name="action_settings">Settings</string> <!-- Strings used for fragments for navigation --> <string name="first_fragment_label">First Fragment</string> <string name="second_fragment_label">Second Fragment</string> <string name="nav_header_desc">Navigation header</string> <string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string> <string name="next">Next</string> <string name="previous">Previous</string> <string name="hello_first_fragment">Hello first fragment</string> <string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string> <!-- TODO: Remove or change this placeholder text --> <string name="hello_blank_fragment">Hello blank fragment</string> </resources>
Az alkalmazás>src>fő>res értékei>styles.xml.> Ha nincs styles.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> </resources>
Az alkalmazás>src>fő>res értékei>themes.xml.> Cserélje le a themes.xml tartalmát a következő kódrészletre:
<resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> <style name="Theme.MSALAndroidapp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Primary brand color. --> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/white</item> <!-- Secondary brand color. --> <item name="colorSecondary">@color/teal_200</item> <item name="colorSecondaryVariant">@color/teal_700</item> <item name="colorOnSecondary">@color/black</item> <!-- Status bar color. --> <item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item> <!-- Customize your theme here. --> </style> <style name="Theme.MSALAndroidapp.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <style name="Theme.MSALAndroidapp.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="Theme.MSALAndroidapp.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> </resources>
Az alkalmazás>src>fő>újrarajzolható>>ic_single_account_24dp.xml. Ha nincs ic_single_account_24dp.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/> </vector>
Az alkalmazás>src>fő>rajzolható>>side_nav_bar.xml. Ha nincs side_nav_bar.xml a mappában, hozza létre és adja hozzá a következő kódrészletet:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:angle="135" android:centerColor="#009688" android:endColor="#00695C" android:startColor="#4DB6AC" android:type="linear" /> </shape>
Az alkalmazás>src>fő>újrarajzolható.> A mappához adjon hozzá egy png Microsoft-emblémát.
microsoft_logo.png
A felhasználói felület XML-ben való deklarálása lehetővé teszi, hogy elkülönítse az alkalmazás bemutatóját a viselkedését vezérlő kódtól. Az Android-elrendezésről további információt az Elrendezések című témakörben talál .
Az alkalmazás tesztelése
A futtatása helyileg
Az alkalmazást egy teszteszközön vagy emulátoron hozhatja létre és helyezheti üzembe. Be kell tudnia jelentkezni, és jogkivonatokat kell beszereznie a Microsoft Entra-azonosítóhoz vagy a személyes Microsoft-fiókokhoz.
A bejelentkezés után az alkalmazás megjeleníti a Microsoft Graph-végpontról /me
visszaadott adatokat.
Hozzájárulás
Amikor minden felhasználó először jelentkezik be az alkalmazásba, a Microsoft identitása kérni fogja, hogy járuljon hozzá a kért engedélyekhez. Egyes Microsoft Entra-bérlők letiltották a felhasználói hozzájárulást, ami megköveteli, hogy a rendszergazdák minden felhasználó nevében hozzájárulást adjanak. A forgatókönyv támogatásához vagy saját bérlőt kell létrehoznia, vagy rendszergazdai hozzájárulást kell kapnia.
Az erőforrások eltávolítása
Ha már nincs rá szükség, törölje az alkalmazás regisztrálása lépésben létrehozott alkalmazásobjektumot.
Súgó és támogatás
Ha segítségre van szüksége, szeretne jelentést készíteni egy problémáról, vagy szeretne többet megtudni a támogatási lehetőségekről, olvassa el a súgót és a fejlesztők támogatását.
Következő lépések
Az összetettebb forgatókönyvek megismeréséhez tekintse meg a GitHubon elvégzett működő kódmintát .
További információ a védett webes API-kat meghívó mobilalkalmazások többrészes forgatókönyv-sorozatunkban való készítéséről: