Android开发

android日记(十)

本文主要是介绍android日记(十),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

上一篇: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)
    })

     

这篇关于android日记(十)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!