天天看點

Android适配全面總結(三)----ROM适配

版權聲明:本文為部落客原創文章(部分引用他人博文,已加上引用說明),未經部落客允許不得轉載。 https://www.jianshu.com/p/f9c67a4b908e 轉載請标明出處: 本文出自 AWeiLoveAndroid的部落格 第一篇文章講了 Android适配全面總結(一)----螢幕适配 上一篇文章講了 Android适配全面總結(二)----版本适配

這一篇文章講一下 ROM适配。

Android是開源的,不同的手機廠商都有自己定制的系統,是以這就給開發者帶來了ROM适配難題。在一些群裡面經常看到有人因為手機适配問題,說這個手機坑,那個手機坑,其實那是沒有對ROM定制系統的一些變更了解,導緻了盲目的說出這些指責的話。如果你熟悉了,也就會少走很多彎路。下面這篇文章就來講一下幾個主流手機的ROM适配問題。

一、手機平台相關文檔

(一)小米

1、

小米開發者文檔

2、

開發人員必看:《小米應用開發者文檔》

在這裡可以找到在小米手機上開發、分發應用的相關文檔~

3、

常見問題

4、

小米帳号場景化登入

5、

技術文檔

(二)華為

華為開發者文檔
2、華為部分裝置不列印Log

部分的華為裝置工程模式下log是關閉的,華為部分裝置不列印Log的解決方案:

1.如果是華為手機,進入撥号界面輸入:*#*#2846579#*#*進入頁面設定。
2.如果是華為pad,進入電腦輸入:()()2846579()()= 進入頁面設定。
           
3、華為手機擷取拍照權限後拍照,傳回值為空

問題起源:

開發中遇到了需要拍照和從圖庫中選擇圖檔展示并上傳的功能,其他手機測試沒問題,華為手機擷取拍照權限後拍照,傳回值為空。

問題分析:

原來是華為在7.0以後的系統中,對于拍照後傳回的圖檔也做了權限處理。是以說,華為7.0在拍照的時候,不僅要拿到拍照

CAMERA

的權限,還要拿到讀寫檔案的權限

WRITE_EXTERNAL_STORAGE

解決方法部分代碼如下:

//聲明兩個常量
public static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 0x0001;
public static final int MY_PERMISSIONS_REQUEST_CALL_PHONE2 = 0x0002;

//設定權限
public void setTakePhonePermissions(Context context) {  
    if (Build.VERSION.SDK_INT >= 23) {  
        int checkCallPhonePermission = ContextCompat.checkSelfPermission(context,  
                Manifest.permission.CAMERA);  
        if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {  
            ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.CAMERA,}  
                    , MY_PERMISSIONS_REQUEST_CALL_PHONE);  
        } else if (ContextCompat.checkSelfPermission(context,  
                Manifest.permission.WRITE_EXTERNAL_STORAGE)  
                != PackageManager.PERMISSION_GRANTED) {  
            ActivityCompat.requestPermissions(context,  
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA},  
                    MY_PERMISSIONS_REQUEST_CALL_PHONE2);  
        } else {  
            takePhoto();  
        }  
    //小于23的不需要動态權限
    } else {  
        takePhoto();  
    }  
}  
           
4、華為Android7.0手機打開攝像頭拍照閃退問題。
5、華為手機Android8.0 使用代碼安裝APK閃退問題

更新版本APK自動安裝的時候,在安卓6.0、7.0下都OK,唯獨在華為安卓8.0手機閃退。

解決方案: 隻要在Mainfest.xml 中加入請求安裝權限就OK了

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />  
           
  • 關于閃退的小結:
    1. 在Android 7.0(及以上)手機開啟拍照功能,既要申請

      CAMERA

      權限,還要申請

      WRITE_EXTERNAL_STORAGE

      權限。
    1. Android 7.0(API24)以及以上版本不支援file://這種類型的URI,而是使用content://這種類型的URI。不然會報

      android.os.FileUriExposedException

      這個錯,使用Android 7.0(及以上)手機拍照功能時,一定要注意這個api的變化。
    1. 使用Android 8.0(及以上)手機更新安裝apk時,在Mainfest.xml 中請求安裝權限

      android.permission.REQUEST_INSTALL_PACKAGES

