RxPermissions 源碼解析之舉一反三

[toc]

RxPermissions 源碼解析

簡介

RxPermissions 是基于 RxJava 開發的用于幫助 在Android 6.0 中處理運行時權限檢測的框架。在 Android 6.0 中增加了對危險權限的動態申請,而不是像 Android 6.0 之前的默認全部獲取的方式。

原始動態權限的獲取

如果按照以往的獲取權限方式的話,那么我們獲取權限一般需要有 3 個步驟,第一步是先判斷當前是否已經獲取到該權限了;第 2 步申請對應的權限;第 3 步在 Activity 或者 Fragment 中處理獲取權限的結果。具體的實現步驟如下:

  • step 1:判斷權限是否已經獲取。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CAMERA)) {//用于開發者提示用戶權限的用途} else {//申請權限}
復制代碼
  • step 2:申請權限
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CAMERA},REQUEST_CAMERA);
復制代碼
  • step 3:結果處理
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {// 判斷請求碼,確定當前申請的權限if (requestCode == REQUEST_CAMERA) {//判斷權限是否申請通過if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {//授權成功} else {//授權失敗}} else {super.onRequestPermissionsResult(requestCode, permissions, grantResults);}
}復制代碼

RxPermissions 的簡單使用

其實 RxPermissions 的使用方式有兩種

  • 方式 1:
RxPermissions rxPermissions = new RxPermissions(MainActivity.this);rxPermissions.request(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE)//這里填寫所需要的權限.subscribe(new Consumer<Boolean>() {@Overridepublic void accept(Boolean aBoolean) throws Exception {if (aBoolean) {// 通過}else{// 拒絕}}});
復制代碼
  • 方式 2:結合 RxBinding 來使用
