如何在我的Android应用程序中撤消Google帐户访问权限?

MSS

我是android新手,想知道如何撤消使用GoogleSignInApi登录到我的应用程序的用户的访问权限。

当用户撤消应用内的访问权限时,我希望我的应用询问用户的Google帐户ID。目前,即使用户单击了撤消访问按钮,我的应用程序也会在用户单击“登录”按钮后自动登录。

当用户单击我的应用程序的“撤消”或“退出”按钮时,我正在广播意图。以下是我的MainActivity的代码

public class MasterActivity extends AppCompatActivity
    implements NavigationView.OnNavigationItemSelectedListener, GoogleApiClient.OnConnectionFailedListener {

private static final String TAG = MasterActivity.class.getSimpleName();

private SharedPreferences sp;
private SharedPreferences.Editor editor;

private BroadcastReceiver mSignOutReceiver;
private IntentFilter mSignOutFilter;

private IntentFilter mRevokeAccessFilter;
private BroadcastReceiver mRevokeAccessReceiver;


private String mUserName;
private String mPhotoUrl;
private String mEmailId;
private static final String ANONYMOUS = "anonymous";

private ImageView profileImageView;
private TextView profileDisplayName, profileEmailId;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // [START Check for sign out broadcast.]
    mSignOutFilter = new IntentFilter();
    mSignOutFilter.addAction(getString(R.string.action_signout));
    mSignOutReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "Sign Out in progress.");
            Intent signinIntent = new Intent(getApplicationContext(), SigninActivity.class);
            startActivity(signinIntent);
            finish();
        }
    };
    this.registerReceiver(mSignOutReceiver, mSignOutFilter);
    // [END Check for sign out broadcast.]

    mRevokeAccessFilter = new IntentFilter();
    mRevokeAccessFilter.addAction(getString(R.string.action_revoke));
    mRevokeAccessReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "Revoke access in progress.");
            Intent revokeIntent = new Intent(getApplicationContext(), SigninActivity.class);
            startActivity(revokeIntent);
            finish();
        }
    };
    this.registerReceiver(mRevokeAccessReceiver, mRevokeAccessFilter);

    setContentView(R.layout.activity_master);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

   /* FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();
        }
    });*/

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
            this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.setDrawerListener(toggle);
    toggle.syncState();

    NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
    navigationView.setNavigationItemSelectedListener(this);

    sp = getSharedPreferences(getString(R.string.user_cred_sp),MODE_PRIVATE);
    editor = sp.edit();

    if(!sp.contains("USER_ID")){
        //Not signed in, launch the Sign In activity
        Log.d(TAG, "User id not is present.");
        startActivity(new Intent(this, SigninActivity.class));
        finish();
        return;
    } else {
        // [START Set the navigation header details]
        mUserName = sp.getString("USER_DISPLAY_NAME",ANONYMOUS);
        mPhotoUrl = sp.getString("USER_PIC_URL",null);
        mEmailId = sp.getString("USER_EMAIL","[email protected]");
        View headerView = navigationView.inflateHeaderView(R.layout.nav_header_master);
        profileDisplayName = (TextView) headerView.findViewById(R.id.UserNameProfile);
        profileDisplayName.setText(mUserName);
        profileEmailId = (TextView) headerView.findViewById(R.id.EmailIdProfile);
        profileEmailId.setText(mEmailId);
        profileImageView = (ImageView) headerView.findViewById(R.id.ImageViewProfile);
        if(mPhotoUrl!=null){
            Glide.with(getApplicationContext()).load(mPhotoUrl)
                    .thumbnail(0.5f)
                    .crossFade()
                    .diskCacheStrategy(DiskCacheStrategy.ALL)
                    .into(profileImageView);
        }
        //TODO: The orientation of views and image is not proper
        // [END Set the navigation header details]
    }
}

/*@Override
protected void onResume(){
    super.onResume();
    this.registerReceiver(mSignOutReceiver, signOutFilter);
}*/

@Override
protected void onDestroy(){
    super.onDestroy();
    this.unregisterReceiver(mSignOutReceiver);
    this.unregisterReceiver(mRevokeAccessReceiver);
}

