One, the introduction

Daily Android development, to compatible with 6.0 version above, it is inevitable to avoid 6.0 new runtime permission application, that as Android developers, must master and adapt to this situation.Copy the code

Two, the Kuttt point

  1. Easy to call, call only need to provide three parameters
    • FragmentActivity instance
    • CallBack CallBack instance
    • Array of permissions to apply for

Third, Ku’s thought

  1. Move the permission request task to a proxy Fragment and the permission onResult method to that class.
  2. When other projects need access to 6.0 privileges, you don’t need to change the code in the Activity, just add a request method call to provide callbacks and an arraylist of privileges.
  3. RxPermission is the same idea, but the author is not familiar with RxJava, so he wrote one himself.

Four, structure,

1, the Callback

public interface PermissionCallback {
    /** * Permission permission */
    void onGranted(a);

    /** * Permission denied *@paramPerms Rejected permission set */
    void onDenied(List<String> perms);
}
Copy the code

2, FastPermission

public class FastPermission {
    private FastPermission(a) {}private static class Singleton {
        private static final FastPermission instance = new FastPermission();
    }

    public static FastPermission getInstance(a) {
        return Singleton.instance;
    }

    /** * If the value is smaller than 6.0, the permission is not checked@returnIf the version number is greater than 6.0, check permissions */
    private boolean isNeedCheck(a) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
    }

    /** * Check whether the specified permission has been obtained */
    public boolean isAccept(Context context, String permission) {
        if(! isNeedCheck()) {return true;
        } else {
            returnisNeedCheck() && ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; }}/** * Use the Activity to request permission **@paramActivity To inject the FragmentActivity * of the permission request broker@paramCallback permission application success or failure Callback *@paramPerms permission list array */
    public void request(@NonNull FragmentActivity activity, @NonNull PermissionCallback callback, @NonNull String[] perms) {
        PermissionDelegateFragment delegate = findDelegate(activity);
        if(delegate ! =null) { delegate.requestPermission(activity, callback, perms); }}/** * Use Fragment to request permission **@paramFragment Indicates the fragment * used@paramCallback permission application success or failure Callback *@paramPerms permission list array */
    public void request(@NonNull Fragment fragment, @NonNull PermissionCallback callback, @NonNull String[] perms) {
        FragmentActivity activity = fragment.getActivity();
        if(activity ! =null && !activity.isFinishing()) {
            PermissionDelegateFragment delegate = findDelegate(activity);
            if(delegate ! =null) { delegate.requestPermission(activity, callback, perms); }}}/** * Build a hidden fragment for permission */
    private PermissionDelegateFragment findDelegate(FragmentActivity activity) {
        returnPermissionDelegateFinder.getInstance().find(activity); }}Copy the code
  • Permission adaptation is only greater than 6.0. 6.0 does not detect permissions by default.
  • The Manager class provides a request() method for Activity calls.
  • The findDelegate() method internally calls Finder to find and add fragments.

3, PermissionDelegateFinder

public class PermissionDelegateFinder {
    private static final String DELEGATE_FRAGMENT_TAG = PermissionDelegateFragment.class.getSimpleName() + "Tag";

    private static class Singleton {
        private static final PermissionDelegateFinder instance = new PermissionDelegateFinder();
    }

    public static PermissionDelegateFinder getInstance(a) {
        return Singleton.instance;
    }

    /** * Add hidden permission fragment */
    public PermissionDelegateFragment find(@NonNull FragmentActivity activity) {
        PermissionDelegateFragment fragment = null;
        if(activity ! =null && !activity.isFinishing()) {
            FragmentManager fm = activity.getSupportFragmentManager();
            fragment = (PermissionDelegateFragment) fm.findFragmentByTag(DELEGATE_FRAGMENT_TAG);
            if (fragment == null) { fragment = PermissionDelegateFragment.newInstance(); fm.beginTransaction() .add(fragment, DELEGATE_FRAGMENT_TAG) .commitAllowingStateLoss(); }}returnfragment; }}Copy the code
  • Find (), which passes in the FragmentActivity and uses the FragmentManager within it to find, add, or add if it cannot find it.

PermissionDelegateFragment

public class PermissionDelegateFragment extends LifecycleFragment {
    // The identity of the permission callback
    private static final int REQUEST_CODE = 0X0122;
    private SparseArrayCompat<RequestEntry> callbacks = new SparseArrayCompat<RequestEntry>();

    public static PermissionDelegateFragment newInstance(a) {
        return new PermissionDelegateFragment();
    }