6、華為手機app閃退重新開機清空log日志問題

解決方案:

◆ 方式1(最全面的解決方案):

找到手機設定 ---> 最後的開發人員選項 ---> 在調試子產品,打開USB調試。 
還是調試子產品内,找到日志記錄器緩沖區大小,改為1M(也可選擇更大),
然後進入撥号界面輸入:*#*#2846579#*#*  ----> 選擇USB端口設定 ----> 選擇Google模式。
           

◆ 方式2:撥号鍵盤 + 快捷鍵設定(這種方式不是所有log都能顯示)

進入撥号界面輸入:*#*#2846579#*#*
依次選擇:背景設定 ---> LOG設定 ---> AP 日志,重新啟動手機。
           

◆ 方式3:錯誤出現後,迅速拔掉USB線,這是一個拼手速的方法,成功率不敢保證。

7、關于華為手機App權限更改導緻應用重新開機的坑(暫且我還沒有很好的解決方式)

問題重制:

  • 1.當我們在華為手機上打開一個應用,将應用退至背景程序中。
  • 2.打開 “設定”去更改該應用的權限(比如将“存儲”權限由授權狀态改為非授權狀态)。
  • 3.再将該應用重新切換到前台,會發現應用進行了重新啟動。 (在該app中,啟動的時候,FragmentManager仍然會持有原有的fragment。)

網上有人說出了一種原因和一種 解決方案:當應用的權限發生變化的時候,華為手機發出廣播,導緻應用重新啟動。 解決辦法(比較笨):在Activity的onCreate()方法中,根據FragmentManager擷取到已經存在的fragment,并将它們移除掉。重新再建立一下需要展示的fragment

但是我想知道framework層是如何操作的?不知道有沒有大佬能夠分析一下源碼?

(三)魅族

魅族開發者文檔

(四)錘子

錘子開發者文檔

(五)oppo

oppo開發者文檔
2、關于

開發者選項

oppo手機的開發者模式很惡心,開啟“設定”》其他設定》開發者選項》USB調試 待機,然後狀态欄有個黃色的提醒視窗,提示10分鐘後自動關閉開發者選項。

3、關于

驗證碼

裝個應用要驗證碼,打開開發者選項需要驗證碼。。很惡心。。

4、oppo手機的R9系列和A系列的5.1系統存在嚴重的bug,類似以下這種的gc導緻的釋放逾時很多。

(六)vivo

vivo開發者文檔
  • 關于as項目無法在vivo中安裝的問題:

最近适配vivo手機 用的是vivo x9 發現應用無法在手機上安裝 已經打開了開發者模式還是不行,報以下錯:

【解決方案】

關掉Android Studio的Instant Run功能,然後把開發者模式中的USB安全模式(在USB調試下面)和USB調試一起打開。(其他手機遇到同樣問題,也可以用這個方式解決。)

二、開發中遇到的問題在不同手機上的處理方式

(一)沉浸式狀态欄适配

  • 這裡講一下華為手機沉浸式狀态欄和虛拟鍵盤沖突問題怎麼解決:

由于指數限制,詳細代碼請看我的github

https://github.com/AweiLoveAndroid/Solve-StatusBar-VirtualKeyBoard

(二)沉浸式狀态欄圖示的适配

  • 2.2.1 小米MIUI系統适配

之前做沉浸式狀态欄,由于公司APP底色是白色,是以對MIUI進行特殊處理。在MIUI V6及以上版本,調用MIUI的方法将狀态欄圖示改為黑色。發現部分小米手機,這樣的設定不管用,導緻頭上一片白,狀态欄上的東西基本看不到。

調整過程中發現以下情況:

手機型号 MIUI版本 Android版本 系統方法是否生效 MIUI的方法是否生效
紅米 NOTE 1LTE MIUI 8 8.2.1穩定版 4.4 生效
小米5 MIUI 8 8.5.3穩定版 7.0
MI 3W MIUI 9 7.9.14開發版 6.0.1

參考官方文檔:

http://www.miui.com/thread-8946673-1-1.html