@Override
public void onBackPressed() {
    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    if (drawer.isDrawerOpen(GravityCompat.START)) {
        drawer.closeDrawer(GravityCompat.START);
    } else {
        super.onBackPressed();
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.master, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();

    if (id == R.id.action_settings) {
        return true;
    } else if (id == R.id.sign_out_menu ){
        signOutBroadCast();
        return true;
    } else if (id == R.id.revoke_menu){
        revokeAccessBroadCast();
        return true;
    }
    return super.onOptionsItemSelected(item);
}

@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
    // Handle navigation view item clicks here.
    int id = item.getItemId();

    if (id == R.id.nav_logout) {
        // Handle the camera action
    } else if (id == R.id.nav_gallery) {

    } else if (id == R.id.nav_slideshow) {

    } else if (id == R.id.nav_manage) {

    } else if (id == R.id.nav_share) {

    } else if (id == R.id.nav_send) {

    }

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
}

private void signOutBroadCast(){
    // 1. Clear the shared preference.
    editor.clear();
    editor.apply();
    // 2.Send a sign out broadcast
    Intent signOutIntent = new Intent();
    signOutIntent.setAction(getString(R.string.action_signout));
    sendBroadcast(signOutIntent);
    // 3. Start the login Activity
    /*Intent signinIntent = new Intent(this,SigninActivity.class);
    startActivity(signinIntent);
    finish();*/
}

private void revokeAccessBroadCast(){
    // 1. Clear the shared preference.
    editor.clear();
    editor.apply();
    // 2.Send a revoke intent.
    Intent revokeIntent = new Intent();
    revokeIntent.setAction(getString(R.string.action_revoke));
    sendBroadcast(revokeIntent);
    //signOutBroadCast();
}

@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
    Log.d(TAG, "onConnectionFailed: "+ connectionResult);
}
}

这是我的SignInActivity代码

public class SigninActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener, View.OnClickListener,
GoogleApiClient.ConnectionCallbacks{

private static final String TAG = SigninActivity.class.getSimpleName();
private static final int REQ_ACCPICK = 1;
private static final int RC_SIGN_IN = 2;

private GoogleSignInOptions mGoogleSignInOptions;
private GoogleApiClient mGoogleApiClient;
private SharedPreferences sp;
private SharedPreferences.Editor editor;

private IntentFilter mSignOutFilter;
private BroadcastReceiver mSignOutReceiver;

private IntentFilter mRevokeAccessFilter;
private BroadcastReceiver mRevokeAccessReceiver;

private boolean isAccntConnected;

private SignInButton btnSignIn;
private ProgressDialog mProgressDialog;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mSignOutFilter = new IntentFilter();
    mSignOutFilter.addAction(getString(R.string.action_signout));
    mSignOutReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            signOutIfConnected();
            Log.d(TAG, "Sign out complete.");
        }
    };
    this.registerReceiver(mSignOutReceiver, mSignOutFilter);

    mRevokeAccessFilter = new IntentFilter();
    mRevokeAccessFilter.addAction(getString(R.string.action_revoke));
    mRevokeAccessReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG,"Revoke access");
            revokeAccess();
            Log.d(TAG, "Complete access revoked.");
        }
    };
    this.registerReceiver(mRevokeAccessReceiver, mRevokeAccessFilter);
    // [START Sign out if connected.]
    //signOutIfConnected();
    // [END Sign out if connected.]
    setContentView(R.layout.activity_signin);
    btnSignIn = (SignInButton) findViewById(R.id.btn_sign_in);

    btnSignIn.setOnClickListener(this);
    // [START Configure sign in]
    // configure sign in options for google account
    mGoogleSignInOptions= new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestEmail()
            .build();
    // [END Configure sign in]
    isAccntConnected= false;
    // [START Build Google api client]
    /*mGoogleApiClient = new GoogleApiClient.Builder(this)
            .enableAutoManage(this, this)
            .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
            .build();*/
    // [END Build Google api client]
    btnSignIn.setSize(SignInButton.SIZE_STANDARD);
    btnSignIn.setScopes(mGoogleSignInOptions.getScopeArray());

    sp = getSharedPreferences(getString(R.string.user_cred_sp), MODE_PRIVATE);
    editor = sp.edit();

}

/*@Override
protected void onResume(){
    super.onResume();
    this.registerReceiver(mSignOutReceiver, mSignOutFilter);
    this.registerReceiver(mRevokeAccessReceiver, mRevokeAccessFilter);
}*/

