關於android:targetSdkVersion所導致的問題

來源:互聯網
上載者:User

標籤:

最近兩天一直在糾結個問題,就是我們新版的軟體通過IDEA編譯出來運行在4.4的手機上整個相機UI是完全錯亂的,同事幾個手機運行都一樣,錯亂的樣子就是整個UI壓縮擠壓在一起,完全不是你在布局裡面設定的還具有相對位置的樣子。 但是通過IDEA的布局檔案的design按鈕看到的布局展示demo又是正常的,所以,一直懷疑是編譯的問題,或者某些控制項的id是否有重複。於是重新rebuild,重新刪除out,gen目錄,重新編譯,問題依舊。暈哦,怎麼回事?於是,我想仔細去分析下。於是隨意又用meizu的4.1系統的手機去試了下,結果,我擦,居然正常的,怎麼回事?問了同事最近有沒有改過相機相關的代碼,都說沒有。我看了提交記錄,的確是沒有啊,怎麼可能呢?實在想不明白,後來追蹤以前的記錄,發現改了manifest裡面一個地方就是android:targetSdkVersion這個屬性,暈,這個屬相和相機UI展示有毛線關係,哎,還是看看吧。同事從android:targetSdkVersion = 14改成了android:targetSdkVersion = 19,於是恢複試試,結果,我去,居然運行ok了,再改回19試試,擦,又不ok了。好了,原因找到了,是android:targetSdkVersion的問題,可是為什麼android:targetSdkVersion會影響到相機UI的展示?八竿子打不著啊!!android:targetSdkVersion="14",targetSdkVersion屬性會告訴系統應用是在api level為14的系統上進行的測試,應用不允許有向上相容的行為。當應用運行在版本更高的api level的系統上時,應用還是按照targetSdkVersion版本的運行,而不需要根據更高版本的系統來顯示。但是為什麼會出現這個問題?接下來我看了下4.4系統api的說明,在Build.java類裡面        /**         * Android 4.4: KitKat, another tasty treat.         *         * <p>Applications targeting this or a later release will get these         * new changes in behavior:</p>         * <ul>         * <li> The default result of {android.preference.PreferenceActivity#isValidFragment         * PreferenceActivity.isValueFragment} becomes false instead of true.</li>         * <li> In {@link android.webkit.WebView}, apps targeting earlier versions will have         * JS URLs evaluated directly and any result of the evaluation will not replace         * the current page content.  Apps targetting KITKAT or later that load a JS URL will         * have the result of that URL replace the content of the current page</li>         * <li> {@link android.app.AlarmManager#set AlarmManager.set} becomes interpreted as         * an inexact value, to give the system more flexibility in scheduling alarms.</li>         * <li> {@link android.content.Context#getSharedPreferences(String, int)         * Context.getSharedPreferences} no longer allows a null name.</li>         * <li> {@link android.widget.RelativeLayout} changes to compute wrapped content         * margins correctly.</li>         * <li> {@link android.app.ActionBar}'s window content overlay is allowed to be         * drawn.</li>         * <li>The {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}         * permission is now always enforced.</li>         * <li>Access to package-specific external storage directories belonging         * to the calling app no longer requires the         * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} or         * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}         * permissions.</li>         * </ul>         */        public static final int KITKAT = 19;裡面有句話說了android.widget.RelativeLayout} changes to compute wrapped content margins correctly,說重新修改了RelativeLayout計算margin的方式,我想,可能是不是和改動RelativeLayout有關呢?接下來又去看了RelativeLayout的api說明。擦,這下貌似有問題了,RelativeLayout裡面介紹說Note: In platform version 17 and lower, RelativeLayout was affected by a measurement bug that could cause child views to be measured with incorrect MeasureSpec values. (See MeasureSpec.makeMeasureSpec for more details.) This was triggered when a RelativeLayout container was placed in a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view not equipped to properly measure with the MeasureSpec mode UNSPECIFIEDwas placed in a RelativeLayout, this would silently work anyway as RelativeLayout would pass a very large AT_MOST MeasureSpec instead.This behavior has been preserved for apps that set android:targetSdkVersion="17" or older in their manifest's uses-sdk tag for compatibility. Apps targeting SDK version 18 or newer will receive the correct behavior他說RelativeLayout裡面的MeasureSpec.makeMeasureSpec在17以前實現是有問題的,17以後才改了,哦,MeasureSpec.makeMeasureSpec這個方法有問題,好了,繼續調查。我們的相機ui是這樣寫的    <com.pinguo.camera360.camera.controller.CameraLayout        android:id="@+id/layout_camera_container"        android:layout_width="fill_parent"        android:layout_height="fill_parent"        android:background="#ff000000">        <!-- 預覽取景地區,ID不能隨意變動 -->        <include            android:id="@+id/layout_camera_preview"            layout="@layout/camera_preview_container"/>        <!-- 底部bar,ID不能隨意變動 -->        <include            android:id="@+id/layout_camera_bottom_bar"            layout="@layout/layout_camera_bottom_menu2"/>    </com.pinguo.camera360.camera.controller.CameraLayout>   這個CameraLayout是一個自訂的View,繼承的是ViewGroup,   ----public class CameraLayout extends ViewGroup-----   然後CameraLayout裡面重寫了onMeasure方法,也用到了MeasureSpec.makeMeasureSpec方法,恩,可能有問題,繼續       @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int width = MeasureSpec.getSize(widthMeasureSpec);        int height = MeasureSpec.getSize(heightMeasureSpec);        calcLayoutRect(width, height);        final int count = getChildCount();        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);            if (child.getVisibility() != View.GONE) {                switch (child.getId()) {    // 就是這個android:id="@+id/layout_camera_preview"                    case PREVIEW_ID:                        int wm2 = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,preLayRect.right - preLayRect.left);                        int hm2 = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,preLayRect.bottom - preLayRect.top);                        child.measure(wm2, hm2);                        break;    // 就是這個android:id="@+id/layout_camera_bottom_bar"                    case BOTTOM_ID:                        int wm3 = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,botLayRect.right - botLayRect.left);                        int hm3 = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,botLayRect.bottom - botLayRect.top);                        child.measure(wm3, hm3);                        break;                    default:                        break;                }            }        }    }仔細一看,發現 MeasureSpec.makeMeasureSpec(preLayRect.right - preLayRect.left,MeasureSpec.EXACTLY);這個方法參數傳反了,public static int makeMeasureSpec(int size, int mode)方法第一個應該是size,第二個是mode,但是我們自己不小心寫錯了。那麼為什麼寫錯了,一直沒出錯呢,使用者沒反饋呢,不應該啊。繼續看源碼當我們把android:targetSdkVersion="14"配置成14的時候,也就是使用4.0的實現,4.0源碼的makeMeasureSpec是這樣實現的,4009        /**14010         * Creates a measure specification based on the supplied size and mode.14011         *14012         * The mode must always be one of the following:14013         * <ul>14014         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>14015         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>14016         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>14017         * </ul>14018         *14019         * @param size the size of the measure specification14020         * @param mode the mode of the measure specification14021         * @return the measure specification based on size and mode14022         */14023        public static int makeMeasureSpec(int size, int mode) {14024            return size + mode;14025        }大爺的,這就是上面系統說的那個問題了,這個實現不管你參數傳沒傳反,都是返回一樣的結果。所以,配置成android:targetSdkVersion="14,即使參數傳反了,也沒問題。如果配置android:targetSdkVersion="19",那麼採用4.4的實現來運行代碼。而4.4修改後的實現是這樣的        /**         * Creates a measure specification based on the supplied size and mode.         *         * The mode must always be one of the following:         * <ul>         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>         * </ul>         *         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's         * implementation was such that the order of arguments did not matter         * and overflow in either value could impact the resulting MeasureSpec.         * {@link android.widget.RelativeLayout} was affected by this bug.         * Apps targeting API levels greater than 17 will get the fixed, more strict         * behavior.</p>         *         * @param size the size of the measure specification         * @param mode the mode of the measure specification         * @return the measure specification based on size and mode         */        public static int makeMeasureSpec(int size, int mode) {            if (sUseBrokenMakeMeasureSpec) {                return size + mode;            } else {                return (size & ~MODE_MASK) | (mode & MODE_MASK);            }        }sUseBrokenMakeMeasureSpec是什麼呢?從源碼可知它是用來適配的。// Older apps may need this compatibility hack for measurement.sUseBrokenMakeMeasureSpec = targetSdkVersion <= JELLY_BEAN_MR1;恩,問題找到了,當你配置android:targetSdkVersion="19"後,此時sUseBrokenMakeMeasureSpec == false,然後走的邏輯就是return (size & ~MODE_MASK) | (mode & MODE_MASK);因為此時我們方法參數傳發了,所以,就導致UI布局整個出了問題,錯亂了。當配置android:targetSdkVersion="14"後,及時當前運行在4.4的手機上,sUseBrokenMakeMeasureSpec也為ture,系統走的還是老的錯誤實現方式布局(雖然是錯誤的,但是google能讓它正常運行,只是實現不對而已),所以,就還是ok的。這就是和原因所在。


關於android:targetSdkVersion所導致的問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.