RxPermissions rxPermissions = new RxPermissions(MainActivity.this);
// Must be done during an initialization phase like onCreate
RxView.clicks(findViewById(R.id.enableCamera)).compose(rxPermissions.ensure(Manifest.permission.CAMERA)).subscribe(granted -> {// R.id.enableCamera has been clicked});復制代碼

源碼分析

整體介紹

接著我們來對這個 RxPermissions 進行一個源碼的解析,但是打開源碼的時候,我們可以發現,這個庫里面,其實就只有 3 個類:RxPermissions、RxPermissionsFragment、Permission

  • RxPermissions
    • 最主要的實現類,利用 rxjava,為我們提供了方便權限申請的類
  • RxPermissionsFragment
    • 是一個 fragment,主要的動態權限獲取類
  • Permission
    • 定義的權限的 model 類

源碼分析

RxPermissions 實例創建

對于源碼的分析,我們應該先從簡單的使用入手。下面我們可以先看看實例化 RxPermissionsFragment 的時候是做了什么?

    RxPermissionsFragment mRxPermissionsFragment;public RxPermissions(@NonNull Activity activity) {mRxPermissionsFragment = getRxPermissionsFragment(activity);}復制代碼

我們可以看到,上面的代碼中,實例化 RxPermissionsFragment 的時候,里面先創建了一個 RxPermissionsFragment 的實例。我們再接著看 getRxPermissionsFragment 這個方法的實現。

    private RxPermissionsFragment getRxPermissionsFragment(Activity activity) {//  查找 RxPermissionsFragment 是否已經被添加了RxPermissionsFragment rxPermissionsFragment = findRxPermissionsFragment(activity);boolean isNewInstance = rxPermissionsFragment == null;if (isNewInstance) {rxPermissionsFragment = new RxPermissionsFragment();FragmentManager fragmentManager = activity.getFragmentManager();fragmentManager.beginTransaction().add(rxPermissionsFragment, TAG).commitAllowingStateLoss();fragmentManager.executePendingTransactions();}return rxPermissionsFragment;}復制代碼

在 getRxPermissionsFragment() 這個方法中,首先是先查找當前是否已經添加了這個 rxPermissionsFragment 的實例,如果已經添加,那么直接返回已經添加的實例,如果沒有添加過的話,那么就重新再創建一個 RxPermissionsFragment 實例并提交;

    private RxPermissionsFragment findRxPermissionsFragment(Activity activity) {return (RxPermissionsFragment) activity.getFragmentManager().findFragmentByTag(TAG);}
復制代碼

到此,rxPermissionsFragment 的實例化已經完成,接著我們需要看看 request 這個方法中實現了什么。

request 方法

    public Observable<Boolean> request(final String... permissions) {return Observable.just(TRIGGER).compose(ensure(permissions));}
復制代碼
    static final Object TRIGGER = new Object();
復制代碼

從上面的代碼中,我們可以看到,request 方法中需要傳入的參數是一個 權限的數組,返回值是 Observable 對象。Observable.just(TRIGGER) 是快捷創建一個 Observable 的方式,由于 TRIGGER 是一個空的 Object 對象,所以 TRIGGER 就是一個占位符而已,Observable.just(TRIGGER) 創建的是一個 Observable,之后通過 compose 將 Observable 轉化為 Observable 并返回。在 compose 中需要的參數是一個 ObservableTransformer,那么我們接著看 ensure() 這個方法。

ensure(permissions);

public <T> ObservableTransformer<T, Boolean> ensure(final String... permissions) {// 創建一個Transformer對象返回return new ObservableTransformer<T, Boolean>() {@Overridepublic ObservableSource<Boolean> apply(Observable<T> o) {//request(o, permissions) 方法返回 Observable<Permission> 對象return request(o, permissions)// 將 Observable<Permission> 轉換為 Observable<Boolean>,在這里會等待所有的權限都返回了一次性發射數據。.buffer(permissions.length).flatMap(new Function<List<Permission>, ObservableSource<Boolean>>() {@Overridepublic ObservableSource<Boolean> apply(List<Permission> permissions) throws Exception {// 如果permissions為空那么直接返回Observable.empty();if (permissions.isEmpty()) {// Occurs during orientation change, when the subject receives onComplete.// In that case we don't want to propagate that empty list to the// subscriber, only the onComplete.return Observable.empty();}// Return true if all permissions are granted.for (Permission p : permissions) {if (!p.granted) {return Observable.just(false);}}return Observable.just(true);}});}};}復制代碼

在 ensure 的這個方法中,最終會返回的是 ObservableTransformer<T, Boolean> 對象。接著我們看看 ObservableTransformer 的匿名實現類里面的 apply 方法,這里實現的就是將 Observable 轉換為 Observable 的操作。我們對 apply 這個方法里面的代碼進行簡化一下。

return request(o,permissions).buffer(permissions.length).flatMap(new Function<List<Permission>, ObservableSource<Boolean>>{});
復制代碼
  • request() 方法返回 Observable 對象
  • buffer(len) 操作符將一個 Observable 變換為 Observable<List>,原來的 Observable 正常發射數據,變換產生的 Observable 發射這些數據的緩存集合。buffer 將數據緩存到一個集合當中,然后在適當(比如:所有請求的權限結果都返回了)的時機一起發送。
  • flatMap() 方法將 Observable<List> 轉化為 Observable

request(o, permissions);

    private Observable<Permission> request(final Observable<?> trigger, final String... permissions) {if (permissions == null || permissions.length == 0) {throw new IllegalArgumentException("RxPermissions.request/requestEach requires at least one input permission");}return oneOf(trigger, pending(permissions)).flatMap(new Function<Object, Observable<Permission>>() {@Overridepublic Observable<Permission> apply(Object o) throws Exception {return requestImplementation(permissions);}});}復制代碼

在 request 這個方法里面,其實 oneOf() 和 pending() 方法我們可以忽略的,主要的話,我們應該關注 requestImplementation(final String... permissions) 這個方法,在這個方法里面,主要實現了權限的請求。

requestImplementation

@TargetApi(Build.VERSION_CODES.M)private Observable<Permission> requestImplementation(final String... permissions) {List<Observable<Permission>> list = new ArrayList<>(permissions.length);List<String> unrequestedPermissions = new ArrayList<>();// In case of multiple permissions, we create an Observable for each of them.// At the end, the observables are combined to have a unique response.for (String permission : permissions) {mRxPermissionsFragment.log("Requesting permission " + permission);if (isGranted(permission)) {// Already granted, or not Android M// Return a granted Permission object.// 權限已經被同意或者不是 Android 6.0 以上版本,創建一個 同意的 Permission 對象。list.add(Observable.just(new Permission(permission, true, false)));continue;}if (isRevoked(permission)) {// 權限被拒絕,返回一個 拒絕的 Permission 對象。list.add(Observable.just(new Permission(permission, false, false)));continue;}PublishSubject<Permission> subject = mRxPermissionsFragment.getSubjectByPermission(permission);// 如果 subject 不存在,那么創建一個 subject。if (subject == null) {unrequestedPermissions.add(permission);subject = PublishSubject.create();mRxPermissionsFragment.setSubjectForPermission(permission, subject);}list.add(subject);}// 還未提起申請的權限進行申請if (!unrequestedPermissions.isEmpty()) {String[] unrequestedPermissionsArray = unrequestedPermissions.toArray(new String[unrequestedPermissions.size()]);requestPermissionsFromFragment(unrequestedPermissionsArray);}// 嚴格按照順序發射數據return Observable.concat(Observable.fromIterable(list));}復制代碼

onRequestPermissionsResult()

@TargetApi(Build.VERSION_CODES.M)public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode != PERMISSIONS_REQUEST_CODE) return;boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];for (int i = 0; i < permissions.length; i++) {shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]);}onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);}void onRequestPermissionsResult(String permissions[], int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {for (int i = 0, size = permissions.length; i < size; i++) {log("onRequestPermissionsResult  " + permissions[i]);// Find the corresponding subjectPublishSubject<Permission> subject = mSubjects.get(permissions[i]);if (subject == null) {// No subject foundLog.e(RxPermissions.TAG, "RxPermissions.onRequestPermissionsResult invoked but didn't find the corresponding permission request.");return;}// 發射權限申請結果mSubjects.remove(permissions[i]);boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;subject.onNext(new Permission(permissions[i], granted, shouldShowRequestPermissionRationale[i]));subject.onComplete();}}復制代碼

RxJava 操作符

Observable.just()

just 操作符是將一個對象轉化為 Observable 的操作符。這個對象可以是一個數字、字符串或者是數組對象等,是 RxJava 中快速創建一個 Observable 對象的操作符。如果有 subscriber 訂閱的話,那么會依次調用 onNext() 和 OnComplete() 方法。所以這里只是創建了一個 Observable 對象,方便后續的調用。

compose(Transformer)操作符

compose 操作符是對 Observable 對象的整體轉化。例如:通過 Transformer,我們可以將 Observable 對象轉換成 Observable 對象了。

    public static ObservableTransformer<String,Boolean> getTransformer(){return new ObservableTransformer<String, Boolean>() {@Overridepublic ObservableSource<Boolean> apply(Observable<String> upstream) {return upstream.flatMap(new Function<String, ObservableSource<Boolean>>() {@Overridepublic ObservableSource<Boolean> apply(String s) throws Exception {return Observable.just(true);}});}};}
復制代碼
/*** 線程切換* @return*/public static <T> ObservableTransformer<T,T> getScheduler(){return new ObservableTransformer<T, T>() {@Overridepublic ObservableSource<T> apply(Observable<T> upstream) {return upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());}};}復制代碼

buffer 操作符

buffer 操作符將一個 Observable 變換為另一個,原來的 Observable 正常發射數據,變換產生的 Observable 發射這些數據的緩存集合。buffer將數據緩存到一個集合當中,然后在適當的時機一起發送。 buffer(count) 以列表(List)的形式發射非重疊的緩存,每一個緩存至多包含來自原始Observable的count項數據(最后發射的列表數據可能少于count項)

  • 例如:緩存 2 個數據之后,再發送數據(調用 buffer(count) 函數)
                Observable.just(1,2,3,4,5,6).buffer(2).subscribe(integers -> {Log.i(TAG, "accept size: "+integers.size());for (Integer integer : integers) {Log.i(TAG, "accept: "+integer);}});
復制代碼
  • 輸出結果
2018-12-14 11:16:28.452 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept size: 2
2018-12-14 11:16:28.452 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept: 1
2018-12-14 11:16:28.453 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept: 2
2018-12-14 11:16:28.453 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept size: 2
2018-12-14 11:16:28.453 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept: 3
2018-12-14 11:16:28.453 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept: 4
2018-12-14 11:16:28.453 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept size: 2
2018-12-14 11:16:28.453 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept: 5
2018-12-14 11:16:28.453 28126-28126/com.luwei.lwbaselib I/LwBaseActivity: accept: 6
復制代碼
  • 例如:緩存 3 個數據,再發送數據,每次移動 1 步
                Observable.just(1,2,3,4).buffer(3,1).subscribe(integers -> {Log.i(TAG, "accept size: "+integers.size());for (Integer integer : integers) {Log.i(TAG, "accept: "+integer);}});
復制代碼
  • 輸出結果
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept size: 3
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 1
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 2
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 3
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept size: 3
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 2
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 3
2018-12-14 11:24:31.455 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 4
2018-12-14 11:24:31.456 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept size: 2
2018-12-14 11:24:31.456 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 3
2018-12-14 11:24:31.456 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 4
2018-12-14 11:24:31.456 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept size: 1
2018-12-14 11:24:31.456 29164-29164/com.luwei.lwbaselib I/LwBaseActivity: accept: 4
復制代碼

concat 操作符

是接收若干個Observables,發射數據是有序的,不會交叉。

Subject

  • 作為 Observable 和 Observer 之間的橋梁
  • 可以當做 Observable
  • 可以當做 Observer

PublishSubject

繼承至 Subject,它的 Observer 只會接收到 PublishSubject 被訂閱之后發送的數據。示例代碼如下;我們只會接收到 publishSubject3 和 publishSubject4;

                PublishSubject<String> publishSubject = PublishSubject.create();publishSubject.onNext("publishSubject1");publishSubject.onNext("publishSubject2");publishSubject.subscribe(new Consumer<String>() {@Overridepublic void accept(String s) throws Exception {Log.i(TAG, "accept: "+s);}});publishSubject.onNext("publishSubject3");publishSubject.onNext("publishSubject4");復制代碼
  • 執行結果
2018-12-14 11:33:18.168 29916-29916/com.luwei.lwbaselib I/LwBaseActivity: accept: publishSubject3
2018-12-14 11:33:18.168 29916-29916/com.luwei.lwbaselib I/LwBaseActivity: accept: publishSubject4
復制代碼

舉一反三

可以看到,在 RxPermissions 這個獲取權限的開源框架中,往 Activity 中添加了一個空的 Fragment,這個 fragment 才是用來發起申請權限和處理權限的請求,最后再將結果返回,這樣子就避免了我們發送請求之后,還需要在 onRequestPermissionsResult 中進行處理,并判斷 requestCode 等繁瑣操作。想到這里,我們平時使用 startActivityForResult 時,我們也可以同樣采用這樣的思路來簡化我們的請求。

同樣的,我們采用添加空白的 fragment,來做 startActivityForResult 請求,主要的實現類有 SimpleForResult 和 SimpleOnResultFragment,ActivityResultInfo 是請求 model,接下我們先看代碼。

SimpleForResult


/*** @Author: chenjianrun* @Time: 2018/12/7* @Description:   避免調用 startActivity 時,需要 onActivityResult 處理的類*/
public class SimpleForResult {private static final String TAG = "SimpleForResult";private SimpleOnResultFragment mSimpleOnResultFragment;public SimpleForResult(AppCompatActivity activity) {mSimpleOnResultFragment = getOnResultFragment(activity.getSupportFragmentManager());}public SimpleForResult(Fragment fragment){mSimpleOnResultFragment = getOnResultFragment(fragment.getChildFragmentManager());}private SimpleOnResultFragment getOnResultFragment(FragmentManager fragmentManager) {SimpleOnResultFragment simpleOnResultFragment = findSimpleOnResultFragment(fragmentManager);if (simpleOnResultFragment == null) {simpleOnResultFragment = new SimpleOnResultFragment();fragmentManager.beginTransaction().add(simpleOnResultFragment, TAG).commitAllowingStateLoss();fragmentManager.executePendingTransactions();}return simpleOnResultFragment;}private SimpleOnResultFragment findSimpleOnResultFragment(FragmentManager fragmentManager) {return (SimpleOnResultFragment) fragmentManager.findFragmentByTag(TAG);}public Observable<ActivityResultInfo> startForResult(Intent intent) {return mSimpleOnResultFragment.startForResult(intent);}public Observable<ActivityResultInfo> startForResult(Class<?> clazz) {Intent intent = new Intent(mSimpleOnResultFragment.getActivity(), clazz);return startForResult(intent);}public void startForResult(Intent intent, Callback callback) {mSimpleOnResultFragment.startForResult(intent, callback);}public void startForResult(Class<?> clazz, Callback callback) {Intent intent = new Intent(mSimpleOnResultFragment.getActivity(), clazz);startForResult(intent, callback);}public interface Callback {void onActivityResult(int requestCode, int resultCode, Intent data);}
}復制代碼

SimpleOnResultFragment


/*** @Author: chenjianrun* @Time: 2018/12/7* @Description:    真正調用 startActivity 和處理 onActivityResult 的類。*/
public class SimpleOnResultFragment extends Fragment {private static Map<Integer, PublishSubject<ActivityResultInfo>> mSubjects = new HashMap<>();private static Map<Integer, SimpleForResult.Callback> mCallbacks = new HashMap<>();public SimpleOnResultFragment() {}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setRetainInstance(true);}public Observable<ActivityResultInfo> startForResult(final Intent intent) {int requestCode = generateRequestCode();PublishSubject<ActivityResultInfo> subject = PublishSubject.create();mSubjects.put(requestCode, subject);startActivityForResult(intent, requestCode);return subject;}public void startForResult(Intent intent, SimpleForResult.Callback callback) {int requestCode = generateRequestCode();mCallbacks.put(requestCode, callback);startActivityForResult(intent, requestCode);}@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);//rxjava方式的處理PublishSubject<ActivityResultInfo> subject = mSubjects.remove(requestCode);if (subject != null) {subject.onNext(new ActivityResultInfo(requestCode, resultCode, data));subject.onComplete();}//callback方式的處理SimpleForResult.Callback callback = mCallbacks.remove(requestCode);if (callback != null) {callback.onActivityResult(requestCode, resultCode, data);}}private int generateRequestCode(){Random random = new Random();for (;;){int code = random.nextInt(65536);if (!mSubjects.containsKey(code) && !mCallbacks.containsKey(code)){return code;}}}
}
復制代碼

