1. 程式人生 > >Android API相容,其他API,UI適配(3)

Android API相容,其他API,UI適配(3)

-- 在application的oncreate方法前
StrictMode.setThreadPolicy(new StrictModel.ThreadPolicy.Builder().detectAll().penaltyLog().build());
 執行緒檢測策略; 虛擬機器檢測策略

-- ANR典型的分析情況
 1.如果TOTAL的和接近100,有可能是因為當前使用的app佔用的cpu太高,導致系統將你的殺死。
 2.如果TOTAL很小,則說明執行緒被阻塞了,主執行緒在等待下條訊息的進入,任務在等待時anr。
 3.如果ioWait很高,則說明是io操作導致的

ANR一般有三種類型:
 1:KeyDispatchTimeout(5 seconds) --主要型別
按鍵或觸控事件在特定時間內無法得到響應
 2:BroadcastTimeout(10 seconds)
BroadcastReceiver在的onRecieve執行在主執行緒中,短時間內無法處理完成導致
 3:ServiceTimeout(20 seconds) --小概率型別
Service的各個宣告週期在特定時間內無法處理完成

1.ANR關鍵點:ioWait很高,ContentResolver in AsyncTask onPostExecute
 首先看到total中ioWait很高,說明是io操作導致的;
 具體原因,可以看到關鍵詞sqlite,ContentResolver
2.ANR關鍵詞OSNetworkSystem.receiveStream,net
3.ANR關鍵詞:VMWAIT,VMRuntime.trackExternalAllocation

-- Service導致的ANR
Service binderService與ANR?
 Service 裡面開啟一個執行緒處理網路資料,不要用ipc bindService的方式,用廣播通知資料更新。
 問題出在onStartCommand方法中.
 Service也是執行在主執行緒的,你在裡邊做耗時操作肯定會anr的   你應該在service 裡邊在開一個子執行緒去做耗時的操作呀.
service anr關鍵方法.
Android O StartService的 anr timeout 流程分析- https://blog.csdn.net/sinat_20059415/article/details/80997425

 binderService需要context上下文;跨程序時,只能用StartService(),因為上下文物件對其無用?

寫native的binder service- https://github.com/cloudchou/NativeBinderTest
行內函數模版。
 startForegroundService是同步的(阻塞),startService是非同步的(命令一次性下發,不阻塞)。

-- AppOpsManager.checkOpNoThrow()

判斷應用在前後臺的方法-https://github.com/wenmingvs/AndroidProcess
AppLock應用鎖,保護你的隱私- https://github.com/lizixian18/AppLock

使用UsageStatsManager需要獲取許可權相關程式碼:
    /**
     * 判斷是否已經獲取 有權檢視使用情況的應用程式 許可權
     *
     * @param context
     * @return
     */
    public static boolean isStatAccessPermissionSet(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                PackageManager packageManager = context.getPackageManager();
                ApplicationInfo info = packageManager.getApplicationInfo(context.getPackageName(), 0);
                AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
                appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, info.uid, info.packageName);
                return appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, info.uid, info.packageName) == AppOpsManager.MODE_ALLOWED;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        } else {
            return false;
        }
    }
 
    /**
     * 檢視是存在檢視使用情況的應用程式介面
     *
     * @return
     */
    public static boolean isNoOption(Context context) {
        PackageManager packageManager = context.getPackageManager();
        Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
        List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        return list.size() > 0;
    }
 
    /**
     * 轉跳到 有權檢視使用情況的應用程式 介面
     *
     * @param context
     */
    public static void startActionUsageAccessSettings(Context context) {
        Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
        context.startActivity(intent);
    }

獲取棧頂包名的方法有好幾個,根據不同的android版本方法也不一樣,在android5.0以上,推薦使用UsageStatsManager來獲取,具體方法:
public String getLauncherTopApp(Context context, ActivityManager activityManager) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            List<ActivityManager.RunningTaskInfo> appTasks = activityManager.getRunningTasks(1);
            if (null != appTasks && !appTasks.isEmpty()) {
                return appTasks.get(0).topActivity.getPackageName();
            }
        } else {
            //5.0以後需要用這方法
            UsageStatsManager sUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
            long endTime = System.currentTimeMillis();
            long beginTime = endTime - 10000;
            String result = "";
            UsageEvents.Event event = new UsageEvents.Event();
            UsageEvents usageEvents = sUsageStatsManager.queryEvents(beginTime, endTime);
            while (usageEvents.hasNextEvent()) {
                usageEvents.getNextEvent(event);
                if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
                    result = event.getPackageName();
                }
            }
            if (!android.text.TextUtils.isEmpty(result)) {
                return result;
            }
        }
        return "";
    }

