Android知識點

  • 2020 年 3 月 27 日
  • 筆記

開發tip

  • 用getApplication()來取Context當參數 對於需要使用Context對象作為參數的函數,要使用getApplication()獲取Context對象當參數,而不要使用this,除非你需要特定的組件實例!getApplication()返回的Context是屬於Application的,它會在整個應用的生命周期記憶體在,遠大於某個組件的生命周期,所以即使某個引用長期持有Context對象也不會引發記憶體泄露。比如toast
  • 或許我們較為習慣 if(object == null); 這種寫法,對於直接對象的判斷自然是沒有問題的。但是如果遇到對象嵌套,譬如 if(object.child == null) 的情況,如果一開始 object 就是個 null 對象,可想而知程式在執行條件語句判斷時就會報 NullPointerException 異常。當然你可以先判斷 object 再判斷 child,但與其這樣不如一開始就習慣 if(null == object.child); 來得更方便些,equals() 同理。
  • 頻繁操作的文件建議考慮使用緩衝提高程式性能,intent不要傳遞大量數據,使用持久化數據來處理(存在文件中),不要在application中快取數據
  • 在涉及網路的操作中,必須要考慮到沒有網路和訊號差等情況
  • 一些華為機器log和Toast列印一直異常,不要在這上面耽誤時間
  • 程式碼至少每天備份一次,或者是完善一個功能就備份一次,不要堆積之後一次性備份,因為在你的程式碼出問題需要回溯程式碼時你需要從伺服器上重新取程式碼,同時也可以避免程式碼不是最新導致最後和其他人合併時不知道改了哪些地方
  • 注意:有些開發者可能也發現了,如果我們需要一個圓形的ImageButton的話,其實,我們沒有必要自己寫。如果ImageButton的圖標是固定不變的,我們完全可以讓設計師給我設計一個圓形的圖片,然後直接設置再ImageButton上就可以了。
  • 同步方法:SDK里大部分方法都為同步方法,即這個方法執行完畢,才會走後面的程式碼。非同步方法:帶有callback以及api注釋里明確寫明非同步方法的方法,即不需要等這個方法走完,後邊的程式碼就已經在執行了,通過callback得到方法執行的結果。
  • 不少比較有經驗的Android程式設計師可能都遇到過這個情況,就是當你的項目變得越來越大,有的時候載入一張drawable-hdpi下的圖片,程式就直接OOM崩掉了,但如果將這張圖放到drawable-xhdpi或drawable-xxhdpi下就不會崩掉,其實就是這個道理。那麼經過上面一系列的分析,答案自然也就出來了,圖片資源應該盡量放在高密度文件夾下,這樣可以節省圖片的記憶體開支,而UI在設計圖片的時候也應該盡量面向高密度螢幕的設備來進行設計。就目前來講,最佳放置圖片資源的文件夾就是drawable-xxhdpi。那麼有的朋友可能會問了,不是還有更高密度的drawable-xxxhdpi嗎?幹嗎不放在這裡?這是因為,市面上480dpi到640dpi的設備實在是太少了,如果針對這種級別的螢幕密度來設計圖片,圖片在不縮放的情況下本身就已經很大了,基本也起不到節省記憶體開支的作用了。
  • 不能在 Activity 沒有完全顯示時顯示PopupWindow和Dialog。例如在activity的onCreate方法裡面調用popupwindow的show方法,有可能由於activity沒有完全初始化導致程式異常(android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid),如果非要在一進activity就顯示popupwindow,用handler.post、View.postDelay來處理
  • 所有的圖片務必先用http://tinypng.com壓一遍用
  • JPG 代替 PNG,由於 JPG 沒有 Alpha 通道,所以文件更小,適用於不需要透明度的圖片可以考慮。
  • 不同Module的資源文件、布局名不要相同
  • 需要管理相互獨立的並且隸屬於Activity的Fragment使用FragmentManager(),而在Fragment中動態的添加Fragment要使用getChildFragmetManager()來管理,而不是getFragmentManager。
  • 在ScrollView中添加一個Android:fillViewport="true"屬性就可以了。顧名思義,這個屬性允許 ScrollView中的組件去充滿它。 當ScrollView沒有fillViewport=「true」時, 裡面的元素(比如LinearLayout)會按照wrap_content來計算(不論它是否設了"fill_parent")
  • gson同一欄位,數據類型不同的欄位,類型可以寫object
  • radiobutton布局下面倆個button切換時,fragment里有listview會頂到下面,可以在布局初始化完後lv.setFocusable(false)
  • 有一種情況是手機並不在我們身邊,我們也無法使用調試工具。此時可以接入一些第三方的日誌記錄工具。在開發狀態下不建議使用友盟 360之類sdk,因為很有可能我們的app根本無法連接到網路就崩潰了。 可以選擇把日誌存到本地文件中。再由使用手機的人發回來。一般這個人是測試。如果app未接入任何日誌保存工具,可以在data/anr/目錄下查看到所有的ANR異常資訊。但需要su許可權。否則無法訪問到。
  • 在複雜的布局上,比如很多app的首頁需要載入不同類型的item。使用了RecyclerView多類型載入,刷新數據時一定要使用單獨對item刷新api。切勿使用notifyDataSetChanged()方法,這裡要用兩個參數的notifyItemChanged(1,"gfg")方法。
  • 當無法通過搜索解決問題的時候,讀源碼是最快的解決思路。千萬不要瞎猜和嘗試隨緣寫程式碼來解決問題。
  • .9圖的左上代表可以拉伸的區域,右下代表可以填充的區域
  • 多語言字元串設置要都配置上,要不然就報android.content.res.Resources$NotFoundException
  • 在8.0上切換語言部分文字沒有切換,原因是getResources原來是通過application的,改成activity的就可以了
  • 找不到問題,可以二分注釋程式碼來定位問題
  • TextView.setTextColor方法不能用R.Color.XXX設置顏色

