上一篇:android日记(九)
1.Math.abs()一定返回正数吗?
- int型范围 -2^31 ~ 2^31 – 1 ,也就是 -2147483648 ~ 2147483647。
- 通常来说一个负int整数,经过Math.abs()后,会得到相应的正整数。
- 但是对于-2147483648就比较特殊,因为在int范围内,不存在2147483648的正数。当最小负数加绝对值后,已经超过了最大的正数。
- 实际上,Math.abs(-2147483648) = -2147483648;
2.Release包如何调试?
官方文档:android:debuggable
是否可以调试应用(即使在处于用户模式的设备上运行时)。如果可以调试,则设为"true"
;如果无法调试,则设为"false"
。默认值为"false"
。- 默认地,debug默认debuggable=true,release默认debuggable=false。
- 可以配置Release包的debuggable也为true,重新打个包后,便可以调试了,有两种配置方法。
buildTypes { release { debuggable true } }
or
<application xmlns:tools="http://schemas.android.com/tools" android:debuggable="true"></application>
- Release下的WebView也可以在Chorme devTools中调试,设置方式如下,
WebView.setHorizontalScrollBarEnabled(true)
- 也可以通过对手机系统root,或者刷机后,修改系统的ro.debuggable为1,调试Release。
3.Android签名V1和V2
- 在打签名包时,签名有V1和V2两个版本。其中,V1(Jar Signature)只对jar包进行签名检验,V2(Full APK Signature)对整个apk都进行签名校验,更加安全。
-
由于V2版本的签名在Android7.0才开始支持,对7.0以下将无法安装。因此当app的minSdkVersion需要兼容7.0以下设备时,不能只采用V2签名;换言之,如果app无需兼容7.0以下,则应当选择V2签名,确保签名包完全无法被更改,更安全。
- 如果只选择V1签名,7.0以下设备不受影响,7.0以上设备也还是能够正常安装,只是不够安全。
- 但当targetSdk升级到android11(API 30)后,仅使用V1签名的apk将无法在android11上安装或更新。
- 一般地,为了兼容7.0以下设备,打包时,同时选择V1和V2签名,所有机型都能安装,还能保证7.0及以上的安全。
4.通过adb shell命令dump app的信息
- 手机里安装了某个app,有时候我们想知道该app的一些信息,比如版本号、唤起协议、签名版本、targetSdk版本等,就可以通过adb shell 命令获取。
- 完整命令如下
adb shell dumpsys package <package_name> //获取全部信息 adb shell dumpsys package <package_name> | grep XXX //获取XXX信息
- 在此前之前,需要先知道目标app的包名,打开目标app后,使用下面的命令获得
adb shell dumpsys window | grep mCurrentFocus // 或者 adb shell dumpsys window | grep mFocusedWindows
- 比如查看查看微信,其包名为com.tencent.mm
查看微信的包信息
- 信息太多,可在抓取时搜索过滤。
5.android应用设置里的“清除缓存”与“清除数据”分别清除了什么数据
- app应用数据存储在data/data/app_package_name/
- 清除数据 —— 清除全部应用数据
- 在应用数据目录中,存在cache文件夹
- 清除数据 —— 清除cache文件夹中的数据
6.Android文件缓存目录
- 手机ROM存储空间,包括外部公共目录、外部私有目录和内部目录
- 外部公共目录,路径为/storage/emulated/0,通过getExternalStorageDirectory()访问,app卸载时不会清除
- 外部私有目录,路径为/sdcard/Android/data/app_package_name/,通过getExternalFilesDir()和getExternalCacheDir()访问,app卸载时会清除
- app内部目录,路径为/data/data/app_package_name,通过getCacheDir()和getFilesDirs()访问,app卸载时会清除
- 应用管理中的清除缓存,会清除/data/data/app_package_name/cache目录
- android10(targetSdkVersion=29)新变更,在此之前访问所有存储目录都无需权限,但此之后,访问外部公共目录需要授权。android11后变为强制,未授权访问会导致崩溃。外部存储空间-共享存储空间、外部存储空间-其它目录 App无法通过路径直接访问,不能新建、删除、修改目录/文件等, 需要通过Uri访问
7.Java8的Optional用法
- Optional不能避免空指针问题,但是可以对可能存在的Null值问题做到一种提示
- 使用Optional可以让判空变得优雅,使用if判空
if (user != null) { Address address = user.getAddress(); if (address != null) { Country country = address.getCountry(); if (country != null) { String isocode = country.getIsocode(); if (isocode != null) { isocode = isocode.toUpperCase(); } } } }
使用Optional简化
String nullName = null; String name = Optional.ofNullable(nullName).orElse("default_name");
8.Java内部类引入外部局部变量为何必须是final修饰
- final本身只是一个语法层面的修饰符,在编译后的字节码中没有任何意义
- 内部类,包括匿名内部类,引用外部变量时,分两种情况。
- 一是:外部变量是外部类的全局变量,内部类实际上持有外部类的引用,从而能够正常引用和操作外部类的全部变量。
int index = 1; protected void onResume() { super.onResume(); backImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { index ++;//编译无误 } }); }
- 二是:外部变量是外部类方法的局部变量,外部类方法里的局部变量在方法执行完时,方法栈的生命就结束了。那为什么内部类里还能引用到这个局部变量呢?这是因为局部变量被“拷贝”了一份到内部类中。
protected void onResume() { super.onResume(); int index = 1; backImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { index ++;//编译不过 } }); }
- 正是因为这种拷贝机制的存在,内部类里操作的局部变量,和外部方法里的局部变量,只是表面看着一样,实则不是同一个变量。那就导致了,内部类里对局部变量的修改,不会同步到外部方法的变量。java设计者为了避免这种“无效修改”对开发人员造成困惑,从而在语法层面规定,内部类里只能引用外部方法中被final修饰过的局部变量,杜绝修改。
9.Kotlin内部类引用外部局部变量并修改的原理
- 在java中,内部类只能引用外部final的局部变量,但是kotlin内部类中却可以引用和修改外部var修饰的局部变量。
- 这并不是局部变量方法栈的生命周期发生了变化,而是内部类对外部局部变量的引用发生了变化。
- kotlin会将内部类用到的外部类局部变量自动进行一层包装,包装类对象引用是final修饰的,局部变量做为包装类的一个成员属性,将包装类引用传到内部类中,内部类中便可以随意修改包装类的这个成员属性,而不改变包装类引用本身。例如,
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) var index = 1 mapView.setOnClickListener { index ++ } }
反编译成java实现,可以看到,外部局部变量index,在内部类中的实际引用是IntRef包装类引用。
public void onViewCreated(@NotNull android.view.View view, @Nullable Bundle savedInstanceState) { Intrinsics.checkNotNullParameter(view, "view"); super.onViewCreated(view, savedInstanceState); final IntRef index = new IntRef(); index.element = 1; CtripUnitedMapView var10000 = this.mapView; if (var10000 == null) { Intrinsics.throwUninitializedPropertyAccessException("mapView"); } var10000.setOnClickListener((OnClickListener)(new OnClickListener() { public final void onClick(android.view.View it) { int var10001 = index.element++; } })); }
10.kotlin内联函数let、with、run、apply
- let函数,本质是当前对象的一个扩展函数。
-
with函数,本质不是扩展函数,而是定义了一个函数,并将当前对象做为函数的参数。
override fun onBindViewHolder(holder: ViewHolder, position: Int){ val item = getItem(position)?: return with(item){ holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn) holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary) holder.tvExtraInf.text = "难度:$gradeInfo | 单词数:$length | 读后感: $numReviews" ... } }
- run函数,相当于let与with的结合,是当前对象的扩展函数,并将当前对象做为参数传入
val user = User("Kotlin", 1, "1111111") val result = user.run { println("my name is $name, I am $age years old, my phone number is $phoneNum") 1000 }
- apply函数,结构与run函数一样,只是返回值不同。run函数返回的是必包最后一行的执行结果,而apply函数返回的对象本身。
mapContainer.addView(FrameLayout(this).apply { layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) addView(initMapView(), ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) })
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280183.html