這些年,我爬過的 Android 坑 | 持續更新 (2020/07/03 update)

總結目錄

<<視圖篇>>

  • 如何理解非主線程能夠更新UI

    谷歌在 viewRootImpl 中檢查更新ui的線程
    void checkThread() {  
        if (mThread != Thread.currentThread()) {  
            throw new CalledFromWrongThreadException(  
                    "Only the original thread that created a view hierarchy can touch its views.");  
        }  
    } 
    複製代碼

    在執行onCreate的時候這個判斷並無執行到html

  • dialogFragment 全屏時左右留空的解決方案

    在 fragment#onResume 中從新調整 window 佈局java

    android.view.WindowManager.LayoutParams lp = window.getAttributes();
    lp.width = WindowManager.LayoutParams.MATCH_PARENT;
    lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
    window.setAttributes(lp);
    複製代碼
  • dialogFragment 全屏時狀態欄出現黑色佈局的解決方案

    在主題中設置
    <item name="android:windowIsFloating">true</item>
    複製代碼

    此時 window 爲 wrap_content,若是出現左右空白,則考慮使用上個問題的方案。linux

  • 當應用退回後臺一段時間重返後,Fragment 切換重疊的解決方案

    在線上項目中咱們遇到一個場景:當應用按下 Home 退回後臺,而後過一段時間以後從後臺拉起咱們的項目。極少數機型在主頁進行多個 fragment 的切換時出現了 fragment 的重疊。通過定位以後發現,這些機型的運存偏小,性能誤差,出現這種現象的緣由是因爲內存的壓力的緣由,系統並不知後臺的程序哪個才須要保持運行,就會嘗試回收內存佔用較大的頁面,當咱們的頁面被系統銷燬時,fragmentActivity#onSaveInstanceState 被執行並保存了一些瞬態信息,好比界面 fragment 的視圖信息。當咱們再次拉起應用的時候,會讓原來的 fragmentActivity 重建並從新構建了一個新的 fragment ,此時會疊加到已經被恢復的 fragment 之上致使重疊。android

    比較暴力的作法是不讓 activity 保存狀態,好比git

    @Override
    public void onSaveInstanceState(Bundle outState) {
    	 //直接不調用 super.onSaveInstanceState(outState);
    	 //或者直接傳遞空數據 
        super.onSaveInstanceState(new Bundle());
    }
    
    複製代碼

    比較優雅的作法是,好比github

    @Override
    public void onSaveInstanceState(Bundle outState) {
    	 getSupportFragmentManager().putFragment(outState, you_key, CusFragment);
        super.onSaveInstanceState(outState);
    }
    
    //在onCreate的時候判斷是否已經存在保存的信息
     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState != null) {
            CusFragment fragment =  (CusFragment)getSupportFragmentManager().getFragment(savedInstanceState, you_key);
        } else {
        	  //init CusFragment
        }
    }
    複製代碼
  • 多個fragment 保存狀態時可能出現 TransactionTooLargeException 的解決方案

    出現 TransactionTooLargeException 異常時,由於線上咱們使用了 FragmentStatePagerAdapter 做爲 fragment 適配器爲了儘量過緩存下瀏覽過的 fragment 以得到更好的體驗,承載多個 FragmentStatePagerAdapter#saveState 會被調用並對每個 fragmentbundle 數據進行保存。因爲咱們的 bundle 較大,而且保存下來的 bundle 並不會由於 fragment 被銷燬而銷燬,因此須要保存的 bundle 數據會一直增加,直到出現TransactionTooLargeException 異常. 咱們參考stackoverflow相關問題 直接重載 saveState 丟棄 states 內容。面試

    public Parcelable saveState() {
        Bundle bundle = (Bundle) super.saveState();
        bundle.putParcelableArray("states", null); // Never maintain any states from the base class, just null it out
        return bundle;
    }
    複製代碼

    另外推薦 toolargetool 工具能夠在開發中實時觀測頁面內存變化。sql

  • recyclerview 調用 notifyItemRemoved 方法移除某個 Item 以後由於引用 position 引發 crash 的緣由

    notifyItemRemoved方法並不會移除列表的數據源的數據項致使數據源中的數據與列表Item數目不一致,須要同步刷新數據源。數據庫

  • recyclerview 局部刷新Item時會由於默認動畫致使閃爍的解決方案

    由於recyclerview存在ItemAnimator,且在刪除/更新/插入Item時會觸發,可設置不支持該動畫便可。後端

    ((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
    複製代碼
  • recyclerview 中的 item 出現莫名的偏移滾動

    這個問題通過定位存在於 viewholder 中的某個 view 可能提早獲取到焦點。 同時在。alibaba-vlayout 庫中也發現有人反饋改問題。 issues-255 解決的方法是在 Recyclerview中外層父佈局中添加 android:descendantFocusability="blocksDescendants" 用於父佈局覆蓋 Recyclerview 優先搶佔焦點。

  • recyclerview 內容超過一屏時,findFistCompletelyVisibleItemPosition 會返回 -1 的緣由

    緣由是在 findOneVisibleChild 計算出來的 start 和 end 已經超過了 reclclerview 的 start 和 end.通過研究源碼獲得如下。

    findFirstCompletelyVisibleItemPosition -> -1
    findLastCompletelyVisibleItemPosition -> -1
    findFirstVisibleItemPosition -> 正常
    findLast
    複製代碼
  • recyclerview 中 ViewHolder 沒法被佈局填充的問題解決方法

    緣由是在構建 Holder 的時候,獲取的 view 視圖的時候 LayoutInflater#inflate() 最後的參數傳遞錯誤,若是傳遞的 null,則默認 xml 佈局加載後的 View 的寬高信息失效

    @Override
    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_layout,parent, false);
        MyHolder holder = new MyHolder(view);
        return holder;
    }
    複製代碼

    新版本已不存在這個問題。

  • textview 中富文本點擊事件攔截了長按事件的解決方案

    這個問題常見於消息列表中,某條消息使用了ClickableSpan用於處理富媒體的點擊事件,同時這個消息又須要支持長按複製。因爲LinkMovementMethod方法在onTouchEvent一直返回true,能夠經過自定義View.onTouchListener來替換setMovenmentMethod達到效果。
    public class ClickMovementMethod implements View.OnTouchListener {
    private LongClickCallback longClickCallback;
    
    public static ClickMovementMethod newInstance() {
        return new ClickMovementMethod();
    }
    
    @Override
    public boolean onTouch(final View v, MotionEvent event) {
        if (longClickCallback == null) {
            longClickCallback = new LongClickCallback(v);
        }
    
        TextView widget = (TextView) v;
        // MovementMethod設爲空,防止消費長按事件
        widget.setMovementMethod(null);
        CharSequence text = widget.getText();
        Spannable spannable = Spannable.Factory.getInstance().newSpannable(text);
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) {
            int x = (int) event.getX();
            int y = (int) event.getY();
            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();
            x += widget.getScrollX();
            y += widget.getScrollY();
            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);
            ClickableSpan[] link = spannable.getSpans(off, off, ClickableSpan.class);
            if (link.length != 0) {
                if (action == MotionEvent.ACTION_DOWN) {
                    v.postDelayed(longClickCallback, ViewConfiguration.getLongPressTimeout());
                } else {
                    v.removeCallbacks(longClickCallback);
                    link[0].onClick(widget);
                }
                return true;
            }
        } else if (action == MotionEvent.ACTION_CANCEL) {
            v.removeCallbacks(longClickCallback);
        }
    
        return false;
    }
    
    private static class LongClickCallback implements Runnable {
        private View view;
    
        LongClickCallback(View view) {
            this.view = view;
        }
    
        @Override
        public void run() {
            // 找到可以消費長按事件的View
            View v = view;
            boolean consumed = v.performLongClick();
            while (!consumed) {
                v = (View) v.getParent();
                if (v == null) {
                    break;
                }
                consumed = v.performLongClick();
            }
        }
    }
    }
    
    textView.setOnTouchListener(ClickMovementMethod.newInstance());
    複製代碼
  • 如何禁止 ViewPager 的滑動

    重寫ViewPager onTouchEvent 和 onInterceptTouchEvent 並返回false,不處理任何滑動事件

    @Override
    public boolean onTouchEvent(MotionEvent arg0) {
        return false;
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent arg0) {
        return false;
    }
    複製代碼
  • 如何仿蘑菇街/馬蜂窩 Viewpager 裝載圖片以後切換時動態變動高度

    imageViewPager 爲普通的 Viewpager 對象

    imageListInfo爲存放圖片信息的list,imageShowHeight爲業務須要顯示高度,經過切換時動態計算調整

    imageViewPager.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    imageViewPager.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    //根據viewpager的高度,拉伸顯示圖片的寬度調整高度。
                    ViewGroup.LayoutParams layoutParams = imageViewPager.getLayoutParams();
                    layoutParams.height = imageListInfo.imageShowHeight[0];
                    imageViewPager.setLayoutParams(layoutParams);
                }
            });
            imageViewPager.setAdapter(imagePagerAdapter);
            imageViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                @Override
                public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                    if (position == imageListInfo.getImageListSize() - 1) {
                        return;
                    }
                    int height = (int) (imageListInfo.imageShowHeight[position] * (1 - positionOffset) + imageListInfo.imageShowHeight[position + 1] * positionOffset);
                    ViewGroup.LayoutParams params = imageViewPager.getLayoutParams();
                    params.height = height;
                    imageViewPager.setLayoutParams(params);
                }
    
                @Override
                public void onPageSelected(int position) {
                    if (!clickListBySelf) {
                        toSelectIndex(imageListInfo.selected, position);
                    }
                }
    
                @Override
                public void onPageScrollStateChanged(int state) {
    
                }
            });
    複製代碼
  • 如何控制 appbarLayout 隨時定位到某個位置

    CoordinatorLayout.Behavior behavior =((CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams()).getBehavior();
    if (behavior instanceof AppBarLayout.Behavior) {
          AppBarLayout.Behavior appBarLayoutBehavior = (AppBarLayout.Behavior) behavior;
          int topAndBottomOffset = appBarLayoutBehavior.getTopAndBottomOffset();
          if (topAndBottomOffset != 0) {
                 appBarLayoutBehavior.setTopAndBottomOffset(0);
          }
    複製代碼
  • 如何禁止 appbarLayout 滾動

    CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
    AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
    behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
        @Override
        public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
            return false;
        }
    });
    複製代碼
  • edittext 未能響應 onClickListener 事件的解決方案

    Edittext監聽未獲取焦點的Edittext的點擊事件,第一次點擊觸發OnFocusChangeListener,在獲取焦點的狀況下才能響應onClickListener

  • 使用 listview 或gridview 的處理 item 的 state_selected 事件是無效的解決方案

    在xml佈局中對listview或gridview設置Android:choiceMode="singleChoice",並使用state_activated狀態來代替state_selected狀態。(2016.12.10)

  • 解決5.0以上Button自帶陰影效果的方案

    在xml定義的Button中,添加如下樣式定義

    style="?android:attr/borderlessButtonStyle"
    複製代碼
  • 針對 onSingleTapUp 和 onSIngleTapConfirmed 的使用區別

    前者在按下並擡起時發生,後者有一個附加條件時Android會確保點擊以後在短期內沒有再次點擊纔會觸發。經常使用於若是須要監聽單擊和雙擊事件。

  • 如何使用layer-list畫三角形

    <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    //左
    <item>
        <rotate
            android:fromDegrees="45"
            android:pivotX="85%"
            android:pivotY="135%">
            <shape android:shape="rectangle">
                <size
                    android:width="16dp"
                    android:height="16dp" />
                <solid android:color="#7d72ff" />
            </shape>
        </rotate>
    
    </item>
    
    //右
    <item>
        <rotate
            android:fromDegrees="45"
            android:pivotX="15%"
            android:pivotY="-35%">
            <shape android:shape="rectangle">
                <size
                    android:width="16dp"
                    android:height="16dp" />
                <solid android:color="#7d72ff" />
            </shape>
        </rotate>
    
    </item>
    
    //上/正
    <item>
        <rotate
            android:fromDegrees="45"
            android:pivotX="-40%"
            android:pivotY="80%">
            <shape android:shape="rectangle">
                <size
                    android:width="16dp"
                    android:height="16dp"/>
                <solid android:color="#7d72ff"/>
            </shape>
        </rotate>
    </item>
    
    //下
    <item>
        <rotate
            android:fromDegrees="45"
            android:pivotX="135%"
            android:pivotY="15%">
            <shape android:shape="rectangle">
                <size
                    android:width="16dp"
                    android:height="16dp"/>
                <solid android:color="#7d72ff"/>
            </shape>
        </rotate>
    </item>
    複製代碼