/*@Override
protected void onPause(){
    super.onPause();
    this.unregisterReceiver(mSignOutReceiver);
    this.unregisterReceiver(mRevokeAccessReceiver);
}*/
@Override
protected void onDestroy(){
    super.onDestroy();
    this.unregisterReceiver(mSignOutReceiver);
    this.unregisterReceiver(mRevokeAccessReceiver);
}

@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
    Log.d(TAG, "onConnectionFailed: " + connectionResult);
}

private void initGAC(){
    /*mGoogleSignInOptions= new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .setAccountName(acntName)
            .build();*/
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .enableAutoManage(this, this)
            .addApi(Auth.GOOGLE_SIGN_IN_API, mGoogleSignInOptions)
            //.setAccountName(acntName)
            .build();
}

@Override
public void onStart() {
    super.onStart();
    if(mGoogleApiClient!=null){
        OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
        if (opr.isDone()) {
            //  If the user's cached credentials are valid, the OptionalPendingResult will be "done"
            // and the GoogleSignInResult will be available instantly.
            Log.d(TAG, "Got cached sign in");
            GoogleSignInResult result = opr.get();
            handleSignInResult(result);
        } else {
            // If the user has not previously signed in on this device or the sign-in has expired,
            // this asynchronous branch will attempt to sign in the user silently.  Cross-device
            // single sign-on will occur in this branch.
            showProgressDialog();
            opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
                @Override
                public void onResult(GoogleSignInResult googleSignInResult) {
                    hideProgressDialog();
                    handleSignInResult(googleSignInResult);
                }
            });
        }
    }
}

@Override
public void onClick(View v) {
    int id = v.getId();
    if (id == R.id.btn_sign_in) {
        signIn();
    }
}

private void signIn() {
    /*startActivityForResult(AccountPicker.newChooseAccountIntent(
            null, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null
    ),REQ_ACCPICK);*/
    Log.d(TAG, "Sign in method called.");
    initGAC();
    Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
    startActivityForResult(signInIntent, RC_SIGN_IN);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    /*if(requestCode == REQ_ACCPICK){
        if(data!=null && data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)!=null){
            mEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
            initGAC(mEmail);
            Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
            startActivityForResult(signInIntent, RC_SIGN_IN);
        }
    } else*/
    if (requestCode == RC_SIGN_IN) {
        Log.d(TAG, "Sign in request");
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        handleSignInResult(result);
    }
}

private void handleSignInResult(GoogleSignInResult result) {
    Log.d(TAG, "handleSignInResult:" + result.isSuccess());
    if (result.isSuccess()) {
        // Sign in successful. Show authenticated UI
        // Set the data in intent and send it to next activity
        GoogleSignInAccount acct = result.getSignInAccount();
        Log.d(TAG, "Account Profile URL: "+acct.getPhotoUrl().toString());
        String userId = acct.getId();
        String userDisplayName = acct.getDisplayName();
        String userPhotoUrl = acct.getPhotoUrl().toString();
        String userEmail = acct.getEmail();

        //Set the id in shared preferences so that it can be used to log out
        editor.putString("USER_ID", userId);
        editor.putString("USER_DISPLAY_NAME", userDisplayName);
        editor.putString("USER_PIC_URL", userPhotoUrl);
        editor.putString("USER_EMAIL", userEmail);
        editor.commit();
        //dataIntent.putExtra("USER_EMAIL",userEmail);
        Intent dataIntent = new Intent(this, MasterActivity.class);
        startActivity(dataIntent);
    }
}

private void showProgressDialog() {
    if (mProgressDialog == null) {
        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setMessage(getString(R.string.loading));
        mProgressDialog.setIndeterminate(true);
    }
    mProgressDialog.show();
}

private void hideProgressDialog() {
    if (mProgressDialog != null && mProgressDialog.isShowing()) {
        mProgressDialog.hide();
    }
}

private void signOutIfConnected() {
    if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
        //mEmail=null;
        mGoogleApiClient.clearDefaultAccountAndReconnect();
        Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(
                new ResultCallback<Status>() {
                    @Override
                    public void onResult(Status status) {
                        Log.d(TAG, "Sign Out using Google Api.");
                        mGoogleApiClient.disconnect();
                        isAccntConnected = false;
                    }
                });
    }
}

