void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } 复制代码
在执行onCreate的时候这个判断并无执行到html
在 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);
复制代码
<item name="android:windowIsFloating">true</item> 复制代码
此时 window 为 wrap_content,若是出现左右空白,则考虑使用上个问题的方案。linux
在线上项目中咱们遇到一个场景:当应用按下 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 } } 复制代码
出现 TransactionTooLargeException
异常时,由于线上咱们使用了 FragmentStatePagerAdapter 做为 fragment 适配器为了尽量过缓存下浏览过的 fragment 以得到更好的体验,承载多个 FragmentStatePagerAdapter#saveState 会被调用并对每个 fragment 的 bundle 数据进行保存。因为咱们的 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
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
复制代码
缘由是在构建 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; } 复制代码
新版本已不存在这个问题。
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 onTouchEvent 和 onInterceptTouchEvent 并返回false,不处理任何滑动事件
@Override public boolean onTouchEvent(MotionEvent arg0) { return false; } @Override public boolean onInterceptTouchEvent(MotionEvent arg0) { return false; } 复制代码
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) { } }); 复制代码
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); } 复制代码
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中,添加如下样式定义
style="?android:attr/borderlessButtonStyle" 复制代码
前者在按下并抬起时发生,后者有一个附加条件时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)
确认最终的排版信息。
在大神信息流中,有一些卡片信息须要设置单行缩略。在 MTL 兼容测试过程当中发现有一些机型显示异常,通过概括及校验,这部分机型的版本都是 < 6.0。 经过在 stackoverflow 也找到了相同的问题场景 text ellipsize behavior in android version < 6.0 . 针对这部分版本的手机,咱们须要在设置单行的时候把 android:maxLines="1"
改为 android:singleLine="true"
。即便 IDE 提示该 API 已通过期了!
android:focusable="true" android:focusableInTouchMode="true" 复制代码
从 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); } 复制代码
因为咱们在优化 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" /> 复制代码
@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。
通常而言,若是要在多线程环境下使用数据库,则确保多个线程中使用的是同一个SQLiteDataBase对象,该对象对应一个db文件。
特殊状况,若是多个 SQLiteDataBase 打开同一个 db 文件,同时使用不一样线程同时写(insert,update,exexSQL)会致使在 SQLiteStatement.native_execute
方法时可能致使异常。这个异常来自本地方法里面,仅仅在Java对有对 SQLiteDataBase 进行同步锁保护。可是多线程读(query)返回的事 SQLiteCursor保存查询条件并无马上执行查询,仅仅在须要时加载部分数据,能够多线程不一样 SQLiteDataBase 进行读。
若是要处理上述问题,可使用 “一个线程写,多个线程同时读,每一个线程都用各自SQLiteOpenHelper。”
在android 3.0版本以上 打开 enableWriteAheadLogging。当打开时,它容许一个写线程与多个读线程同时在一个SQLiteDatabase上起做用。实现原理是写操做实际上是在一个单独的文件,不是原数据库文件。因此写在执行时,不会影响读操做,读操做读的是原数据文件,是写操做开始以前的内容。在写操做执行成功后,会把修改合并会原数据库文件。此时读操做才能读到修改后的内容。可是这样将花费更多的内存。
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"/> 复制代码
或者使用 android:maxAspectRatio="ratio_float"(API LEVEL 26)
ratio_float 通常为屏幕分辨率高宽比。 其余好比凹槽区域,圆角切割等问题能够参考市面上最新的vivo机型对应的 vivo开放平台 文档。
因为部分华为中,若是app注册超过500个BroadcastReceiver就会抛出 “ Register too many Broadcast Receivers ” 异常。经过分析发现其内部有一个白名单,本身能够经过建立一个新的app,使用微信包名进行测试,发现并无这个限制。经过反射 LoadedApk 类拿到 mReceiverResource 中的 mWhiteList 对象添加咱们的包名就能够了。 能够参考 github.com/llew2011/Hu… 这个项目。
在咱们项目中首页曾经有个控件叫 “AdView” 用来显示广告用的,离谱的时这个 View 在咱们 setVisibility() 的时候华为手机上又调用了一次 setVisibility(View.GONE). 后面更改 view 的名称解决了这个问题。这个问题应该是由于 ROM 内部经过必定的规则来判断咱们的应用中视图 View 是否为广告视图进而强制隐藏致使的。因此在写 App 显示广告相关的视图时,命名尽可能隐晦一点哈。
参考 网易考拉实现的适配方法
今天收到魅族渠道的警报称“推送内容可能过长”。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; } 复制代码
因为咱们的应用LauncherActivity用于分发不一样场景的入口,A逻辑进入特殊场景页面A,B逻辑进入主页面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(); } 复制代码
可参考我另外一篇文章 对线上项目拉起应用场景的思考总结
因为咱们项目须要处理沉浸式,因此针对 android:windowIsTranslucent 的属性默认打开的。可是线上发现部分 8.0设备出现诡异的 crash,缘由是咱们对于页面的 orientation 申明都统一为 portrait 。查阅 android 源码的更新发如今 8.0 源码的逻辑里面这两个逻辑居然不兼容,随后在 8.0 版本后谷歌进行了修复。可是国内部分 ROM 看起来并无修复这个问题。后面同事提供了一个比较取巧的方案,经过为页面指定 android:screenOrientation="behind" 来避免 8.0 版本的问题同时兼容全部 android 版本。
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> 复制代码
参考 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(); } 复制代码
参考 CI 讨论区 添加 dist: precise
及 before_install 项中新增 sdkmanager指令
。具体可参考个人 开源项目配置
这个问题的缘由是构建工具绘制行比较复杂决策来肯定主 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 复制代码
Issue 中表示,AGP(Android Gradle Plugin)3.4 已解决脱糖问题,可尝试升级解决。
不一样之处在于,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') } 复制代码
linux/mac OS 上使用 “:” 分割多个classpath路径,window使用 “;” 分割。
若是linux/mac OS 路径存在空格,暂时避免,使用多种方式尝试未果=。=。
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 链接华为真机调试的时候,IDE 一直出现 “Warning: debug info can be unavailable. Please close other application using ADB: Restart ADB integration and try again” 的错误提示。 重启 ADB 无数遍和关闭除 IDE 意外可能链接 ADB 的软件都无效,最终重启真机解决。缘由是ADB链接的问题,由于有时ADB会在真实/虚拟设备上缓存一个无效的链接,而且因为该链接繁忙致使也没法链接到该设备。
有时项目申明了代理或者 AS 配置了代理以后再清除代理。发现项目 run 的时候依然会跑到代理上去。缘由是本地 gradle 目录下也缓存了一份代理信息。须要完全删除。
mac : ~/.gradle/gradle.properties 删除对应代理信息就能够了。
其余 OS:可参考这个思路找到对应文件删除便可
复制代码
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
// 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()) 复制代码
重点熟悉下 Unicode 编码标识的 emoji 下针对多平面 emoji 的拆分逻辑。可参考这篇文章
咱们业务代码中曾出现过 “为了防止某个页面在短期内重复进入,用 System.currentTimeMillis()
为了记录了上一次进入页面的时刻”。结果有个用户修改了系统时间以后,发现没法二次进入这个页面,缘由是系统时间修改致使二次进去时 System.currentTimeMillis()
与上次记录的时间作差结果出现了极大的偏差。因此,推荐如下方法。
SystemClock.elapsedRealtime();
//别再用如下方法
System.currentTimeMillis()
复制代码
想了解更细节的缘由可查看 Don't Use System.currentTimeMillis() for Time Interval
关于更细节的面试经历,可参考 这些年,我所经历的面试|写给疫情下的应届生及求职者 一文。