-- Android 5.0 應用使用情況統計資訊
要使用android.app.usage API ,首先必須要在AndroidManifest.xml中宣告許可權,如下:
<uses-permission Android:name="android.permission.PACKAGE_USAGE_STATS" />
然後需要開啟允許檢視使用情況的應用介面,引導使用者授權,如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { try { startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)); } catch (Exception e) { e.printStackTrace(); }}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean checkUsagePermission(Context context) { 
  AppOpsManager appOpsManager = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); 
 int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(),ontext.getPackageName()); 
 return mode == AppOpsManager.MODE_ALLOWED;
}

-- Android許可權管理與AppOpsManager,在SDK 19中Google引入了AppOpsManager
 值得一提的是這個api是在19新加入的,所以要注意加個判斷,其實 Android 官方一直有這個設定許可權的入口,Setting---Security---AppOps 只是一直被google隱藏了。
 Android許可權管理與AppOpsManager- https://blog.csdn.net/u012526436/article/details/72819034
知許可權管理的功能AppOpsManager,資訊的儲存在“data/system/appops.xml”檔案中
if (Build.VERSION.SDK_INT >= 19){}

  而AppOps所管理的是所有可能涉及使用者隱私和安全的操作,包括 access notification, keep weak lock,  activate vpn, display toast 等等,有些操作是不需要Manifest裡申請許可權的。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //KITKAT 19
    appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
    int checkResult = appOpsManager.checkOpNoThrow(
            AppOpsManager.OPSTR_FINE_LOCATION, Binder.getCallingUid(), context.getPackageName());
    if(checkResult == AppOpsManager.MODE_ALLOWED){
        Toast.makeText(context,"有許可權",Toast.LENGTH_LONG).show();
        Log.e("jijiaxin","有許可權");
    }else if(checkResult == AppOpsManager.MODE_IGNORED){
        // TODO: 只需要依此方法判斷退出就可以了,這時是沒有許可權的。
        Toast.makeText(context,"被禁止了",Toast.LENGTH_LONG).show();
        Log.e("jijiaxin","被禁止了");
    }else if(checkResult == AppOpsManager.MODE_ERRORED){
        Toast.makeText(context,"出錯了",Toast.LENGTH_LONG).show();
        Log.e("jijiaxin","出錯了");
    }else if(checkResult == 4){
        Toast.makeText(context,"許可權需要詢問",Toast.LENGTH_LONG).show();
        Log.e("jijiaxin","許可權需要詢問");
    }
}

public boolean selfPermissionGranted(Context context, String permission) {
    boolean ret = true;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (targetSdkVersion >= Build.VERSION_CODES.M) {
            ret = context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
        } else {
          ret = PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
        }
    }
    return ret;
}

在Android5.0,即Lollipop(api level 21)之前,大家都幸福的使用如下程式碼來獲得當前執行的app,即所謂的top Activity:
ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
ComponentName cn = activityManager.getRunningTasks(1).get(0).topActivity;

Marshmallow(api level 23),M = 23;
//檢測使用者是否對本app開啟了“Apps with usage access”許可權
    private boolean hasPermission() {
        AppOpsManager appOps = (AppOpsManager)
                getSystemService(Context.APP_OPS_SERVICE);
        int mode = 0;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
            mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
                    android.os.Process.myUid(), getPackageName());
        }
        return mode == AppOpsManager.MODE_ALLOWED;
    }

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                final AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
                int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
                        android.os.Process.myUid(), context.getPackageName());
                if (mode == AppOpsManager.MODE_ALLOWED) {
                    return true;
                } else {
                    if (viewWrapper != null) {
                        openPermissonActivity(viewWrapper);
                    }
                }
            }