private void revokeAccess(){
    if (mGoogleApiClient != null && mGoogleApiClient.isConnected() && isAccntConnected == true) {

        Auth.GoogleSignInApi.revokeAccess(mGoogleApiClient).setResultCallback(
                new ResultCallback<Status>() {
                    @Override
                    public void onResult(Status status) {
                        Log.d(TAG, "Revoke access using Google Api.");
                        signOutIfConnected();
                    }
                });
    }
}

@Override
public void onConnected(@Nullable Bundle bundle) {
    this.isAccntConnected = true;
}

@Override
public void onConnectionSuspended(int i) {

}

/*@Override
public void onConnected(@Nullable Bundle bundle) {
    if(!sp.contains("USER_ID_TOKEN")){
        signOut();
    }
}

@Override
public void onConnectionSuspended(int i) {

}*/
}

我可能缺少某些东西或做错了什么。请指导我。

MSS

花了两天时间,尝试了解Android活动的生命周期后,我能够找到解决该问题的方法。上面的代码有两个主要缺陷。

  1. 我尚未在GoogleApiClient中注册回调。由于此代码不在onConnected(),onConnectionSuspended()和onConnectionFailed()内部流动。解决方法是按如下所示进行注册:

     mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi(Auth.GOOGLE_SIGN_IN_API, mGoogleSignInOptions)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();
    
  2. 我已经启用了GoogleApiClient的自动管理功能,如此链接https://developers.google.com/identity/sign-in/android/所示尽管这不是一个缺陷,但现在我无法利用它来谋取利益。

onStart调用时,它会自动连接GoogleApiClient实例,调用时,它onStop断开连接因此,当用户登录到我的应用程序时,该应用程序会将她带到MasterActivity。在此过程中,onStop将调用SignInActivity的方法。这断开了GoogleApiClient实例的连接(请记住automanaged )。因此解决方案是在我的代码中自己管理它。

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == RC_SIGN_IN && resultCode == RESULT_OK) {
        Log.d(TAG, "Sign in request");
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        mGoogleApiClient.connect();//CALL TO CONNECT GoogleApiClient
        handleSignInResult(result);
    }
}

private void signOutIfConnected() {
    if (mGoogleApiClient.isConnected()) {
        mGoogleApiClient.clearDefaultAccountAndReconnect();
        Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(
                new ResultCallback<Status>() {
                    @Override
                    public void onResult(Status status) {
                        Log.d(TAG, "Sign Out using Google Api.");
                        mGoogleApiClient.disconnect();
                        //CALL TO DISCONNECT GoogleApiClient
                        isAccntConnected = false;
                    }
                });
    }
}

private void revokeAccess(){
if (isAccntConnected == true) {

    Auth.GoogleSignInApi.revokeAccess(mGoogleApiClient).setResultCallback(
            new ResultCallback<Status>() {
                @Override
                public void onResult(Status status) {
                    Log.d(TAG, "Revoke access using Google Api.");
                    //signOutIfConnected(); //REMOVED
                }
            });
    }
}
 @Override
public void onConnected(@Nullable Bundle bundle) {
    isAccntConnected = true;
}

我还引入了一个布尔标志isAccntConnected,以确保onConnected在调用revokeAccess方法之前已经方法进行了调用,如此处https://developers.google.com/identity/sign-in/android/disconnect所示,在调用revokeAccess之前,必须确认已调用GoogleApiClient.onConnected。

完成这些更改后,我的应用程序将按预期运行。取消访问的代码保持不变,除了我删除了signOutIfConnectedonResult方法内部对方法的嵌套调用

希望这个答案对像我这样的许多初学者有帮助。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何授予Facebook帐户的应用程序访问权限

来自分类Dev

如何更改Android中Twitter的应用程序访问权限?

来自分类Dev

如何在我的Ember应用程序中访问jsPDF?

来自分类Dev

撤消对应用程序Gmail API的访问权限

来自分类Dev

firebase SSR问题,“应用程序请求访问您的Google帐户的权限”弹出,而不是网站

来自分类Dev

如何在我的Android应用程序中实现和使用Google Cloud SQL(Eclipse)

来自分类Dev

如何在Java单击按钮中注销我的Android应用程序中的Google用户?

来自分类Dev

如何在我的Google地图应用程序(Android)中检查Internet连接

来自分类Dev

如何在Java中单击Button并在我的Android应用程序中注销Google用户?

来自分类Dev