(三)應用解除安裝然後安裝更新的适配

  • 2.3.1 華為适配

    華為手機程式解除安裝,安裝更新包,還是提醒更新包與安裝應用簽名不一緻。

  • 2.3.2 魅族适配
    • 問題1. 測試的簽名和你正式出包的簽名如果不一緻就不能安裝,解除安裝應用也沒用。
    • 問題2. 用as安裝過應用,解除安裝後安裝正式的apk就安裝不了,用adb指令解除安裝後就行了。

(四)改變狀态欄字型顔色為黑色的适配

  • 2.4.1 小米适配
/**
 * 改變小米的狀态欄字型顔色為黑色,要求MIUI6以上
 * tested on: MIUI V7 5.0 Redmi-Note3
 */
private void processMIUI(boolean lightStatusBar) throws Exception{
    Window window = getWindow();
    Class<? extends Window> clazz = window.getClass();
    int darkModeFlag;
    Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
    Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
    darkModeFlag = field.getInt(layoutParams);
    Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
    extraFlagField.invoke(window,lightStatusBar ? darkModeFlag : 0, darkModeFlag);
}
           
  • 2.4.2 魅族适配
/**
 * 改變魅族的狀态欄字型為黑色,要求FlyMe4以上
 */
private void processFlyMe(boolean isLightStatusBar) throws Exception{
    Window window = getWindow();
    WindowManager.LayoutParams layoutParams = window.getAttributes();
    Class<?> instance = Class.forName("android.view.MiuiWindowManager$LayoutParams");
    int value = instance.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON").getInt(layoutParams);
    Field field = instance.getDeclaredField("meizuFlags");
    field.setAccessible(true);
    int origin = field.getInt(layoutParams);
    if(isLightStatusBar){
        field.set(layoutParams, origin | value);
    }else{
        field.set(layoutParams, -value | origin);
    }

}
           

下面來一張示例圖:

(五)螢幕圓角實作和适配

示例圖

  • 實作原理:利用WindowManager将我們的圓角加到螢幕的四個角,圓角顔色設定為黑色,形成視覺圓角螢幕。

下面簡單的把一些核心代碼講一下:

  • ** 自定義圓角View,這裡以左上角為例:**
// top left
case Gravity.TOP | Gravity.LEFT:
    path.moveTo(0.0f, 0.0f);
    path.lineTo(0.0f, (float) h);
    path.arcTo(new RectF(0.0f, 0.0f,
            ((float) w) * 2.0f, ((float) h) * 2.0f), 180.0f, 90.0f, true);
    path.lineTo((float) w, 0.0f);
    path.lineTo(0.0f, 0.0f);
    path.close();
    break;
           
  • windowmanager在添加view的時候需要設定一個WindowManager.LayoutParams。下面初始化這個Params:
// window manager
manager = (WindowManager) this.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams();
/*
 系統提示類型:7.0以前可以直接用TOAST的類型,不用申請權限,直接添加
 7.0以後不行了,需要申請SYSTEM_ALERT_WINDOW權限,window type最好
 設定為ERROR 或者 PHONE
 */
if (Utilities.isCanUseToastType()) {
    params.type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
    params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
}
params.format = 1;
params.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN // 全屏
        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS // 覆寫到status bar
        | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION // 覆寫到導航欄

        // 以下屬性設定加載我們圓角window 不搶焦點,不攔截事件
        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.alpha = 1.0f;
params.x = 0;
params.y = 0;
// 設定  大小為全屏
params.width = ViewUtil.getScreenSize(this).x;
params.height = ViewUtil.getScreenSize(this).y;


           
  • 圓角加到螢幕上:
public void addCornerViewByPosition(String position){
    boolean enable = true;
    switch (position) {
        case LEFT_TOP:
            enable = leftTopEnable;
            params.gravity = Gravity.TOP | Gravity.LEFT;
            break;
        case RIGHT_TOP:
            enable = rightTopEnable;
            params.gravity = Gravity.TOP | Gravity.RIGHT;
            break;
        case LEFT_BOTTOM:
            enable = leftBottomEnable;
            params.gravity = Gravity.BOTTOM | Gravity.LEFT;
            break;
        case RIGHT_BOTTOM:
            enable = rightBottomEnable;
            params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
            break;
    }
    CornerView corner = buildCorner(enable,params.gravity);
    if(!corners.containsValue(corner)) {
        corners.put(position, corner);
        manager.addView(corner, params);
    }
}
           