    @Override
    public void onDetach(a) {
        super.onDetach();
        popAll();
        getLifecycle().removeAllListener();
    }

    /** * The request action must be called after OnAttach@paramEntry requests wrapping object */
    private void pushStack(final RequestEntry entry) {
        callbacks.put(entry.hashCode(), entry);
        this.getLifecycle().addListener(new SimpleFragmentLifecycleAdapter() {
            @Override
            public void onAttach(a) {
                super.onAttach();
                callbacks.get(entry.hashCode()).getRunnable().run();
                getLifecycle().removeListener(this); }}); }/** * removes ** from the collection@paramEntry Request wrapper object to remove */
    private void popStack(RequestEntry entry) {
        callbacks.remove(entry.hashCode());
    }

    /** * remove all callback */
    private void popAll(a) {
        if(callbacks ! =null && callbacks.size() > 0) { callbacks.clear(); }}/** * Apply for permission in batches **@paramContext *@paramCallback permission allows or denies callback *@paramPerms specifies the permission array */ to request
    public void requestPermission(final Context context, final PermissionCallback callback, final String[] perms) {
        if(callback ! =null && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            callback.onGranted();
            return;
        }
        pushStack(RequestEntry.newBuilder().withCallback(callback).withRunnable(new Runnable() {
            @Override
            public void run(a) {
                // Only apply for permissions that are not allowed by the user
                List<String> unGrantedList = new ArrayList<String>();
                for (String permission : perms) {
                    if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                        unGrantedList.add(permission);
                    }
                }
                if (unGrantedList.size() > 0) {
                    PermissionDelegateFragment.this.requestPermissions(unGrantedList.toArray(new String[]{}), REQUEST_CODE);
                } else {
                    if(callback ! =null) {
                        callback.onGranted();
                    }
                }
            }
        }).build());
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_CODE:
                if (grantResults.length > 0&& callbacks ! =null && callbacks.size() > 0) {
                    for (int i = 0; i < callbacks.size(); i++) {
                        RequestEntry entry = callbacks.valueAt(i);
                        PermissionCallback callback = entry.getCallback();
                        // Find the denied permission
                        List<String> deniedList = new ArrayList<String>();
                        for (int j = 0; j < grantResults.length; j++) {
                            int grantResult = grantResults[j];
                            String permission = permissions[j];
                            if (grantResult != PackageManager.PERMISSION_GRANTED) {
                                deniedList.add(permission);
                            }
                        }
                        // All allowed
                        if (deniedList.isEmpty()) {
                            callback.onGranted();
                        } else{ callback.onDenied(deniedList); } popStack(entry); }}break; }}}Copy the code
  • RequestPermission (), which is the request method that the proxy Fragment provides to finder calls.
  • OnRequestPermissionsResult () is to request a callback, allowing all the permissions, the correction of callback onGranted (), are not allowed, not the callback for save permissions, and callback onDenied method comes back.
  • PushStack () adds a task to a queue, and removes the popStack and popAll tasks when they are finished. In pushStack there is also a call to getLifecycle().addListener(). Why is that? Because fragments just after new operation, add into the Activity after also need to Attach, to apply for, and callback onRequestPermissionsResult (), otherwise it will throw an exception, tip need to Attach, continue to look at.
  • Request will probably be called before onAttach, and if we add flag bits, save callback and instance, and then callback, wouldn’t it be more elegant, like pushing a task into a queue, and then executing the tasks in the queue in sequence when onAttach is attached? This is a reference to Glide’s life cycle management, which borrows its own onAttach() callback and adds a listener callback.

RequestEntry, a class that wraps Callback and runnable for each request.

/** * Created by Hezihao on 2017/10/13. * Callback and runnable */

public class RequestEntry {
    private  PermissionCallback callback;
    private Runnable runnable;

    private RequestEntry(a) {}public RequestEntry newInstance(Builder builder) {
        this.callback = builder.callback;
        this.runnable = builder.runnable;
        return this;
    }

    public static Builder newBuilder(a) {
        return new Builder();
    }

    public PermissionCallback getCallback(a) {
        return callback;
    }

    public Runnable getRunnable(a) {
        return runnable;
    }

    public static class Builder {
        private  PermissionCallback callback;
        private Runnable runnable;

        public Builder withCallback(PermissionCallback callback) {
            this.callback = callback;
            return this;
        }

        public Builder withRunnable(Runnable runnable) {
            this.runnable = runnable;
            return this;
        }

        public RequestEntry build(a) {
            RequestEntry entry = new RequestEntry();
            return entry.newInstance(this); }}}Copy the code