如何在我的Google地图应用程序(Android)中检查Internet连接

来自分类Dev

如何在Android应用程序上使用Google帐户注册用户

来自分类Dev

如何在Android的我的应用程序中打开其他应用程序的链接

来自分类Dev

如何在 Facebook 开发者网站中删除我的应用程序被拒绝的权限?

来自分类Dev

如何在Azure AD上向我自己的应用程序添加应用程序权限

来自分类Dev

如何检查位置权限已由用户在android 6.0中为我的应用程序提供?

来自分类Dev

如何在android中获取应用程序权限设置并显示在屏幕上?

来自分类Dev

如何确保我的Android 6 Cordova应用程序在尝试访问文件系统之前提示用户授予权限?

来自分类Dev

我的应用程序中的权限错误

来自分类Dev

如何在Android应用程序中使用Google公共API访问密钥?

来自分类Dev

如何在Android应用程序中使用Google公共API访问密钥?

来自分类Dev

如何查找用户用来登录我的基于Google Fit的android应用程序的帐户(电子邮件)?

来自分类Dev

我们如何在C#Windows应用程序中访问动态创建的控件?

来自分类Dev

如何在linkedin开发人员应用程序中设置“ r_fullprofile”访问权限?

来自分类Dev

如何在linkedin开发人员应用程序中设置“ r_fullprofile”访问权限?

来自分类Dev

从Android中的GoogleApiClient获取我的应用程序的访问令牌

来自分类Dev

如何在我的android应用程序的用户界面中调用另一个android应用程序?

来自分类Dev

Android如何检查应用程序权限?

来自分类Dev

如何在我自己的Google地图应用程序中设置Google Maps Directions?

来自分类Dev

如何检查我在我的商家帐户中开发的应用程序的 Apple 应用程序内购买交易 ID?

Related 相关文章

  1. 1

    如何授予Facebook帐户的应用程序访问权限

  2. 2

    如何更改Android中Twitter的应用程序访问权限?

  3. 3

    如何在我的Ember应用程序中访问jsPDF?

  4. 4

    撤消对应用程序Gmail API的访问权限

  5. 5

    firebase SSR问题,“应用程序请求访问您的Google帐户的权限”弹出,而不是网站

  6. 6

    如何在我的Android应用程序中实现和使用Google Cloud SQL(Eclipse)

  7. 7

    如何在Java单击按钮中注销我的Android应用程序中的Google用户?

  8. 8

    如何在我的Google地图应用程序(Android)中检查Internet连接

  9. 9

    如何在Java中单击Button并在我的Android应用程序中注销Google用户?

  10. 10

    如何在我的Google地图应用程序(Android)中检查Internet连接

  11. 11

    如何在Android应用程序上使用Google帐户注册用户

  12. 12

    如何在Android的我的应用程序中打开其他应用程序的链接

  13. 13

    如何在 Facebook 开发者网站中删除我的应用程序被拒绝的权限?

  14. 14

    如何在Azure AD上向我自己的应用程序添加应用程序权限

  15. 15

    如何检查位置权限已由用户在android 6.0中为我的应用程序提供?

  16. 16

    如何在android中获取应用程序权限设置并显示在屏幕上?

  17. 17

    如何确保我的Android 6 Cordova应用程序在尝试访问文件系统之前提示用户授予权限?

  18. 18

    我的应用程序中的权限错误

  19. 19

    如何在Android应用程序中使用Google公共API访问密钥?

  20. 20

    如何在Android应用程序中使用Google公共API访问密钥?

  21. 21

    如何查找用户用来登录我的基于Google Fit的android应用程序的帐户(电子邮件)?

  22. 22

    我们如何在C#Windows应用程序中访问动态创建的控件?

  23. 23

    如何在linkedin开发人员应用程序中设置“ r_fullprofile”访问权限?

  24. 24

    如何在linkedin开发人员应用程序中设置“ r_fullprofile”访问权限?

  25. 25

    从Android中的GoogleApiClient获取我的应用程序的访问令牌

  26. 26

    如何在我的android应用程序的用户界面中调用另一个android应用程序?

  27. 27

    Android如何检查应用程序权限?

  28. 28

    如何在我自己的Google地图应用程序中设置Google Maps Directions?

  29. 29

    如何检查我在我的商家帐户中开发的应用程序的 Apple 应用程序内购买交易 ID?

热门标签

归档