螢幕圓角實作和适配,詳細的可以點選這裡:

http://mp.weixin.qq.com/s/h5qRvfgVj04f_xExTtrIHg

(六)在帶虛拟按鍵的手機上,虛拟按鍵會遮擋全屏圖檔的底部的解決。

在做splash頁面的時候,通過windowBackground設定背景圖檔,在帶虛拟按鍵的手機上,虛拟按鍵會遮擋圖檔的底部,這個問題的解決方式:

參考:

http://blog.csdn.net/c15522627353/article/details/52452490

究竟如何适配Android底部虛拟按鍵,可以參考這篇博文:

https://www.jianshu.com/p/b499628e0ae0

(七)懸浮窗權限設定了,dialog還是不提示。

(八)在Nexus 手機,原生Android 8.0上,使用掃碼的時候顯示的拍照預覽方向不正,有180度的旋轉并且變形的,解決方案:

private void surfaceIsChanged() {
    if (mHolder.getSurface() == null) {
        System.out.println("getSurface,nullnull");
        return;
    }
    try {
        mCamera.stopPreview();
    } catch (Exception e) {
        e.printStackTrace(); 
    }
    try {
        Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
        int dataBufferSize = (int) (previewSize.height * previewSize.width
              * (ImageFormat.getBitsPerPixel(mCamera.getParameters()
             .getPreviewFormat()) / 8.0));
        mCamera.addCallbackBuffer(new byte[dataBufferSize]);
        mCamera.addCallbackBuffer(new byte[dataBufferSize]);
        mCamera.addCallbackBuffer(new byte[dataBufferSize]);
        mCamera.setPreviewDisplay(mHolder);
        mCamera.setPreviewCallback(previewCallback);
        mCamera.startPreview();
        mCamera.autoFocus(autoFocusCallback);
        
        // 核心代碼:根據照相的内容進行設定顯示方向。
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        // DO your logic to get front or back camera...or loop through all avaialable.
        int camIdx = 0; 
        Camera.getCameraInfo(camIdx, cameraInfo);

        try {
            // If using back camera then simply rotate what CameraInfo tells you.
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK){
                mCamera.setDisplayOrientation(cameraInfo.orientation);
            }else{
                // If using front camera note that image might be flipped
                //  to give users the impresion the are looking at a mirror.
                mCamera.setDisplayOrientation( (360 - cameraInfo.orientation) % 360);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //開始掃描
        // Toast.makeText(QRZbarActivity.this, "開始掃描",Toast.LENGTH_SHORT).show(); 
        // 打開閃光燈,這個方法自己去實作,這裡不是重點,就不寫了。
        autoOpenLight();
    } catch (Exception e) {
        Toast.makeText(BaseScanActivity.this, R.string.account_toast_not_open_camera, 
                   Toast.LENGTH_SHORT).show();
        // showTip("您未允許" + getResources().getString(R.string.app_name) 
        // + "通路您的相冊\n請在“安全中心 -授權管理”中更改設定");
        Log.d("DBG", "Error starting camera preview: " + e.getMessage());
    }
}
           

這個解決方案來自:

https://stackoverflow.com/questions/12017148/android-camera-setdisplayorientation90-fails-in-different-devices#

(九)擷取手機裡所有儲存設備盤符,不同廠商手機的路徑可能不一樣。

問題描述:華為手機很變态,存儲路徑跟原生系統的不一樣,是以需要對其做特别處理。

解決方案:需要用到一個被系統隐藏的方法,即StorageManager下的getVolumePaths()方法。具體通過反射可以得到,其中mPath、mRemovable、mEmulated、mState這幾個屬性是我們需要關注的。

具體代碼,可以參看部落格

Android判斷是否存在外置SD卡(擷取手機所有儲存設備的路徑)
【好消息】我的微信公衆号正式開通了,關注一下吧!

關注一下我的公衆号吧

繼續閱讀