Android内存泄漏accountmanager依赖

Posted

技术标签:

【中文标题】Android内存泄漏accountmanager依赖【英文标题】:Android memory leak accountmanager dependency 【发布时间】:2018-02-22 12:25:44 【问题描述】:

对于我的项目,我使用 Retrofit 进行 API 调用。 对于身份验证,我使用的是 JWT 令牌。 对于该令牌的存储和刷新流程,我使用Google's AccountManager。

ApiService 类是一个单例,我构建如下:

 private ApiService(AccountManager accountManager) 
        mAccountManager = accountManager;
        build();
    

    public static synchronized ApiService getInstance(AccountManager accountManager) 
        if (mInstance == null) 
            mInstance = new ApiService(accountManager);
        
        return mInstance;
    

在这个课程中,我正在构建 OkHttp3 客户端和 Retrofit 服务。

当服务器响应挑战时,Okhttp3 有一个很好的方法,所以我这样设置:

 OkHttpClient client = new OkHttpClient.Builder()
    .authenticator(new ApiAuthenticator(mAccountManager))

如您所见,我正在设置 Okhttp3 Authenticator 的自定义实现,如下所示:

    /**
     * 2017 App-vise.
     * Created by daangeurts on 31/07/2017.
     */
    public class ApiAuthenticator implements Authenticator 

        private static final String TAG = "ApiAuthenticator";
        private AccountManager accountManager;


    ApiAuthenticator(AccountManager accountManager) 
        this.accountManager = accountManager;
    

    private static int responseCount(Response response) 
        int result = 1;
        while ((response = response.priorResponse()) != null) 
            result++;
        
        return result;
    

    /**
     * Returns a request that includes a credential to satisfy an authentication challenge in @code
     * response. Returns null if the challenge cannot be satisfied.
     *
     * @param route
     * @param response
     */
    @Nullable
    @Override
    public Request authenticate(@NonNull Route route, @NonNull Response response) throws IOException 
        if (response.request().url().encodedPath().startsWith("login_check") || response.request().url().encodedPath().startsWith("token"))
            return null;

        if (responseCount(response) >= 2) 
            // If both the original call and the call with refreshed token failed,
            // it will probably keep failing, so don't try again.
            return null;
        

        for (Challenge challenge : response.challenges()) 
            if (challenge.scheme().equals("Bearer")) 
                Account[] accounts = accountManager.getAccountsByType(AuthConstants.ACCOUNT_TYPE);
                if (accounts.length != 0) 
                    String oldToken = accountManager.peekAuthToken(accounts[0], AuthConstants.AUTHTOKEN_TYPE_FULL_ACCESS);

                    if (oldToken != null) 
                        accountManager.invalidateAuthToken(AuthConstants.ACCOUNT_TYPE, oldToken);
                    
                    try 
                        String token = accountManager.blockingGetAuthToken(accounts[0], AuthConstants.AUTHTOKEN_TYPE_FULL_ACCESS, false);

                        if (token == null) 
                            accountManager.removeAccount(accounts[0], null, null);
                        

                        if (token != null) 
                            Request.Builder builder = response.request().newBuilder();
                            return builder.header("Authorization", "Bearer " + token).build();
                        
                     catch (OperationCanceledException | AuthenticatorException e) 
                        e.printStackTrace();
                        Log.d(TAG, e.getLocalizedMessage());
                    
                
            
        
        return null;
    

我的某些函数的存储库如下所示:

public class ApiCheckinRepository implements CheckinRepository 

    private AccountManager accountManager;

    public ApiCheckinRepository(AccountManager accountManager) 
        this.accountManager = accountManager;
    

    @Override
    public Observable<Response<Message>> checkin(QrCode qrCode) 
       return ApiService.getInstance(accountManager).checkin(qrCode);
    

但是现在我遇到了由 accountManager 依赖引起的内存泄漏,我再也看不到解决方案了...

【问题讨论】:

【参考方案1】:

希望它能帮助您解决问题:-

public class AccountLeakHandler 

    public static AccountManagerFuture<Bundle> safeGetAuthToken(AccountManager accountManager,
                                                                Account account,
                                                                String authTokenType,
                                                                Bundle options,
                                                                Activity activity,
                                                                AccountManagerCallback<Bundle> callback,
                                                                Handler handler) 
        return accountManager.getAuthToken(
                account,
                authTokenType,
                options,
                null,
                new ResolveLeakAccountManagerCallback(callback, activity),
                handler);
    


    public static class ResolveLeakAccountManagerCallback implements AccountManagerCallback<Bundle> 

        private AccountManagerCallback<Bundle> originalCallback = null;
        private Activity originalActivity = null;

        public ResolveLeakAccountManagerCallback(AccountManagerCallback<Bundle> callback, Activity activity) 
            originalCallback = callback;
            originalActivity = activity;
        

        @Override
        public void run(AccountManagerFuture<Bundle> future) 
            Bundle bundle = null;
            try 
                bundle = future.getResult();
             catch (Exception e) 
                // error happen
            

            if (bundle == null) 
                callAndClear(future);
             else 
                Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
                if (intent != null) 
                    if (originalActivity != null) 
                        try 
                            //make FutureTask callback again
                            new RInstance(FutureTask.class, future).setValue("state", 0);
                         catch (Exception e) 
                        originalActivity.startActivity(intent);
                     else 
                        callAndClear(future);
                    
                 else 
                    callAndClear(future);
                
            
        

        private void callAndClear(AccountManagerFuture<Bundle> future) 
            if (originalCallback != null) 
                originalCallback.run(future);
                originalCallback = null;
            
            originalActivity = null;
        
    

欲了解更多信息,请访问以下链接:-

https://github.com/square/leakcanary/issues/97

Binder preventing garbage collection

【讨论】:

以上是关于Android内存泄漏accountmanager依赖的主要内容,如果未能解决你的问题,请参考以下文章

Android内存泄漏查找和解决

Android内存优化三:内存泄漏检测与监控

Android ValueAnimator --内存泄漏

Android应用程序内存泄漏介绍

android 内存泄漏检测工具 LeakCanary 泄漏金丝雀

Android技术分享| Android 中部分内存泄漏示例及解决方案