視圖篇java
服務篇linux
線程篇android
網絡篇git
數據篇github
機型系統適配篇sql
編譯構建篇數據庫
版本控制篇後端
其餘安全
谷歌在 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的時候這個判斷並無執行到
在 fragment#onResume 中從新調整 window 佈局
<item name="android:windowIsFloating">true</item>
此時 window 爲 wrap_content,若是出現左右空白,則考慮使用上個問題的方案。
notifyItemRemoved
方法並不會移除列表的數據源的數據項致使數據源中的數據與列表Item數目不一致,須要同步刷新數據源。由於recyclerview存在ItemAnimator,且在刪除/更新/插入Item時會觸發,可設置不支持該動畫便可。
((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
這個問題通過定位存在於 viewholder 中的某個 view 可能提早獲取到焦點。 同時在。alibaba-vlayout 庫中也發現有人反饋改問題。 issues-255 解決的方法是在 Recyclerview中外層父佈局中添加 android:descendantFocusability="blocksDescendants" 用於父佈局覆蓋 Recyclerview 優先搶佔焦點。
緣由是在 findOneVisibleChild 計算出來的 start 和 end 已經超過了 reclclerview 的 start 和 end.通過研究源碼獲得如下。
findFirstCompletelyVisibleItemPosition -> -1 findLastCompletelyVisibleItemPosition -> -1 findFirstVisibleItemPosition -> 正常 findLast
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());
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.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監聽未獲取焦點的Edittext的點擊事件,第一次點擊觸發OnFocusChangeListener,在獲取焦點的狀況下才能響應onClickListener
在xml佈局中對listview或gridview設置Android:choiceMode="singleChoice",並使用state_activated狀態來代替state_selected狀態。(2016.12.10)
在xml定義的Button中,添加如下樣式定義
前者在按下並擡起時發生,後者有一個附加條件時Android會確保點擊以後在短期內沒有再次點擊纔會觸發。經常使用於若是須要監聽單擊和雙擊事件。
<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>
在大神 app 信息流快捷評論模塊中,在交付快捷評論動畫的時候發現,使用屬性動畫實現的抖動效果在部分機型上出現閃爍。而咱們的實現抖動效果是經過 View.ROTATION
來實現的。通過研究,部分機型由於硬件加速的緣由致使的。爲動畫 view 進行如下設置
view.setLayerType(View.LAYER_TYPE_HARDWARE,null);
不一樣於其餘 ViewGroup 控制子 View 的排版,ConstraintLayout 須要構建 ConstraintSet
對象來粘合。 在手動添加子 View 的場景下,能夠經過 ConstraintSet#clone(ConstraintLayout constraintLayout)
來克隆當前已有 ConstraintLayout 的排版信息,而後最後調用 ConstraintSet#applyTo(ConstraintLayout constraintLayout)
確認最終的排版信息。
TextView 在 6.0 版本下設置單行尾部縮略的坑
android:maxLines="1"
改爲 android:singleLine="true"
。即便 IDE 提示該 API 已通過期了!從 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); }
@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) ).
如何優雅處理 sqlite 多線程讀寫問題
通常而言,若是要在多線程環境下使用數據庫,則確保多個線程中使用的是同一個SQLiteDataBase對象,該對象對應一個db文件。
特殊狀況,若是多個 SQLiteDataBase 打開同一個 db 文件,同時使用不一樣線程同時寫(insert,update,exexSQL)會致使在 SQLiteStatement.native_execute 方法時可能致使異常。這個異常來自本地方法裏面,僅僅在Java對有對 SQLiteDataBase 進行同步鎖保護。可是多線程讀(query)返回的事 SQLiteCursor保存查詢條件並無馬上執行查詢,僅僅在須要時加載部分數據,能夠多線程不一樣 SQLiteDataBase 進行讀。
若是要處理上述問題,可使用 「一個線程寫,多個線程同時讀,每一個線程都用各自SQLiteOpenHelper。」
在android 3.0版本以上 打開 enableWriteAheadLogging。當打開時,它容許一個寫線程與多個讀線程同時在一個SQLiteDatabase上起做用。實現原理是寫操做實際上是在一個單獨的文件,不是原數據庫文件。因此寫在執行時,不會影響讀操做,讀操做讀的是原數據文件,是寫操做開始以前的內容。在寫操做執行成功後,會把修改合併會原數據庫文件。此時讀操做才能讀到修改後的內容。可是這樣將花費更多的內存。
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則不能夠,因此須要根據業務來調整。
popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
參考 github.com/zhitaocai/T… 項目,可是在小米3或者小米Note(4.4.4)手機上
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mContext 須要使用 ApplicationContext 才能生效。
因爲谷歌把 Toast 設置爲系統消息權限,能夠參考 Android關閉通知消息權限沒法彈出Toast的解決方案 維護本身的消息隊列.
在 AndroidManifest.xml 中聲明一下 meta-data
<meta-data android:name="andriod.max_aspect" android:value="ratio_float"/>
因爲部分華爲中,若是app註冊超過500個BroadcastReceiver就會拋出 「 Register too many Broadcast Receivers 」 異常。經過分析發現其內部有一個白名單,本身能夠經過建立一個新的app,使用微信包名進行測試,發現並無這個限制。經過反射 LoadedApk 類拿到 mReceiverResource 中的 mWhiteList 對象添加咱們的包名就能夠了。 能夠參考 github.com/llew2011/Hu… 這個項目。
參考 網易考拉實現的適配方法
今天收到魅族渠道的警報稱「推送內容可能過長」。IM功能針對離線設備走設備商的推送,魅族推送限制了title標題1-32字符,content內容1-100字符。若是頻繁推送超過限制的通知,魅族推送服務器可能不會下發推送到魅族設備。故服務端限制發送到魅族服務器的消息標題和內容長度解決。
判斷啓動頁面是不是根節點(推薦)
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。
if (後端控制是否須要進入特殊場景頁面) { boolean goToA = false; if (getIntent() != null) { String action = getIntent().getAction(); Set 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(); }
可參考我另外一篇文章 對線上項目拉起應用場景的思考總結
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>
參考 CI 討論區 添加 dist: precise 及 before_install 項中新增 sdkmanager指令。具體可參考個人 開源項目配置
Dalvik 支持的 android 版本下進行分包執行會有一些限制
這個問題的緣由是構建工具繪製行比較複雜決策來肯定主 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' ... } } }
這個問題出如今使用 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
不一樣之處在於,findBinding將遍歷父節點,而若是使用getBinding時當view不是跟節點會返回null。
參考 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 配置本地離線包
linux/mac OS 上使用 「:」 分割多個classpath路徑,window使用 「;」 分割。
若是linux/mac OS 路徑存在空格,暫時避免,使用多種方式嘗試未果=。=。
項目從 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)
git reset --soft HEAD^ 撤銷當前的commit,若是隻是修改提示,則使用 git commit --amend
git rm -r --cached . git add . git commit -m 'update .gitignore'
緣由是多媒體焦點被通話搶奪以後播放音量被充值,解決方法可參考 github.com/google/ExoP…
解決手段: File->Project Structure中修改Build tools version
unicode編碼下1箇中文字符或英文字符都佔2個字節; GBK編碼下測試: 「java測試」.length() 返回 6,「GBK測試」.getBytes().length() 返回 8 總結:1箇中文字符佔2個字節 1個英文字符佔1個字節 utf-8編碼下測試: 「java測試」.length() 返回 6,「GBK測試」.getBytes().length() 返回 10 總結:1箇中文字符佔3個字節 1個英文字符佔1個字節 utf-16編碼下測試: 「java測試」.length() 返回 6,「GBK測試」.getBytes().length() 返回 14 總結:1箇中文字符佔3個字節 1個英文字符佔2個字節 utf-32編碼下測試: 「java測試」.length() 返回 6,「GBK測試」.getBytes().length() 返回 24 總結:1箇中文字符或英文字符都佔4個字節
<h4 id="9-4"> 關於 emoji 編碼的長度計算問題 </h4>
重點熟悉下 Unicode 編碼標識的 emoji 下針對多平面 emoji 的拆分邏輯。可參考這篇文章