```
  • 關於屬性動畫中旋轉 View 時部分機型出現 View 閃爍的解決方案

    在大神 app 信息流快捷評論模塊中,在交付快捷評論動畫的時候發現,使用屬性動畫實現的抖動效果在部分機型上出現閃爍。而咱們的實現抖動效果是經過 View.ROTATION 來實現的。通過研究,部分機型由於硬件加速的緣由致使的。爲動畫 view 進行如下設置

    view.setLayerType(View.LAYER_TYPE_HARDWARE,null);
    複製代碼
  • 關於 ConstraintLayout 的代碼佈局下的注意事項

    不一樣於其餘 ViewGroup 控制子 View 的排版,ConstraintLayout 須要構建 ConstraintSet 對象來粘合。 在手動添加子 View 的場景下,能夠經過 ConstraintSet#clone(ConstraintLayout constraintLayout) 來克隆當前已有 ConstraintLayout 的排版信息,而後最後調用 ConstraintSet#applyTo(ConstraintLayout constraintLayout) 確認最終的排版信息。

  • TextView 在 6.0 版本下設置單行尾部縮略的坑

    在大神信息流中,有一些卡片信息須要設置單行縮略。在 MTL 兼容測試過程當中發現有一些機型顯示異常,通過概括及校驗,這部分機型的版本都是 < 6.0。 經過在 stackoverflow 也找到了相同的問題場景 text ellipsize behavior in android version < 6.0 . 針對這部分版本的手機,咱們須要在設置單行的時候把 android:maxLines="1" 改爲 android:singleLine="true"。即便 IDE 提示該 API 已通過期了!

  • EditText 自動獲取焦點引發問題的解決方案

    在xml中對其父控件添加 foucusable 控制,添加如下屬性來攔截 EditText 的默認行爲
    android:focusable="true"  
    android:focusableInTouchMode="true"
    複製代碼

<<服務篇>>

  • 後臺手動清理應用以後,service中啓動的notifications並無消失的解決方案

    How to remove all notifications when an android app (activity or service) is killed? 的諸多討論中學習到, Service#onTaskRemoved 是咱們的App被清理以後Service的回調。嘗試過一下方法並不能達到清除的效果。

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        NotificationManager nManager = ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE));
        nManager.cancelAll();
    }
    複製代碼

    在線上應用中,因爲咱們的通知相似於將軍令這種有定時更新的功能,須要完全乾掉全部serivce承載的功能,下面方法可行

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        stopSelf();
        stopForeground(true);
    }
    複製代碼
  • 全局的Context使用更爲優雅的獲取方案

    因爲咱們在優化 Application 啓動時間時,打算移除 applciation 全部有關靜態申明的變量,其中就包含全局 context 這個變量。咱們參考的是 leakCanary 庫的作法,使用 ContentProvider 來承載全局 context 的獲取,緣由是在 ActivityThread 的初始化流程中,ContentProvider#onCreate() 是在 Application#attachBaseContext(Context)Application#onCreate() 之間的。因此獲取的 context 是有效的

    class ContextProvider : ContentProvider() {
    
        companion object {
            private lateinit var mContext: Context
            private lateinit var mApplication: Application
            fun getGlobalContext(): Context = mContext
            fun getGlobalApplication(): Application = mApplication
        }
        override fun onCreate(): Boolean {
            mContext = context!!
            mContext = context!!.applicationContext as Application
            return false
        }
    
        override fun insert(uri: Uri, values: ContentValues?): Uri? = null
        override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
        override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int = -1
        override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = -1
        override fun getType(uri: Uri): String? = null
    }
    
    
    //manifest申明
    <!-- Context提供者 -->
    <provider
            android:name=".ContextProvider"
            android:authorities="${your_application_id}.contextprovider"
            android:exported="false" />
    複製代碼

<<線程篇>>

  • 建議新起線程不要隨便調用網絡請求,通常的newThread沒有looper隊列,參考handlerThread

<<網絡篇>>

  • Retorfit get 請求參數出現錯誤的解決方案

    @GET( BASE_URL + "index/login" )
    Observable< LoginResult > requestLogin( @QueryMap(encoded = true) Map< String, String > params );
     
    final Map< String, String > paramsMap = new ParamsProvider.Builder().
            append( "username", account ).
            append( "password",URLEncoded(password) ).
    
    複製代碼

    好比登錄,encode = true 表示未對特殊字符進行url編碼,默認是false。

<<數據篇>>

  • 如何優雅處理 sqlite 多線程讀寫問題

    • 一個helper實例對應一個 db connectton,這個鏈接能提供讀鏈接和寫鏈接,若是隻是調用 read-only,則默認也會有寫鏈接。
    • 一個helper實例能夠在多個線程中使用,java層會使用鎖機制保證線程同步,哪怕有100個線程,對數據度的調用也會被序列化
    • 若是嘗試從不一樣 connection 同時對數據庫進行寫操做,則有一個會失敗。並不會按照第一個寫完再輪到第二個寫,有一些sqlite版本甚至不會有錯誤提示。

    通常而言,若是要在多線程環境下使用數據庫,則確保多個線程中使用的是同一個SQLiteDataBase對象,該對象對應一個db文件。

    特殊狀況,若是多個 SQLiteDataBase 打開同一個 db 文件,同時使用不一樣線程同時寫(insert,update,exexSQL)會致使在 SQLiteStatement.native_execute 方法時可能致使異常。這個異常來自本地方法裏面,僅僅在Java對有對 SQLiteDataBase 進行同步鎖保護。可是多線程讀(query)返回的事 SQLiteCursor保存查詢條件並無馬上執行查詢,僅僅在須要時加載部分數據,能夠多線程不一樣 SQLiteDataBase 進行讀。

    若是要處理上述問題,可使用 「一個線程寫,多個線程同時讀,每一個線程都用各自SQLiteOpenHelper。」

    在android 3.0版本以上 打開 enableWriteAheadLogging。當打開時,它容許一個寫線程與多個讀線程同時在一個SQLiteDatabase上起做用。實現原理是寫操做實際上是在一個單獨的文件,不是原數據庫文件。因此寫在執行時,不會影響讀操做,讀操做讀的是原數據文件,是寫操做開始以前的內容。在寫操做執行成功後,會把修改合併會原數據庫文件。此時讀操做才能讀到修改後的內容。可是這樣將花費更多的內存。

  • 如何理解 Intent 傳遞數據出現 `TransactionTooLargeException`的問題

    Intent 傳輸數據的機制中,用到了 Binder。Intent 中的數據,會做爲 Parcel 被存儲在 Binder 的事務緩衝區(Binder transaction buffer)中的對象進行傳輸.而這個 Binder 事務緩衝區具備一個有限的固定大小,當前爲 1MB。你可別覺得傳遞 1MB 如下的數據就安全了,這裏的 1MB 空間並非當前操做獨享的,而是由當前進程所共享。也就是說 Intent 在 Activity 間傳輸數據,自己也不適合傳遞太大的數據.

    參考阿里 《Android 開發者手冊》 對於Activity間數據通信數據較大,避免使用Intent+Parcelable的方式,能夠考慮使用EventBus等代替方案,避免 TransactionTooLargeException。EventBus使用黏性事件來解決,可是針對Activity重建又能拿到Intent而EventBus則不能夠,因此須要根據業務來調整。

<<機型系統適配篇>>

  • 如何解決 NatigationBar 被 PopupWindow 遮擋的問題

    popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    複製代碼
  • 如何解決MIUI系統後臺沒法 toast 的問題

    參考 github.com/zhitaocai/T… 項目,可是在小米3或者小米Note(4.4.4)手機上

    mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
    複製代碼

    mContext 須要使用 ApplicationContext 才能生效。

  • 如何解決關閉通知欄權限沒法彈出 toast 的問題

    因爲谷歌把 Toast 設置爲系統消息權限,能夠參考 Android關閉通知消息權限沒法彈出Toast的解決方案 維護本身的消息隊列.

  • 如何適配 vivo 等雙面屏幕

    在 AndroidManifest.xml 中聲明一下 meta-data

    <meta-data
    android:name="andriod.max_aspect" android:value="ratio_float"/>
    複製代碼

    或者使用 android:maxAspectRatio="ratio_float"(API LEVEL 26) ratio_float 通常爲屏幕分辨率高寬比。 其餘好比凹槽區域,圓角切割等問題能夠參考市面上最新的vivo機型對應的 vivo開放平臺 文檔。

  • 如何解決華爲設備產生太多 broadcast 致使crash的問題

    因爲部分華爲中,若是app註冊超過500個BroadcastReceiver就會拋出 「 Register too many Broadcast Receivers 」 異常。經過分析發現其內部有一個白名單,本身能夠經過建立一個新的app,使用微信包名進行測試,發現並無這個限制。經過反射 LoadedApk 類拿到 mReceiverResource 中的 mWhiteList 對象添加咱們的包名就能夠了。 能夠參考 github.com/llew2011/Hu… 這個項目。

  • 如何解決華爲設備中不展現 「AdView」 的問題

    在咱們項目中首頁曾經有個控件叫 「AdView」 用來顯示廣告用的,離譜的時這個 View 在咱們 setVisibility() 的時候華爲手機上又調用了一次 setVisibility(View.GONE). 後面更改 view 的名稱解決了這個問題。這個問題應該是由於 ROM 內部經過必定的規則來判斷咱們的應用中視圖 View 是否爲廣告視圖進而強制隱藏致使的。因此在寫 App 顯示廣告相關的視圖時,命名儘可能隱晦一點哈。

  • 各類通知欄的適配方案

    參考 網易考拉實現的適配方法

  • 解決針對魅族推送內容限制的問題

    今天收到魅族渠道的警報稱「推送內容可能過長」。IM功能針對離線設備走設備商的推送,魅族推送限制了title標題1-32字符,content內容1-100字符。若是頻繁推送超過限制的通知,魅族推送服務器可能不會下發推送到魅族設備。故服務端限制發送到魅族服務器的消息標題和內容長度解決。

  • 解決從系統安裝起安裝應用後啓動,Home 隱藏後 Launcher 重複啓動的問題

    判斷啓動頁面是不是根節點(推薦)

    if(!isTaskRoot()){
    	finish();
    	return 
    }
    複製代碼

    或者判斷Activity是否多了 FLAG_ACTIVITY_BROUGHT_TO_FRONT ,這個tag是該場景緻使的

    if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
            finish();
            return;
    }
    複製代碼
  • 針對有launcher作爲Activity的應用,在徹底沒有啓動下收到第三方推送(小米,華爲,魅族)/分享拉起的注意事項

    因爲咱們的應用LauncherActivity用於分發不一樣場景的入口,A邏輯進入特殊場景頁面A,B邏輯進入主頁面B。

    • onCreate中優先攔截 intent 判斷拉起參數,若是有拉起參數則直接進入主頁面B,intent交付給主頁面B處理
    • 部分場景下,好比第三方消息推送,華爲和小米拉起閃屏或 launcher intent沒法區分,針對該作法是,限定進入閃屏/launcher的邏輯,剩餘場景統一進入主頁面B
    if (後端控制是否須要進入特殊場景頁面) {
        boolean goToA = false;
        if (getIntent() != null) {
            String action = getIntent().getAction();
            Set<String> category = getIntent().getCategories();
            if (TextUtils.equals(action, "android.intent.action.MAIN")) {
                if (category != null && category.contains("android.intent.category.LAUNCHER")) {
                    Intent intent = new Intent(this, 頁面A.class);
                    intent.setData(getIntent().getData());
                    startActivity(intent);
                    goToA = true;
                }
            }
        }
        if (! goToA) {
            goToMainActivity();
        }
    } else {
        goToMainActivity();
    }
    複製代碼
  • 針對 App 多場景拉起場景下的場景判斷分析

    可參考我另外一篇文章 對線上項目拉起應用場景的思考總結

  • 8.0 部分 ROM 出現 Only fullscreen opaque activities can request orientation 的解決方案

    因爲咱們項目須要處理沉浸式,因此針對 android:windowIsTranslucent 的屬性默認打開的。可是線上發現部分 8.0設備出現詭異的 crash,緣由是咱們對於頁面的 orientation 申明都統一爲 portrait 。查閱 android 源碼的更新發如今 8.0 源碼的邏輯裏面這兩個邏輯居然不兼容,隨後在 8.0 版本後谷歌進行了修復。可是國內部分 ROM 看起來並無修復這個問題。後面同事提供了一個比較取巧的方案,經過爲頁面指定 android:screenOrientation="behind" 來避免 8.0 版本的問題同時兼容全部 android 版本。

  • 9.0 android 支持明文鏈接(Http)

    Android 9(API級別28)開始,默認狀況下禁用明文支持

    <?xml version="1.0" encoding="utf-8"?>
    <manifest ...>
        <uses-permission android:name="android.permission.INTERNET" />
        <application
            ...
            android:usesCleartextTraffic="true"
            ...>
            ...
        </application>
    </manifest>
    複製代碼
  • 10.0 獲取 IMEI 異常的解決方案

    參考 I am getting IMEI null in Android Q 的問題討論。

    官網對 Android 10 設備標識符的更新說明 設備標識符 指出,本來使用 READ_PHONE_STATE 可獲取的 IMEI 和序列號的方式,已經沒法支持了。除非你的 App 被 ROM 設置爲系統級別的 App。

    谷歌雖然給出了 惟一標識符最佳作法,可是該方案適配的場景有限。最好的方法仍是使用 UUID 實現本身的一套設備識別方案。

    參考 Android Q 適配指南 讓你少走一堆彎路 的寫法。

    public static String getUUID() {
    String serial = null;
    String m_szDevIDShort = "35" +
        Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
        Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
        Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
        Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
        Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
        Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
        Build.USER.length() % 10; //13 位
    
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        serial = android.os.Build.getSerial();
        } else {
        serial = Build.SERIAL;
        }
        //API>=9 使用serial號
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    } catch (Exception exception) {
        //serial須要一個初始化
        serial = "serial"; // 隨便一個初始化
    }
        //使用硬件信息拼湊出來的15位號碼
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    }
    複製代碼

<<編譯構建篇>>

  • travis-ci 高版本androidO編譯遇到 license 沒經過編譯失敗的解決方案

    參考 CI 討論區 添加 dist: precisebefore_install 項中新增 sdkmanager指令。具體可參考個人 開源項目配置

  • Dalvik 支持的 android 版本下進行分包執行會有一些限制

    • 冷啓動時須要安裝dex文件,若是dex文件太大則可能致使處理時間太長致使 ANR
    • 即便使用 multiDex 方案在低於 4.0 系統上可能會出現 Dalvik linearAlloc 的bug,這是由於該方案須要申請一個很大的內存,運行時可能的致使程序崩潰。這個限制在 4.0 上雖然有所改善了,可是仍是可能在低於 5.0 的機器上觸發。
  • Dalvik 分包構建每個 dex 文件時可能出現 java.lang.NoClassDefFoundError

    這個問題的緣由是構建工具繪製行比較複雜決策來肯定主 dex 文件中須要的類以便應用可以正常的啓動。若是啓動期間須要的任何類在主 dex 中未能找到,則會拋出上述異常。全部必需要 multiDexKeepFile 或 multiDexKeepProguard 屬性中聲明他們,手動將這些類指定爲主 dex 文件中的必需項。

    建立 multidex-new.txt文件,寫入如下新增的類

    com/example/Main2.class
    com/example/Main3.class
    複製代碼

    建立 meltidex-new.pro,寫入如下 keep 住的類

    -keep class com.example.Main2
    -keep class com.example.Main3
    複製代碼

    而後在gradle multiDexKeepFile屬性 和 multiDexKeepProguard屬性聲明上述文件

    android {
    buildTypes {
        release {
            multiDexKeepFile file 'multidex-new.txt'
            multiDexKeepProguard 'multidex-new.pro'
            ...
        }
    }
    }
    複製代碼
  • Java 8 methods of java.lang.Long and java.lang.Character are not desugared by D8

    這個問題出如今使用 Kotlin 編譯時,從 Kotlin1.3.30 版本開始 ndroid.compileOptions中的Java版本推斷出JVM目標,若是同時設置了sourceCompatibility和targetCompatibility,則選擇「1.8」到那個或更高. 能夠經過指定 JavaVersion 1.6 來解決這個問題。

    sourceCompatibility JavaVersion.VERSION_1_6
    targetCompatibility JavaVersion.VERSION_1_6
    複製代碼

    Issue 中表示,AGP(Android Gradle Plugin)3.4 已解決脫糖問題,可嘗試升級解決。

  • databinding 中 findBinding vs getBinding 的場景區別

    不一樣之處在於,findBinding將遍歷父節點,而若是使用getBinding時當view不是跟節點會返回null。

  • 版本構建出現 Gradle sync failed: Cannot choose between the following configurations of project

    參考 issues 的回答

    If you're using Android plugin for Gradle 3.0.0 or higher, the plugin automatically matches each variant of your app with corresponding variants of its local library module dependencies for you. That is, you should no longer target specific variants of local module dependencies, show as below

    dependencies {
    // Adds the 'debug' varaint of the library to the debug varaint of the app
    debugCompile project(path: ':my-library-module', configuration: 'debug')
    
    // Adds the 'release' varaint of the library to the release varaint of the app
    releaseCompile project(path: ':my-library-module', configuration: 'release')
    }	
    
    複製代碼
  • gradle 配置本地離線包

    1. 離線下載 gradle 離線包保存在 url 中
    2. 修改 gradle/wrapper/gradle-wrapper.properties 調整 distributionUrl 目錄指向 url
    3. 修改 build.gradle classpath 版本映射 gradle 版本
  • 解決kvm/jvm 編譯時 -classpath 遇到的分割及空格的問題

    linux/mac OS 上使用 「:」 分割多個classpath路徑,window使用 「;」 分割。

    若是linux/mac OS 路徑存在空格,暫時避免,使用多種方式嘗試未果=。=。

  • databinding NoSuchMethodError with buildTool 3.4.0

    項目從 gradle 3.1 升級到 3.4.0 並使用了 androidx 以後,發現編譯失敗了。來項目就是使用 databinding,編譯出現了
    java.lang.NoSuchMethodError: No direct method <init>
    (Landroidx/databinding/DataBindingComponent;Landroid/view/View;I)V in 
    class Landroidx/databinding/ViewDataBinding; or its super classes
    (declaration of 'androidx.databinding.ViewDataBinding'
    複製代碼

    緣由咱們使用的 aar 庫中使用了舊版本 gradle 編譯,新版本主端 gradle 升級了,致使舊的 ViewDataBinding 構造器簽名匹配不上新版 androidx.databinding.ViewDataBinding 的簽名。

    //舊版本
    protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount)
    //新版本
    protected ViewDataBinding(Object bindingComponent, View root, int localFieldCount)
    複製代碼

    幸運的是,3.4.1已經修復了。更改 3.4.0 -> 3.4.1 就能夠了。

  • AS鏈接真機調試出現 debug info can be unavailabe 的解決方法

    在使用 AS 鏈接華爲真機調試的時候,IDE 一直出現 「Warning: debug info can be unavailable. Please close other application using ADB: Restart ADB integration and try again」 的錯誤提示。 重啓 ADB 無數遍和關閉除 IDE 意外可能鏈接 ADB 的軟件都無效,最終重啓真機解決。緣由是ADB鏈接的問題,由於有時ADB會在真實/虛擬設備上緩存一個無效的鏈接,而且因爲該鏈接繁忙致使也沒法鏈接到該設備。

  • Mac 上清除 gradle 緩存的項目代理

    有時項目申明瞭代理或者 AS 配置了代理以後再清除代理。發現項目 run 的時候依然會跑到代理上去。緣由是本地 gradle 目錄下也緩存了一份代理信息。須要完全刪除。

    mac : ~/.gradle/gradle.properties 刪除對應代理信息就能夠了。
    其餘 OS:可參考這個思路找到對應文件刪除便可
    複製代碼

<<版本控制篇>>

  • git 修改 commit 記錄

    git reset --soft HEAD^
    
    撤銷當前的commit,若是隻是修改提示,則使用
    git commit --amend
    複製代碼
  • 解決git ignore 文件不生效的問題

    git rm -r --cached .
    git add .
    git commit -m 'update .gitignore'
    複製代碼

<<其餘>>

  • ExoPlayer在接聽電話以後會致使原來設置的 Source 中靜音狀態消失了致使可能返回 app 續播的時候視頻忽然有聲音

    緣由是多媒體焦點被通話搶奪以後播放音量被充值,解決方法可參考 github.com/google/ExoP…

  • AndroidStudio 提示 Please select Android SDK

    解決手段: File->Project Structure中修改Build tools version

  • 關於 Java 中字符與字節的編碼關係認識

    // 1
        Log.d("編碼測試-字符","a".length.toString())
        // 1
        Log.d("編碼測試-字符","測".length.toString())
        // 5
        Log.d("編碼測試-字符","測試abc".length.toString())
    
        // 1
        Log.d("編碼測試-UTF_8","a".toByteArray(Charsets.UTF_8).size.toString())
        // 3
        Log.d("編碼測試-UTF_8","測".toByteArray(Charsets.UTF_8).size.toString())
        // 9 ,UTF_8 支持使用 1,2,3,4個字節進行編碼,一箇中文佔3個字節,一個英文佔1個字節
        Log.d("編碼測試-UTF_8","測試abc".toByteArray(Charsets.UTF_8).size.toString())
    
        // 1
        Log.d("編碼測試-US_ASCII","a".toByteArray(Charsets.US_ASCII).size.toString())
        // 1
        Log.d("編碼測試-US_ASCII","測".toByteArray(Charsets.US_ASCII).size.toString())
        // 5,一箇中文佔1個字節,一個英文佔1個字節
        Log.d("編碼測試-US_ASCII","測試abc".toByteArray(Charsets.US_ASCII).size.toString())
    
        // 1
        Log.d("編碼測試-ISO_8859_1","a".toByteArray(Charsets.ISO_8859_1).size.toString())
        // 1
        Log.d("編碼測試-ISO_8859_1","測".toByteArray(Charsets.ISO_8859_1).size.toString())
        // 5,一箇中文佔1個字節,一個英文佔1個字節
        Log.d("編碼測試-ISO_8859_1","測試abc".toByteArray(Charsets.ISO_8859_1).size.toString())
    
        // 4,存在代理對 +2個字節
        Log.d("編碼測試-UTF_16","a".toByteArray(Charsets.UTF_16).size.toString())
        // 4,存在代理對 +2個字節
        Log.d("編碼測試-UTF_16","測".toByteArray(Charsets.UTF_16).size.toString())
        // 12,UTF_16只支持2或者4個字節編碼,一箇中文佔2個字節,一個英文佔2個字節
        Log.d("編碼測試-UTF_16","測試abc".toByteArray(Charsets.UTF_16).size.toString())
    
        // 2
        Log.d("編碼測試-UTF_16BE","a".toByteArray(Charsets.UTF_16BE).size.toString())
        // 2
        Log.d("編碼測試-UTF_16BE","測".toByteArray(Charsets.UTF_16BE).size.toString())
        // 10,一箇中文佔2個字節,一個英文佔2個字節
        Log.d("編碼測試-UTF_16BE","測試abc".toByteArray(Charsets.UTF_16BE).size.toString())
    
        // 2
        Log.d("編碼測試-UTF_16LE","a".toByteArray(Charsets.UTF_16LE).size.toString())
        // 2
        Log.d("編碼測試-UTF_16LE","測".toByteArray(Charsets.UTF_16LE).size.toString())
        // 10,一箇中文佔2個字節,一個英文佔2個字節
        Log.d("編碼測試-UTF_16LE","測試abc".toByteArray(Charsets.UTF_16LE).size.toString())
    
    
        // 8
        Log.d("編碼測試-UTF_32","a".toByteArray(Charsets.UTF_32).size.toString())
        // 8
        Log.d("編碼測試-UTF_32","測".toByteArray(Charsets.UTF_32).size.toString())
        // 24 utf-32支持 4個字節編碼,同 utf-16 原理,一箇中文佔4個字節,一個英文佔4個字節
        Log.d("編碼測試-UTF_32","測試abc".toByteArray(Charsets.UTF_32).size.toString())
    
        // 4
        Log.d("編碼測試-UTF_32LE","a".toByteArray(Charsets.UTF_32LE).size.toString())
        // 4
        Log.d("編碼測試-UTF_32LE","測".toByteArray(Charsets.UTF_32LE).size.toString())
        //20,一箇中文佔4個字節,一個英文佔4個字節
        Log.d("編碼測試-UTF_32LE","測試abc".toByteArray(Charsets.UTF_32LE).size.toString())
    
        // 4
        Log.d("編碼測試-UTF_32BE","a".toByteArray(Charsets.UTF_32BE).size.toString())
        // 4
        Log.d("編碼測試-UTF_32BE","測".toByteArray(Charsets.UTF_32BE).size.toString())
        // 20,一箇中文佔4個字節,一個英文佔4個字節
        Log.d("編碼測試-UTF_32BE","測試abc".toByteArray(Charsets.UTF_32BE).size.toString())
    複製代碼
  • 關於 emoji 編碼的長度計算問題

    重點熟悉下 Unicode 編碼標識的 emoji 下針對多平面 emoji 的拆分邏輯。可參考這篇文章

  • 不要再使用 System.currentTimeMillis()做爲業務場景的時刻記錄

    咱們業務代碼中曾出現過 「爲了防止某個頁面在短期內重複進入,用 System.currentTimeMillis() 爲了記錄了上一次進入頁面的時刻」。結果有個用戶修改了系統時間以後,發現沒法二次進入這個頁面,緣由是系統時間修改致使二次進去時 System.currentTimeMillis() 與上次記錄的時間作差結果出現了極大的偏差。因此,推薦如下方法。

    SystemClock.elapsedRealtime();
    //別再用如下方法
    System.currentTimeMillis()
    複製代碼

    想了解更細節的緣由可查看 Don't Use System.currentTimeMillis() for Time Interval

關於更細節的面試經歷,可參考 這些年,我所經歷的面試|寫給疫情下的應屆生及求職者 一文。

相關文章
相關標籤/搜索