ActivityResultInfo

package com.luwei.util.forresult;import android.content.Intent;/*** @Author: chenjianrun* @Time: 2018/12/7* @Description:*/
public class ActivityResultInfo {private int requestCode;private int resultCode;private Intent data;public ActivityResultInfo(int requestCode, int resultCode, Intent data) {this.requestCode = requestCode;this.resultCode = resultCode;this.data = data;}public int getRequestCode() {return requestCode;}public void setRequestCode(int requestCode) {this.requestCode = requestCode;}public ActivityResultInfo(int resultCode, Intent data) {this.resultCode = resultCode;this.data = data;}public int getResultCode() {return resultCode;}public void setResultCode(int resultCode) {this.resultCode = resultCode;}public Intent getData() {return data;}public void setData(Intent data) {this.data = data;}
}復制代碼

簡單使用示例

  • 簡單的 Activity 調用
// 簡化調用 startActivityForResult 及避免在 onActivityResult 中處理繁瑣的結果SimpleForResult simpleForResult = new SimpleForResult(this);simpleForResult.startForResult(ToastActivity.class).subscribe((resultInfo) -> {if (resultInfo.getData() != null) {ToastUtils.showLong(resultInfo.getData().getStringExtra("result"));}});
復制代碼
  • 調用攝像頭
    /*** 打開攝像頭*/private void openCamera() {try {mTmpFile = FileUtils.createTmpFile(this);} catch (IOException e) {e.printStackTrace();}simpleForResult.startForResult(getOpenCameraIntent(this, mTmpFile)).subscribe((resultInfo -> {if (resultInfo.getResultCode() == RESULT_OK) {mHeadUrl = mTmpFile.getAbsolutePath();ImageLoaderUtils.loadCircleImage(this, ivHeader, mHeadUrl);// 裁剪(如果沒有要求可裁剪,也可以不要)startPictureZoom(mTmpFile);}}));}/*** 獲取打開照相機的 intent,適配 Android 7.0* @param activity* @param mTmpFile* @return*/public static Intent getOpenCameraIntent(Activity activity,File mTmpFile){Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);if (intent.resolveActivity(activity.getPackageManager()) != null) {if (mTmpFile != null && mTmpFile.exists()) {if (Build.VERSION.SDK_INT >= 24) {// 適配 Android 7.0intent.putExtra(MediaStore.EXTRA_OUTPUT,FileProvider.getUriForFile(activity, activity.getPackageName()+".provider",mTmpFile));} else {intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mTmpFile));}} else {Toast.makeText(activity, me.nereo.image_selector.R.string.error_image_not_exist, Toast.LENGTH_SHORT).show();}} else {Toast.makeText(activity, me.nereo.image_selector.R.string.msg_no_camera, Toast.LENGTH_SHORT).show();}return intent;}復制代碼

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/449962.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/449962.shtml
英文地址,請注明出處:http://en.pswp.cn/news/449962.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

總結Selenium WebDriver中一些鼠標和鍵盤事件的使用

在使用 Selenium WebDriver 做自動化測試的時候&#xff0c;會經常模擬鼠標和鍵盤的一些行為。比如使用鼠標單擊、雙擊、右擊、拖拽等動作&#xff1b;或者鍵盤輸入、快捷鍵使用、組合鍵使用等模擬鍵盤的操作。在 WebDeriver 中&#xff0c;有一個專門的類來負責實現這些測試場…

最快浮點數取絕對值

做視頻算法10多年&#xff0c;經常要算絕對值&#xff0c;整數的絕對值有快速算法&#xff0c;但浮點數的絕對值沒看到有快速算法&#xff0c;經常不段發現&#xff0c;得到如下浮點數的快速算法&#xff1a; 快6倍多&#xff0c; #include <Windows.h> #include <ios…

Linux ln命令、軟鏈接和硬鏈接的區別

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Linux ln命令是一個非常重要命令&#xff0c;它的功能是為某一個文件在另外一個位置建立一個同步的鏈接。 當我們需要在不同的目錄&…

Android應用開發——文件目錄

Android 存儲位置及 API 一、內部存儲 應用安裝后都會在Android 根目錄生成 /data/data/packagename&#xff0c;當前應用讀取不需要讀寫權限 注意&#xff1a; 有些開發者可能看到過應用的根目錄為 /data/user/0/packagename 的情況&#xff0c;這里解釋一下&#xff0c;And…

git常用命令及沖突解決

2019獨角獸企業重金招聘Python工程師標準>>> git常用命令 git config --global user.name chenhongjiang git config --global user.email 123qq.com git init 建立廠庫 git status 查看狀態 git add . 添加當前目錄 git add a.php 添加文件…

C 語言常見問題集

從 http://c-faq-chn.sourceforge.net/ccfaq/index.html 轉載過來&#xff0c;學習C的好助手。 目錄1. 前言2. 聲明和初始化 2.1 我如何決定使用那種整數類型&#xff1f;2.2 64 位機上的 64 位類型是什么樣的&#xff1f;2.3 怎樣定義和聲明全局變量和函數最好&#xff1f;2.4…

【題解】quake

【題解】\(quake\) 題目大意 我們共有報酬\(f\)元&#xff0c;一條邊有它的價值\(w_i\),有它的建造時間\(t_i\)。要求建一些邊&#xff0c;生成一顆樹。求最大的利潤率。 數據范圍 \(n\le 400\) \(m\le10000\) \(Solution\) 實際上\(n,m\)出到\(\le 100000\)應該也是沒問題的。…

Android應用開發——service連接泄露異常:android.app.ServiceConnectionLeaked: that was originally bound here

在做service開發過程中&#xff0c;大部分可能會遇到以下異常&#xff0c;該異常僅通過log輸出&#xff0c;并不會導致app crash。 E/ActivityThread: Activity com.example.image.all_samples.Main2Activity has leaked ServiceConnection com.example.image.all_samples.Mai…

Linux more命令、Linux rhmask命令

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Linux more 命令類似 cat &#xff0c;不過會以一頁一頁的形式顯示&#xff0c;更方便使用者逐頁閱讀&#xff0c;而最基本的指令就是按…

從零開始學習PYTHON3講義(二)把Python當做計算器

《從零開始PYTHON3》第二講 上一講我們說過了如何啟動Python IDLE集成開發學習環境&#xff0c;macOS/Linux都可以在命令行執行idle3。Windows則從開始菜單中去尋找IDLE程序的圖標。 上一講我們還見到了Python的兩種工作模式&#xff0c;交互模式和程序模式。 通常在一個大型的…

Tranquility

本頁目錄與Kafka集群交互Druid使用Tranquility Kafka本文以Kafka為例&#xff0c;介紹在E-MapReduce中如何使用Tranquility從Kafka集群采集數據&#xff0c;并實時推送至Druid集群。 Tranquility是一個以push方式向Druid實時發送數據的應用。它替用戶解決了分區、多副本、服務發…

Iot相關雜燴

人工智能就像人的大腦&#xff0c;而 IoT 就像人的神經網絡 1&#xff09;在天空中巨大的鳥群里&#xff0c;每一只鳥兒都實時判斷自己和四周同伴的距離。這時&#xff0c;它們各自都是一個物聯網節點。2&#xff09;這些“節點”并不是簡單地收集數據&#xff0c;而是在實時計…

水滴石穿C語言之指針、數組和函數

基本解釋   1、指針的本質是一個與地址相關的復合類型&#xff0c;它的值是數據存放的位置&#xff08;地址&#xff09;&#xff1b;數組的本質則是一系列的變量。   2、數組名對應著&#xff08;而不是指向&#xff09;一塊內存&#xff0c;其地址與容量在生命期內保持…

告訴你銀行在年底為存儲做的小動作

25年前&#xff0c;銀行的存款利率是10.98%&#xff0c;可謂巔峰時刻。15年前&#xff0c;銀行的存款利率開始下降&#xff0c;降到了8%的利率。 到了5年前&#xff0c;銀行的存款利率毫無回轉之勢&#xff0c;直線下降到了5%的利率。 而如今&#xff0c;我們無可奈何地接受了2…

爬蟲學習(五)——百度貼吧的爬取

import osimport timeimport urllib.requestimport urllib.parse# 輸入目標頁碼和吧名def header(): url "https://tieba.baidu.com/f?" baming input("請輸入要爬取的吧名") start_page int(input("請輸入起始頁")) end_page …

什么是嵌入式設備?/ 嵌入式設備的定義

什么是嵌入式設備&#xff1f;/ 嵌入式設備的定義 區別于通用計算機的其他設備都可以稱之為嵌入式設備 &#xff08;個人電腦&#xff0c;服務器&#xff09; 一段時期內&#xff0c;必備的硬件配置。 嵌入式開發包括哪些部分&#xff1a; 底層驅動開發&#xff1a; 關鍵字…

Linux mv命令、Linux cp命令、Linux scp命令

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Linux mv命令用來為文件或目錄改名、或將文件或目錄移入其它位置。 語法 mv [options] source dest mv [options] source... director…

創業者談:畏懼失敗,但也要擁抱失敗

摘要&#xff1a;本文作者為Paydirt創始人Tristan Gamilis&#xff0c;他在文中分享了如何面對創業過程中的失敗。作為一個創業者&#xff0c;開始的時候并非全才&#xff0c;很多知識都是經歷了創業中的失敗&#xff0c;摸爬滾打之后才學會的。所以&#xff0c;我們在創業過程…

基于STM32F4移植W5500官方驅動庫ioLibrary_Driver(轉)

源&#xff1a; 基于STM32F4移植W5500官方驅動庫ioLibrary_Driver 參考&#xff1a; 基于STM32W5500 的Ethernet和Internet移植 Upgrade W5500 Throughput on Nucleo STM32F401RE Using SPI DMA

redis 資料

redis是什么: Redis is an open source, BSD licensed, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets. redis是開源,BSD許可,高級的key-value存儲系統. 可以用來存儲字…