Lifecycle callback

public interface Lifecycle {
    /** * Add lifecycle callback listener *@param listener
     */
    void addListener(FragmentLifecycleListener listener);

    /** * Removes the lifecycle callback listener *@param listener
     */
    void removeListener(FragmentLifecycleListener listener);

    /** * Remove all lifecycle callback listeners */
    void removeAllListener(a);
}
Copy the code
  • Concrete implementation class
public class FragmentLifecycle implements Lifecycle {
    // Read/write separation, avoid traversal at the same time add to the collection, throw high concurrency exceptions.
    private final CopyOnWriteArrayList<FragmentLifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<FragmentLifecycleListener>();
    private boolean isAttach;
    private boolean isStarted;
    private boolean isDestroyed;

    @Override
    public void addListener(FragmentLifecycleListener listener) {
        lifecycleListeners.add(listener);
        if (isAttach) {
            listener.onAttach();
        } else if (isDestroyed) {
            listener.onDestroy();
        } else if (isStarted) {
            listener.onStart();
        } else if (isStarted == false) {
            listener.onStop();
        } else{ listener.onDetach(); }}@Override
    public void removeListener(FragmentLifecycleListener listener) {
        if (lifecycleListeners.size() > 0&& lifecycleListeners.contains(listener)) { lifecycleListeners.remove(listener); }}@Override
    public void removeAllListener(a) {
        if (lifecycleListeners.size() > 0) { lifecycleListeners.clear(); }}public void onAttach(a) {
        isAttach = true;
        for(FragmentLifecycleListener listener : lifecycleListeners) { listener.onAttach(); }}public void onStart(a) {
        isStarted = true;
        for(FragmentLifecycleListener listener : lifecycleListeners) { listener.onStart(); }}public void onStop(a) {
        isStarted = false;
        for(FragmentLifecycleListener listener : lifecycleListeners) { listener.onStop(); }}public void onDestroy(a) {
        isDestroyed = true;
        for(FragmentLifecycleListener listener : lifecycleListeners) { listener.onDestroy(); }}public void onDetach(a) {
        isAttach = false;
        for(FragmentLifecycleListener listener : lifecycleListeners) { listener.onDetach(); }}}Copy the code
  • LifecycleFragment using this implementation, PermissionDelegateFragment is inherited in the class
public class LifecycleFragment extends Fragment {
    private FragmentLifecycle lifecycle;

    public LifecycleFragment(a) {
        this(new FragmentLifecycle());
    }

    @SuppressLint("ValidFragment")
    public LifecycleFragment(FragmentLifecycle lifecycle) {
        this.lifecycle = lifecycle;
    }

    public FragmentLifecycle getLifecycle(a) {
        return lifecycle;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        lifecycle.onAttach();
    }

    @Override
    public void onStart(a) {
        super.onStart();
        lifecycle.onStart();
    }

    @Override
    public void onStop(a) {
        super.onStop();
        lifecycle.onStop();
    }

    @Override
    public void onDestroy(a) {
        super.onDestroy();
        lifecycle.onDestroy();
    }

    @Override
    public void onDetach(a) {
        super.onDetach(); lifecycle.onDetach(); }}Copy the code

use

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final String[] permissionList;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            permissionList = new String[]
                    {Manifest.permission.READ_EXTERNAL_STORAGE
                            , Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE};
        } else {
            permissionList = new String[]
                    {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE};
        }

        Button request = (Button) findViewById(R.id.request);
        request.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) { requestPermission(permissionList); }}); }/** * Request permission **@paramPerms requires permission array */
    private void requestPermission(String[] perms) {
        FastPermission.getInstance().request(MainActivity.this.new PermissionCallback() {
            @Override
            public void onGranted(a) {
                Toast.makeText(MainActivity.this."Permission applied successfully, proceed to the next step.", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDenied(final List<String> perms) {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("Application permission")
                        .setMessage("Please allow all permissions applied by app for normal use.")
                        .setPositiveButton("Sure".new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                // Request permission again
                                String[] againPerms = new String[perms.size()];
                                for (int j = 0; j < perms.size(); j++) {
                                    againPerms[j] = perms.get(j);
                                }
                                requestPermission(againPerms);
                            }
                        })
                        .setNegativeButton("Cancel".new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }).create().show(); } }, perms); }}Copy the code

Quick to use

  • Making a link
  • Start, issue, push request!
  • Gradle rely on
compile 'com. HZH: fast - permission: 1.0.7'
Copy the code