獲取棧頂Activity-https://github.com/apkkids/GetTopActivity 

  在實際開發中,經常需要在程式中開啟一些物理資源,如資料庫連線,網路連線,磁碟檔案等,開啟這些資源之後必須顯示關閉,否則將會引起資源洩露。 JVM不是提供了垃圾回收機制嗎?JVM的垃圾回收機制不會回收這些資源嗎?答案是不會,垃圾回收機制屬於Java記憶體管理的一部分,它只是負責回收堆記憶體中分配出來的記憶體,至於程式中開啟的物理資源,垃圾回收機制是無能為力的。
 IO流關閉順序。
一般情況下是:先開啟的後關閉,後開啟的先關閉
另一種情況:看依賴關係,如果流a依賴流b,應該先關閉流a,再關閉流b,例如處理流a依賴節點流b,應該先關閉處理流a,再關閉節點流b
  其實Java方法的返回值,跟引數的傳遞一樣,都是基本型別返回值,而非基本型別,則返回引用.

GradientDrawable.setColor

private void setGradientDrawableColor(int[] colors) {
        GradientDrawable drawable = null;
        if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            drawable = new GradientDrawable();
            //倆設定方法其實就是對應著帶參構造引數的那倆引數
            drawable.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM);
            drawable.setColors(colors);
        }else{
            drawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors);
            setBackgroundDrawable(drawable);
        }
    }

GradientDrawable gd = (GradientDrawable) getBackground();
int[] colors = {0xFFFF0000, 0xFFCC0099};
if (android.os.Build.VERSION.SDK_INT >= 16) {
    gd = gd.mutate(); // For safe resource handling
    gd.setColors(colors);
} else {
    // Fallback for APIs under 16.
    GradientDrawable ngd = new GradientDrawable(/* Orientation variable */, colors);
    // You may have to set other qualities of `ngd` here to make it match.
    setBackgroundDrawable(ngd);
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            activity.getWindowManager().getDefaultDisplay().getRealMetrics(metric);
            LogUtil.d(TAG, "####### dumpScreenInfo RealMetrics (width, height) = ( "
                    + metric.widthPixels + " , " + metric.heightPixels + " )");
}

-- synchronized(Lock)與Lock.notify()
  在併發量比較小的情況下,使用synchronized是個不錯的選擇,但是在併發量比較高的情況下,其效能下降很嚴重,此時ReentrantLock是個不錯的方案。
  需要注意的是,wait()和notify()必須在synchronized程式碼塊中呼叫。 

-- 
Android4.0之後開始支援WifiDirect技術,即Wifi直連,做為一種通訊方式,它的優勢在於傳輸速度快傳輸距離遠。 

if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
                    setBackgroundDrawable( null );
                }
                else {
                    setBackground( null );
                }

int space = 0;
        int columWidth = 0;
        int newSpace = 0;
        int newColumWidth = 0;
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                space = gridView.getVerticalSpacing(); // 統計所有子項的總高度
            } else {
                space = 0;
            }

            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                columWidth = gridView.getColumnWidth();
            } else{
                Field field = GridView.class.getDeclaredField("mColumnWidth");
                field.setAccessible(true);
                Integer value = (Integer) field.get(this);
                field.setAccessible(false);
                columWidth = value.intValue();
            }
        } catch (Exception ex) {
            newSpace = space;
            newColumWidth = columWidth;
        }

-- Android SDK 升級到 23 之後,getDrawable和getColor方法提示過時。
getResources().getColor 替換成 ContextCompat.getColor
getResources().getDrawable 替換成 ContextCompat.getDrawable

例子如下:
int colorInt = getResources().getColor(R.color.colorAccent);//返回的是color的int型別值:-49023
int colorInt2 = ContextCompat.getColor(this, R.color.colorAccent);//返回的是color的int型別值:-49023

Drawable drawable = getResources().getDrawable(R.mipmap.ic_launcher);
Drawable drawable2 = ContextCompat.getDrawable(this,R.mipmap.ic_launcher);

-- BigDecimal的建構函式 public BigDecimal(double val) 損失了double 引數的精度.使用BigDecimal的以String為引數的建構函式:public BigDecimal(String val)  來替代。

-- Handler延遲
if (isPausedBy50() && mRecorderHandler != null) {
            mRecorderHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    
                }
            }, 300);
        }