UI問題

一段文字顏色不同點擊不同,不想寫多個textview,可以這樣寫

String html="僅限<font color=#FF9900>"+offer.getCardGroupName()+"</font>使用";  nrbankTextView.setText(Html.fromHtml(html));    

也可以這樣寫

String content = "發現" + newGroup.size() + "張新卡片";  int textColor = getResources().getColor(R.color.import_card_red);  SpannableString spannableString = TextStyleUtils.setTextStyle(content, newGroup.size() + "張", textColor);  tv_.setText(spannableString);

也可以這樣寫:

    private void setCurrentTip() {          StringBuilder actionText = new StringBuilder();          actionText.append("<a style="text-decoration:none;" href='text' >若平台今日快速還款額度用完,還款將於次日到賬n</a>");          actionText.append("<a style="text-decoration:none;" href='text' >查看</a>");          actionText.append("<a style="text-decoration:none;" href='blue' >規則</a>");          tvBankArrivaldesc.setText(Html.fromHtml(actionText.toString()));          tvBankArrivaldesc.setMovementMethod(LinkMovementMethod                  .getInstance());          CharSequence text = tvBankArrivaldesc.getText();          int ends = text.length();          Spannable spannable = (Spannable) tvBankArrivaldesc.getText();          URLSpan[] urlspan = spannable.getSpans(0, ends, URLSpan.class);          SpannableStringBuilder stylesBuilder = new SpannableStringBuilder(text);          stylesBuilder.clearSpans();          for (URLSpan url : urlspan) {              FeedTextViewURLSpan myURLSpan = new FeedTextViewURLSpan(url.getURL(), getActivity());              stylesBuilder.setSpan(myURLSpan, spannable.getSpanStart(url),                      spannable.getSpanEnd(url), spannable.SPAN_EXCLUSIVE_EXCLUSIVE);          }          tvBankArrivaldesc.setText(stylesBuilder);      }          static class FeedTextViewURLSpan extends ClickableSpan {          private String clickString;          private Context context;            public FeedTextViewURLSpan(String clickString, Context context) {              this.clickString = clickString;              this.context = context;            }            @Override          public void updateDrawState(TextPaint ds) {              ds.setUnderlineText(false);//去掉下劃線              //給標記的部分 的文字 添加顏色              if (clickString.equals("blue")) {                  ds.setColor(Color.parseColor("#4D8FDD"));              } else if (clickString.equals("text")) {                  ds.setColor(Color.parseColor("#999999"));              }          }            @Override          public void onClick(View widget) {              Intent intent = new Intent();              // 根據文字的標記 來進行相應的 響應事件              if (clickString.equals("blue")) {                  ToastUtils.show(context, "規則");              }          }      }

想每次進入頁面刷新,可以放在onResume方法里(不推薦) 顏色透明度:#7f000000 代表50%透明度的黑色

ImageView的android:adjustViewBounds屬性 http://blog.csdn.net/pingchuanyang/article/details/9252689

framelayout兩布局重疊,如何讓下層不響應事件 在layout文件里本層下增加Android:clickable="true" 在上層布局的父布局上增加android:clickable="true"

給圖片設置背景,例如ui給的圖背景透明的圖片

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">      <item>          <shape>              <solid android:color="#1c69ac" />              <corners android:radius="5dp"/>          </shape>      </item>      <item          android:left="15dp"          android:right="15dp"          android:top="15dp"          android:bottom="15dp"          android:drawable="@drawable/icon_yingwen_fuhao_keyboard_del_default"           />  </layer-list>

由於listview的復用機制,當給一個控制設置狀態的時候,相應的要給其他控制項也設置狀態(else),否則就會復用

listview如果單獨更新某個控制項的時候,可以更改單個數據的狀態,然後notify

viewpager+fragment

 //介面可見時再載入數據      @Override      public void setUserVisibleHint(boolean isVisibleToUser{          super.setUserVisibleHint(isVisibleToUser);          if (isVisibleToUser) {              mMyWalletPresenter.reqRanking(position);//[2周排行,1總排行]          }      }

CardView 使用android:background設置背景顏色無效。 app:cardBackgroundColor 設置背景顏色

NestedScrollView嵌套RecyclerView滑動卡頓解決方案 如果你APP的API適配的minSdkVersion高於21,直接在RecyclerView中加上android:nestedScrollingEnabled="false"或者禁用recycleview滑動

      //解決滑動滑動衝突          LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false) {              @Override              public boolean canScrollVertically() {                  return false;              }          };

selector異常 Caused by: org.xmlpull.v1.XmlPullParserException: Binary XML file line #3: <item> tag requires a 'drawable' attribute or child tag defining a drawable 產生原因:我的一個button按鈕的background屬性中設置成"@color/button_text_selector",按照異常來說,這個background這個屬性的值必須是drawable類型的,不能是color類型。

<?xml version="1.0" encoding="utf-8"?>  <selector xmlns:android="http://schemas.android.com/apk/res/android">        <!--<item android:color="@color/iqb_ui_base_color_1004" android:state_enabled="true" />-->      <!--<item android:color="@color/iqb_ui_base_color_1005" android:state_enabled="false" />-->      <!--<item android:color="@color/iqb_ui_base_color_1004" />-->        <item android:state_enabled="true">          <shape>              <solid android:color="@color/iqb_ui_base_color_1004" />          </shape>      </item>      <item android:state_enabled="false">          <shape>              <solid android:color="@color/iqb_ui_base_color_1005" />          </shape>      </item>      </selector>

程式碼寫陰影

<?xml version="1.0" encoding="utf-8"?>  <layer-list xmlns:android="http://schemas.android.com/apk/res/android">        <!-- 陰影部分 -->      <!-- 個人覺得更形象的表達:top代表下邊的陰影高度,left代表右邊的陰影寬度。其實也就是相對應的offset,solid中的顏色是陰影的顏色,也可以設置角度等等 -->      <item          android:bottom="2dp"          android:left="2dp"          android:right="2dp"          android:top="2dp">          <shape android:shape="rectangle">                <gradient                  android:angle="270"                  android:endColor="#0F000000"                  android:startColor="#0F000000" />                <corners                  android:bottomLeftRadius="6dip"                  android:bottomRightRadius="6dip"                  android:topLeftRadius="6dip"                  android:topRightRadius="6dip" />          </shape>      </item>        <!-- 背景部分 -->      <!-- 形象的表達:bottom代表背景部分在上邊緣超出陰影的高度,right代表背景部分在左邊超出陰影的寬度(相對應的offset) -->      <item          android:bottom="5dp"          android:left="3dp"          android:right="3dp"          android:top="3dp">          <shape android:shape="rectangle">                <gradient                  android:angle="270"                  android:endColor="#FFFFFF"                  android:startColor="#FFFFFF" />                <corners                  android:bottomLeftRadius="6dip"                  android:bottomRightRadius="6dip"                  android:topLeftRadius="6dip"                  android:topRightRadius="6dip" />          </shape>      </item>  </layer-list>

單選

    public void setClicked(int clicked) {          mapClassfiyAdapter.setClicked(clicked);      }       public void setClicked(int clicked){          this.clicked=clicked;          notifyDataSetChanged();      }        if(i==clicked){              tv_classfiy.setTextColor(mContext.getResources().getColor(R.color.map_pop_click));          } else {              tv_classfiy.setTextColor(mContext.getResources().getColor(R.color.white));          }

多選

  // 用來控制CheckBox的選中狀況      private static HashMap<Integer, Boolean> isSelected;          public MapClassfiyAdapter(Context mContext, List<String> classfiyList) {          this.mContext = mContext;          isSelected = new HashMap<Integer, Boolean>();      }        // 初始化isSelected的數據      private void initDate() {          for (int i = 0; i < classfiyList.size(); i++) {              getIsSelected().put(i, false);          }      }        public  HashMap<Integer, Boolean> getIsSelected() {          return isSelected;      }        public  void setIsSelected(HashMap<Integer, Boolean> isSelected) {          MapClassfiyAdapter.isSelected = isSelected;      }        @Override      public View getView(int i, View convertView, ViewGroup viewGroup) {       ...          if(getIsSelected().get(i)){              tv_classfiy.setTextColor(mContext.getResources().getColor(R.color.map_pop_click));          }else{              tv_classfiy.setTextColor(mContext.getResources().getColor(R.color.white));          }              tv_classfiy.setText(classfiyList.get(i));          }          ---        public void setClicked(int clicked) {          isSelected = mapClassfiyAdapter.getIsSelected();          Boolean aBoolean = isSelected.get(clicked);          if(aBoolean){              isSelected.put(clicked,false);          }else{              isSelected.put(clicked,true);          }          mapClassfiyAdapter.setIsSelected(isSelected);      }

描邊

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">        <item          android:left="-2dp"          android:right="-2dp">          <shape>              <solid android:color="@color/white" />                <stroke                  android:width="1.5dp"                  android:color="#80ffffff" />          </shape>      </item>    </layer-list>    ---    <layer-list      xmlns:android="http://schemas.android.com/apk/res/android">      <item          android:right="-2dp"//負數,不描右邊          >          <shape              xmlns:android="http://schemas.android.com/apk/res/android"              android:shape="rectangle">              <solid                  android:color="#EFEFEF"/>              <corners                  android:topLeftRadius="10dp"                  android:topRightRadius="0dp"                  android:bottomRightRadius="0dp"                  android:bottomLeftRadius="10dp"                  />              <stroke                  android:width="0.5px"                  android:color="#505050"/>          </shape>      </item>  </layer-list>

popWindow點背景消失

   mPopupWindow = new PopupWindow(                      mPopupView,                      ViewGroup.LayoutParams.WRAP_CONTENT,                      ViewGroup.LayoutParams.WRAP_CONTENT);                //產生背景變暗效果,布局背景要改              // 根布局背景android:background="#00ffffff"              backgroundAlpha(0.5f);              mPopupWindow.setOutsideTouchable(true);              mPopupWindow.setFocusable(true);              mPopupWindow.setBackgroundDrawable(new BitmapDrawable());              mPopupWindow.setOnDismissListener(new PoponDismissListener());                mMyHandler.postDelayed(new Runnable() {                  @Override                  public void run() {                      mPopupWindow.showAtLocation(mPopupView, Gravity.CENTER, 0, 0);                  }              }, 200);    ...     class PoponDismissListener implements PopupWindow.OnDismissListener {          @Override          public void onDismiss() {              backgroundAlpha(1f);          }      }        private void backgroundAlpha(float v) {          WindowManager.LayoutParams lp = context.getWindow().getAttributes();          lp.alpha = v; //0.0-1.0          context.getWindow().setAttributes(lp);      }

資料庫升級 新增版本號,然後

  if (newVersion == 4) {//方法一                  TableUtils.dropTable(connectionSource, CardModel.class,                          true);                  TableUtils.createTable(connectionSource, CardModel.class);                  return;              }              if (oldVersion < 5) {//方法二                  getUserInfoDao().executeRaw("ALTER TABLE `USERINFOTABLE` ADD COLUMN memberaccesstoken TEXT  DEFAULT'';");                  getUserInfoDao().executeRaw("ALTER TABLE `USERINFOTABLE` ADD COLUMN membertype INTEGER DEFAULT 0;");                  return;              }

數字後加2個點

DecimalFormat  df = new DecimalFormat("########0.00");  crt_pp_money.setText("¥" + df.format(Double.valueOf(money)));

填充字元串

<string name="crt_shift_limit">銀行單筆限額%1$d萬,單日限額號%2$d萬</string>  bankMessage.setText(getResources().getString(R.string.crt_shift_limit,3,5));

其他問題

webview post webView.postUrl(mUrl,postdata.getBytes());

webview重定向引起的問題 解決方法:

      WebView.HitTestResult hitTestResult = webView.getHitTestResult();          Log.d("hitTestResult",hitTestResult.getType()+"----"+hitTestResult.getExtra());          if (hitTestResult.getType() == WebView.HitTestResult.UNKNOWN_TYPE) {//=0發生重定向              loadUrl();              return true;          }else{}

或者

    @Override      public void onPageFinished(WebView view, String url) {                  super.onPageFinished(view, url);                  mIsPageLoading= false;//重定向是不回執行finished方法的                }       public void onPageStarted(WebView view, String url, Bitmap favicon) {                  super.onPageStarted(view, url, favicon);                  mIsPageLoading= true;              }

webview:顯示404等異常狀態都有回調,可以顯示一個友好的介面 可以通過URL是否包含某個欄位判斷去哪個頁面

try-catch 捕獲異常可以捕獲里return出去

手機適配

String PhoneName = String.valueOf(android.os.Build.MANUFACTURER);  		if (PhoneName.equals("OPPO")) {  			AutoLogin1.setVisibility(View.INVISIBLE);  			AutoLogin2.setVisibility(View.INVISIBLE);  		}

華為等虛擬鍵盤遮擋問題

  //因為某些機型是虛擬按鍵的,所以要加上以下設置防止擋住按鍵.    setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

如果你觸發了一個intent,而且沒有任何一個app會去接收這個intent,那麼你的app會crash。為了驗證是否有合適的activity會響應這個intent,需要執行queryIntentActivities() 來獲取到能夠接收這個intent的所有activity的list。如果返回的List非空,那麼你才可以安全的使用這個intent。例如:

                PackageManager packageManager = getPackageManager();                  List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);                  boolean isIntentSafe = activities.size() > 0;                  if (isIntentSafe) {                      startActivity(mapIntent);                   }

檢查是否有某個許可權

  public static boolean hasPermissions(Context context, String... perms) {          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {              return true;          }            for (String perm : perms) {              //PERMISSION_DENIED              boolean hasPerm = (ContextCompat.checkSelfPermission(context, perm) ==                      PackageManager.PERMISSION_GRANTED);              if (!hasPerm) {                  return false;              }          }            return true;      }   boolean location = hasPermissions(this, Manifest.permission.ACCESS_COARSE_LOCATION);

跳轉到別的APP

  private void doStartApplicationWithPackageName(String packagename) {            Intent intent = new Intent();          PackageInfo packageinfo = null;          PackageManager packageManager = context.getPackageManager();          try {              packageinfo = packageManager.getPackageInfo(packagename, 0);          } catch (PackageManager.NameNotFoundException e) {              e.printStackTrace();          }          if (packageinfo == null) {              intent.setClass(context, NoLianshuaActivity.class);              context.startActivity(intent);              dismiss();              return;          }          intent = new Intent(Intent.ACTION_VIEW,                  Uri.parse("mydemo://go?money=" + money                  + "&isFromMydemo=" + true));      }
<activity android:name=".Main2Activity">      <intent-filter>          <action android:name="android.intent.action.VIEW" />          <category android:name="android.intent.category.DEFAULT" />          <data android:scheme="mydemo" />      </intent-filter>  </activity>

另一個APP

    private void parseIntentUrl() {          Intent intent = getIntent();          String action = intent.getAction();          if (Intent.ACTION_VIEW.equals(action)) {              Uri uri = intent.getData();              if (uri != null) {                  String host = uri.getHost();                  String dataString = intent.getDataString();                  String id = uri.getQueryParameter("goodsId");                  String path = uri.getPath();                  String path1 = uri.getEncodedPath();                  String queryString = uri.getQuery();                  Log.d("Alex", "host:"+host);                  Log.d("Alex", "dataString:" + dataString);                  Log.d("Alex", "id:" + id);                  Log.d("Alex", "path:" + path);                  Log.d("Alex", "path1:" + path1);                  Log.d("Alex", "queryString:" + queryString);              }          }      }

這樣寫bean的欄位,欄位是new_p也能解析 @SerializedName("new_p") private String psw;

ViewPager越界 ViewPager的子控制項個數(getChildCount())最多為3(在destroyItem()中調用了removeView()),如果要獲取ViewPager某個子頁面,千萬不能使用getChildAt(position),會造成數組越界。可以通過這樣來獲取。

@Override  public Object instantiateItem(ViewGroup container, int position) {  	view.setTag(DEFAULT_TAG + position);  	container.addView(view);  	return view;  }    View child = mViewPager.findViewWithTag(DEFAULT_TAG + position));

Tags: