源码准备 aw-image-build.tar.gz 放入共享文件夹 tar xvf aw-image-build.tar.gz -C ~/linux_h618 注意，虽然后缀名是 tar.gz，但是这个包实际上只用 tar 打包，并没有用 gzip 进行压缩。。。 export PATH=/home/ptz/linux_h618/aw-image-build/toolchains/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/bin:$PATH 加入到 .bashrc 中，把交叉编译工具链加入环境变量 编译 使用 sudo -i, 密码 temppwd ip a iw dev wlan0 scan | grep -A 30 Figurobot vim /etc/wpa_supplicant/wpa_supplicant.conf network={ ssid="Figurobot" psk="Figurobot99" key_mgmt=WPA-PSK } wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf ifconfig, 或者 dhclient wlan0 # 获取IP地址 date -s "2025-05-15 16:00:00" timedatectl set-ntp true , 然后 timedatectl 查看前两个是不是 yes 默认 openssh 就是 ok 的。 stty -F /dev/ttyS3 -echo 关闭回显
vmware 安装 ubuntu cpu 8核，ram 16g 光驱挂载 iso 安装系统 设置光驱关闭 虚拟机 电源 restart Ubuntu 18.04.6 LTS (Bionic Beaver) Ubuntu 20.04.6 LTS (Focal Fossa) 常用配置 分辨率: 右键 display setting 换源: 左下角，software & update，里面选择 aliyun vmware tools 旧的 vm-tools，不能复制粘贴 新的 open-vm-tools，可以复制粘贴 vm-tools https://packages-prod.broadcom.com/tools/frozen/linux/linux.iso 下载 vmware tools 光驱挂载 linux.iso 打开桌面的 vmware tools 盘，然后复制 tar.gz 文件到桌面，再 extrace here 到桌面。 cd Desktop/VMwareTools-10.3.26-22085142/vmware-tools-distrib/, sudo ./vmware-install.pl ，然后 yes，输入密码，一路回车键接收默认即可安装。 参考 VMware support for open-vm-tools Installing VMware Tools in an Ubuntu Virtual Machine open-vm-tools sudo apt-get autoremove open-vm-tools sudo apt-get install open-vm-tools open-vm-tools-desktop 共享盘有时候没有自动挂载，需要手动或设置自动挂载 #手动挂载 sudo mount -t fuse.vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other #自动挂载 #打开/etc/fstab，添加 .host:/ /mnt/hgfs fuse.vmhgfs-fuse auto,allow_other 0 0 参考 VMware虚拟机和主机间复制粘贴共享剪贴板 vmware和主机间复制粘贴文件、文字 立创泰山派训练营学习笔记--Linux SDK编译环境搭建与验证（一） 设置 具体虚拟机 -> 设置 -> 选项 共享文件夹-> 总是启用，然后添加共享文件夹 vmware tools -> 开启时间同步。 配置源 左下角所有应用中，选择 software & updates download from 中选择 other 选择 china -> aliyun 确定 sudo apt update ssh sudo apt install openssh-server 或者 sudo apt install ssh 开发环境 sudo apt-get install git ssh make gcc libssl-dev liblz4-tool expect g++ patchelf chrpath gawk texinfo chrpath diffstat binfmt-support qemu-user-static live-build bison flex fakeroot cmake gcc-multilib g++-multilib unzip device-tree-compiler ncurses-dev python vim 源码准备 linux cd ~/ mkdir -p linux_lckfb/tspi_linux_sdk_repo_20240131/ cd /mnt/hgfs/virtual_share tar xvfz tspi_linux_sdk_repo_20240131.tar.gz -C ~/linux_lckfb/tspi_linux_sdk_repo_20240131/ cd ~/linux_lckfb/tspi_linux_sdk_repo_20240131/ .repo/repo/repo sync -l -j88 把 buildroot_dl_4c7c9df616fb.tar.gz 解压，其中的 buildroot/dl 中的压缩文件，全部放到 tspi_linux_sdk_repo_20240131 下面对应的 buildroot/dl 中，节省后续的下载时间。 板级配置 ./build.sh device/rockchip/rk356x/BoardConfig-rk3566-tspi-v10.mk 选择BoardConfig-rk3566-tspi-v10.mk，这里序列号是3 ./build.sh -h kernel 查看，确定是 rockchip 3566. buildroot export RK_ROOTFS_SYSTEM=buildroot ./build.sh all ./mkfirmware.sh 把 rockdev 中的内容复制出来即可，软连接的需要找到对应的文件。 debian export RK_ROOTFS_SYSTEM=debian 修复依赖 cd debian sudo dpkg -i ubuntu-build-service/packages/* sudo apt-get install -f cd .. ./build.sh debian ./mkfirmware.sh ubuntu 根文件系统 sudo apt-get install qemu-user-static 安装模拟器 http://cdimage.ubuntu.com/ubuntu-base/releases/20.04/release/ 下载 20.04.5 的根文件系统 ubuntu-base-20.04.5-base-arm64.tar.gz 解压根文件系统，注意 p 保留权限 tar -xpf ubuntu-base-18.04.5-base-arm64.tar.gz -C temp/ 准备网络 cp -b /etc/resolv.conf temp/etc/resolv.conf 准备 qemu cp /usr/bin/qemu-aarch64-static temp/usr/bin/ 创建挂载根文件系统脚本: #!/bin/bash function mnt() { echo "MOUNTING" sudo mount -t proc /proc ${2}/proc sudo mount -t sysfs /sys ${2}/sys sudo mount -o bind /dev ${2}/dev sudo mount -t devpts devpts ${2}/dev/pts -o gid=5,mode=620 sudo chroot ${2} } function umnt() { echo "UNMOUNTING" sudo umount -R ${2}/dev/pts sudo umount ${2}/dev sudo umount ${2}/proc sudo umount ${2}/sys } if [ "$1" == "-m" ] && [ -n "$2" ] ; then mnt $1 $2 elif [ "$1" == "-u" ] && [ -n "$2" ]; then umnt $1 $2 else echo "" echo "Either 1'st, 2'nd or both parameters were missing" echo "" echo "1'st parameter can be one of these: -m(mount) OR -u(umount)" echo "2'nd parameter is the full path of rootfs directory(with trailing '/')" echo "" echo "For example: ch-mount -m /media/sdcard/" echo "" echo 1st parameter : ${1} echo 2nd parameter : ${2} fi 添加运行权限: sudo chmod +x rootfs-mount.sh 进入根文件系统: sudo bash rootfs-mount.sh -m temp 更新列表 apt-get update 语言相关: apt install locales echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen echo "zh_CN.UTF-8 UTF-8" >> /etc/locale.gen locale-gen update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 设置语言临时环境变量 export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 解决 debconf 警告， apt install apt-utils 更新软件 apt upgrade. 其实上面的 update 到这边的 upgrade 之间的，都可以放到脚本中去。 安装必要软件: apt install ssh ethtool rsyslog bash-completion htop net-tools wireless-tools network-manager iputils-ping vim git language-pack-en-base ifupdown cutecom audacity v4l-utils cheese chromium-browser wpasupplicant apt-get install ssh apt-get install ethtool apt-get install rsyslog apt-get install bash-completion apt-get install htop apt-get install net-tools apt-get install wireless-tools apt-get install network-manager apt-get install iputils-ping --------下面建议必须装--------- apt-get install vim #语言包 apt-get install language-pack-en-base #这个必须要否则无法生成/etc/network/interfaces配网文件 apt-get install ifupdown #ping apt-get install inetutils-ping #串口 apt-get install cutecom #录音 apt-get install audacity #摄像头 apt-get install v4l-utils apt-get install cheese #浏览器 apt-get install chromium-browser #wifi apt-get install wpasupplicant 参考 编译泰山派Linux SDK可能遇到的问题和解决办法 【立创·泰山派RK3566开发板】Linux系统编译 立创泰山派训练营学习笔记--Linux SDK编译环境搭建与验证（一） rk3399-linux_v2.8 编译报错 lb config: unrecognized option ‘--debootstrap-options‘ rockchip Debian 编译报错 ubuntu22.04编译报错mksquashfs.h文件中多重定义 [Linux]从零开始的泰山派系统安装与远程教程 cdimage.ubuntu Ubuntu Base 20.04.5 LTS (Focal Fossa) ARM64 ubuntu20.04根文件系统制作 Ubuntu 20.04.6 LTS (Focal Fossa) Ubuntu 24.04.3 (Noble Numbat)
问题 买的 ov2640，标称 60 度，实际测试角度只有 30度左右？ 解答 一般标称的 60 度，指的是场角度。我们实际测试的角度，指的是水平角度。 大概是 0.5 的关系。 参考 摄像头视场角-对角线视场角-水平视场角
问题 esp32 主要有几种配网方式？ 解答 AP 配网 esp32 作为ap，手机等设备连接到 esp32 上面，把账号密码输入，esp32 使用这些账号和密码连接。 smartconfig esp32 混杂模式，监听网络广播，使用官方的 app ESPTOUCH 即可配网 airkiss 微信配网，和 smartconfig 本质是一样的。 蓝牙配网 需要官方的蓝牙配网的 app才行。 参考 图文手把手教程--ESP32 一键配网(Smartconfig、Airkiss) ESP32-IDF学习笔记(1)---基于WIFI_PROV API的两种配网 使用Arduino开发ESP32（二十八）：WiFiProvisioning 官方说明文档自翻 【图解】物联网设备的N种Wi-Fi配网方式 Linux 网络接口的混杂模式(Promiscuous mode)认知
mpu6050 通过 I2C 来驱动，可以使用卡尔曼滤波姿态解算。获取的传感器原始数据中，三个方向的加速度，有一个方向是 9.8 的重力加速度。 参考代码 https://blog.csdn.net/ling3ye/article/details/51360568 #include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" MPU6050 accelgyro; unsigned long now, lastTime = 0; float dt; //微分时间 int16_t ax, ay, az, gx, gy, gz; //加速度计陀螺仪原始数据 float aax=0, aay=0,aaz=0, agx=0, agy=0, agz=0; //角度变量 long axo = 0, ayo = 0, azo = 0; //加速度计偏移量 long gxo = 0, gyo = 0, gzo = 0; //陀螺仪偏移量 float pi = 3.1415926; float AcceRatio = 16384.0; //加速度计比例系数 float GyroRatio = 131.0; //陀螺仪比例系数 uint8_t n_sample = 8; //加速度计滤波算法采样个数 float aaxs[8] = {0}, aays[8] = {0}, aazs[8] = {0}; //x,y轴采样队列 long aax_sum, aay_sum,aaz_sum; //x,y轴采样和 float a_x[10]={0}, a_y[10]={0},a_z[10]={0} ,g_x[10]={0} ,g_y[10]={0},g_z[10]={0}; //加速度计协方差计算队列 float Px=1, Rx, Kx, Sx, Vx, Qx; //x轴卡尔曼变量 float Py=1, Ry, Ky, Sy, Vy, Qy; //y轴卡尔曼变量 float Pz=1, Rz, Kz, Sz, Vz, Qz; //z轴卡尔曼变量 void setup() { Wire.begin(); Serial.begin(115200); accelgyro.initialize(); //初始化 unsigned short times = 200; //采样次数 for(int i=0;i<times;i++) { accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值 axo += ax; ayo += ay; azo += az; //采样和 gxo += gx; gyo += gy; gzo += gz; } axo /= times; ayo /= times; azo /= times; //计算加速度计偏移 gxo /= times; gyo /= times; gzo /= times; //计算陀螺仪偏移 } void loop() { unsigned long now = millis(); //当前时间(ms) dt = (now - lastTime) / 1000.0; //微分时间(s) lastTime = now; //上一次采样时间(ms) accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); //读取六轴原始数值 float accx = ax / AcceRatio; //x轴加速度 float accy = ay / AcceRatio; //y轴加速度 float accz = az / AcceRatio; //z轴加速度 aax = atan(accy / accz) * (-180) / pi; //y轴对于z轴的夹角 aay = atan(accx / accz) * 180 / pi; //x轴对于z轴的夹角 aaz = atan(accz / accy) * 180 / pi; //z轴对于y轴的夹角 aax_sum = 0; // 对于加速度计原始数据的滑动加权滤波算法 aay_sum = 0; aaz_sum = 0; for(int i=1;i<n_sample;i++) { aaxs[i-1] = aaxs[i]; aax_sum += aaxs[i] * i; aays[i-1] = aays[i]; aay_sum += aays[i] * i; aazs[i-1] = aazs[i]; aaz_sum += aazs[i] * i; } aaxs[n_sample-1] = aax; aax_sum += aax * n_sample; aax = (aax_sum / (11*n_sample/2.0)) * 9 / 7.0; //角度调幅至0-90° aays[n_sample-1] = aay; //此处应用实验法取得合适的系数 aay_sum += aay * n_sample; //本例系数为9/7 aay = (aay_sum / (11*n_sample/2.0)) * 9 / 7.0; aazs[n_sample-1] = aaz; aaz_sum += aaz * n_sample; aaz = (aaz_sum / (11*n_sample/2.0)) * 9 / 7.0; float gyrox = - (gx-gxo) / GyroRatio * dt; //x轴角速度 float gyroy = - (gy-gyo) / GyroRatio * dt; //y轴角速度 float gyroz = - (gz-gzo) / GyroRatio * dt; //z轴角速度 agx += gyrox; //x轴角速度积分 agy += gyroy; //x轴角速度积分 agz += gyroz; /* kalman start */ Sx = 0; Rx = 0; Sy = 0; Ry = 0; Sz = 0; Rz = 0; for(int i=1;i<10;i++) { //测量值平均值运算 a_x[i-1] = a_x[i]; //即加速度平均值 Sx += a_x[i]; a_y[i-1] = a_y[i]; Sy += a_y[i]; a_z[i-1] = a_z[i]; Sz += a_z[i]; } a_x[9] = aax; Sx += aax; Sx /= 10; //x轴加速度平均值 a_y[9] = aay; Sy += aay; Sy /= 10; //y轴加速度平均值 a_z[9] = aaz; Sz += aaz; Sz /= 10; for(int i=0;i<10;i++) { Rx += sq(a_x[i] - Sx); Ry += sq(a_y[i] - Sy); Rz += sq(a_z[i] - Sz); } Rx = Rx / 9; //得到方差 Ry = Ry / 9; Rz = Rz / 9; Px = Px + 0.0025; // 0.0025在下面有说明... Kx = Px / (Px + Rx); //计算卡尔曼增益 agx = agx + Kx * (aax - agx); //陀螺仪角度与加速度计速度叠加 Px = (1 - Kx) * Px; //更新p值 Py = Py + 0.0025; Ky = Py / (Py + Ry); agy = agy + Ky * (aay - agy); Py = (1 - Ky) * Py; Pz = Pz + 0.0025; Kz = Pz / (Pz + Rz); agz = agz + Kz * (aaz - agz); Pz = (1 - Kz) * Pz; /* kalman end */ Serial.print(agx);Serial.print(","); Serial.print(agy);Serial.print(","); Serial.print(agz);Serial.println(); } 参考 MPU-6050寄存器手册中文版 MPU-60X0寄存器中文版V4.0.pdf Arduino uno + mpu6050 陀螺仪 运用卡尔曼滤波姿态解算实验 Arduino uno + mpu6050 陀螺仪 运用卡尔曼滤波姿态解算实验 MPU6050
在 ros 中，用于串口通信的是 rosserial，在 ros2 中，替代的是 micro-ROS. 创建项目 使用 stm32cube 先创建好一个工程，配置好相关的参数。 生成 micro-ros 静态库 可以先看下介绍 https://github.com/micro-ROS/micro_ros_setup 在项目文件夹下面，clone 一下 https://github.com/micro-ROS/micro_ros_stm32cubemx_utils 进入 micro_ros_stm32cubemx_util 之后，切换到 ros 版本分支，比如 foxy 根据需要，决定是否使用 dos2unix。 修改 Makefile，增加如下: ####################################### # micro-ROS addons ####################################### LDFLAGS += micro_ros_stm32cubemx_utils/microros_static_library/libmicroros/libmicroros.a C_INCLUDES += -Imicro_ros_stm32cubemx_utils/microros_static_library/libmicroros/microros_include Add micro-ROS utils C_SOURCES += micro_ros_stm32cubemx_utils/extra_sources/custom_memory_manager.c C_SOURCES += micro_ros_stm32cubemx_utils/extra_sources/microros_allocators.c C_SOURCES += micro_ros_stm32cubemx_utils/extra_sources/microros_time.c Set here the custom transport implementation C_SOURCES += micro_ros_stm32cubemx_utils/extra_sources/microros_transports/dma_transport.c print_cflags: @echo $(CFLAGS) + 拉取生成 micro-ros 库的 docker `docker pull microros/micro_ros_static_library_builder:foxy` + 运行 `docker run -it --rm -v $(pwd):/project --env MICROROS_LIBRARY_FOLDER=micro_ros_stm32cubemx_utils/microros_static_library microros/micro_ros_static_library_builder:foxy` + 查看生成的静态库 `micro_ros_stm32cubemx_utils/microros_static_library/libmicroros/libmicroros.a` # 使用 micro-ros + 参考 micro_ros_stm32cubemx_utils 下面的 sample_main ，修改项目自身的 main # 参考: + [How to setup micro-ROS on any STM32 microcontroller](https://www.youtube.com/watch?v=xbWaHARjSmk) + [【分享】microROS 和 rosserial 对比](https://fishros.org.cn/forum/topic/1960/%E5%88%86%E4%BA%AB-microros-%E5%92%8C-rosserial-%E5%AF%B9%E6%AF%94) + [stm32系列MICROROS环境配置（使用cubemx+clion+freertos）](https://fishros.org.cn/forum/topic/2423/stm32%E7%B3%BB%E5%88%97microros%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE-%E4%BD%BF%E7%94%A8cubemx-clion-freertos)
背景 需要在 fragment 进行 replace 然后 pop 回来之后，一些选择项还是原来的选项。 方法 查阅了很多资料，发现如果是 view 类型那么可以被自动保存，非 view 的就需要我们自己来保存了。 按照 fragment 的生命周期来说，需要在 onDestroyView 中进行保存，在 onCreateView 中进行读取。 如果是比较简单的，那么直接使用本身生成的 fragement 类型中的 setArguments 和 getArguments 来得到 bundle 来操作即可。 如果是比较复杂的，最好自己来实现一个保存和读取的，方便控制。然后在 onDestroyView, onCreateView 里面进行调用即可。 public Bundle setSaveBundle(Bundle bundle) { if (bundle == null) { bundle = new Bundle(); } bundle.putInt("type_index", analysisTypeLastSelected); DLog.d(TAG, "setSaveBundle: " + "type_index: " + analysisTypeLastSelected); return bundle; } public void getSaveBundle(Bundle bundle) { if (bundle == null) { DLog.d(TAG, "getSaveBundle: " + "null!"); return; } analysisTypeLastSelected = bundle.getInt("type_index"); DLog.d(TAG, "getSaveBundle: " + "type_index: " + analysisTypeLastSelected); } 参考: [译] 保存/恢复 Activity 和 Fragment 状态的最佳实践 https://segmentfault.com/a/1190000006691830 切换Fragment时实现数据保持 https://www.cnblogs.com/zhujiabin/p/4192613.html Pro Android学习笔记（四一）：Fragment（6）：数据保留 https://blog.csdn.net/flowingflying/article/details/12749403 Android Fragment 生命周期onCreatView、onViewCreated https://blog.csdn.net/asdf717/article/details/51383750 Android中Fragment数据保存和恢复 https://www.jianshu.com/p/015c79bedb41 onSaveInstanceState()和onRestoreInstanceState()使用详解 https://www.jianshu.com/p/27181e2e32d2 Android 使用onSaveInstanceState保存数据 https://blog.csdn.net/qq_34694875/article/details/109530012 android onSaveInstanceState方法 https://www.jianshu.com/p/5dde143a143f Android Fragment 在返回栈popBackStack() ，返回时onResume问题 https://blog.csdn.net/qq_34983989/article/details/78126362 Android Fragment回退栈管理(popBackStack) https://blog.csdn.net/qq_35988274/article/details/100518346
[转] Android studio中使用Recyclerview与Activity跳转
转自: https://blog.csdn.net/nonamepao/article/details/120943245 项目介绍 本次项目在上一次类微信界面的设计上，增加了用recyclerview实现的模拟微信聊天联系人列表，其中从列表跳转到联系内容的部分使用了activity的跳转。 设计 列表界面 用一个xml来设计列表的界面，这里采用了最简单的单个textview作为标题模式。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" xmlns:tools="http://schemas.android.com/tools" android:layout_margin="8dp" android:orientation="vertical"> <TextView android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="8dp" android:gravity="center" android:text="TextView" /> </LinearLayout> 联系内容界面 显示联系内容的界面新建两个activity来实现，在activity2.xml文件作为被跳转的布局文件用来显示联系内容。 activity_1.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context=".Activity1"> <TextView android:id="@+id/textView7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="这是第一个activity" android:textSize="40sp" /> </LinearLayout> activity_2.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Activity2"> <TextView android:id="@+id/textView5" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="联系内容" android:textSize="40dp" /> </LinearLayout> 列表的展示 修改之前的weixin.java文件，调用布局文件来实现列表的展示 package com.example.wechatlike; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; public class weixin extends Fragment { private RecyclerView recyclerView; private MyAdapter myadapter; private List<String> list = new ArrayList<>(); private Context context; public weixin() { } private void initData(){ for(int i=0;i<10;i++) { list.add("联系人" + i ); } } private void initView3(){ context=this.getActivity(); myadapter=new MyAdapter(list,context); LinearLayoutManager manager3=new LinearLayoutManager(context); manager3.setOrientation(LinearLayoutManager.VERTICAL); recyclerView.setAdapter(myadapter); recyclerView.setLayoutManager(manager3); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_weixin, container, false); recyclerView=view.findViewById(R.id.recycleview); initData(); initView3(); return view; } } adpter适配器 adpter对前后接口进行适配，在常规recyclerview适配上增加OnItemClickListener 监听事件。 package com.example.wechatlike; import android.content.Context; import android.content.Intent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import java.util.List; public class MyAdapter extends RecyclerView.Adapter <MyAdapter.MyViewHolder>{ private View itemView; private Context context; private MyAdapter myadapter; private List<String> list; private OnItemClickListener onItemClickListener; public MyAdapter(List<String> list, Context context){ this.context=context; this.list =list; } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { itemView= LayoutInflater.from(context).inflate(R.layout.item,parent,false); MyViewHolder myViewHolder=new MyViewHolder(itemView); return myViewHolder; } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { holder.textView1.setText(list.get(position)); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(context,Activity2.class); context.startActivity(intent); } }); } @Override public int getItemCount() { return list.size(); } public class MyViewHolder extends RecyclerView.ViewHolder{ TextView textView1; public MyViewHolder(@NonNull View itemView) { super(itemView); textView1=itemView.findViewById(R.id.textView1); } } public interface OnItemClickListener{ void onItemClick(int position); } public void setOnItemClickListener(OnItemClickListener onItemClickListener){ this.onItemClickListener = onItemClickListener; } } 结果展示 代码仓库：张润宇/WeChatlike https://gitee.com/noname-pao/we-chatlike
转自: https://blog.csdn.net/weixin_46093316/article/details/106138435 总结目录 视图篇 如何理解非主线程可以更新UI dialogFragment 全屏时左右留空的解决方案 dialogFragment 全屏时状态栏出现黑色布局的解决方案 多个fragment 切换重叠的解决方案 多个fragment 保存状态时可能出现 TransactionTooLargeException 的解决方案 recyclerview 调用 notifyItemRemoved 方法移除某个 Item 之后因为引用 position 引起 crash 的原因 recyclerview 局部刷新Item时会因为默认动画导致闪烁的解决方案 recyclerview 中的 item 出现莫名的偏移滚动 recyclerview 内容超过一屏时，findFistCompletelyVisibleItemPosition 会返回 -1 的原因 textview 中富文本点击事件拦截了长按事件的解决方案 如何禁止 ViewPager 的滑动 如何仿蘑菇街/马蜂窝 Viewpager 装载图片之后切换时动态变更高度 如何控制 appbarLayout 随时定位到某个位置 如何禁止 appbarLayout 滚动 edittext 未能响应 onClickListener 事件的解决方案 使用 listview 或gridview 的处理 item 的 state_selected 事件是无效的解决方案 解决5.0以上Button自带阴影效果的方案 针对 onSingleTapUp 和 onSIngleTapConfirmed 的使用区别 如何使用layer-list画三角形 关于属性动画中旋转 View 时部分机型出现 View 闪烁的解决方案 关于 ConstraintLayout 的代码布局下的注意事项 TextView 在 6.0 版本下设置单行尾部缩略的坑 服务篇 后台手动清理应用之后，service中启动的notifications并没有消失的解决方案 全局的Context使用更为优雅的获取方案 线程篇 建议新起线程不要随便调用网络请求，一般的newThread没有looper队列，参考handlerThread 网络篇 Retorfit get 请求参数出现错误的解决方案 数据篇 如何优雅处理 sqlite 多线程读写问题 如何理解 Intent 传递数据出现 TransactionTooLargeException 的问题 机型系统适配篇 如何解决 NatigationBar 被 PopupWindow 遮挡的问题 如何解决MIUI系统后台无法 toast 的问题 如何解决关闭通知栏权限无法弹出 toast 的问题 如何适配 vivo 等双面屏幕 如何解决华为设备产生太多 broadcast 导致crash的问题 各种通知栏的适配方案 解决针对魅族推送内容限制的问题 解决从系统安装起安装应用后启动，Home 隐藏后 Launcher 重复启动的问题 针对有launcher做为Activity的应用，在完全没有启动下收到第三方推送（小米，华为，魅族）/分享拉起的注意事项 针对 App 多场景拉起场景下的场景判断分析 8.0 部分 ROM 出现 Only fullscreen opaque activities can request orientation 的解决方案 9.0 android 支持明文连接（Http) 编译构建篇 travis-ci 高版本androidO编译遇到 license 没通过编译失败的解决方案 Dalvik 支持的 android 版本下进行分包执行会有一些限制 Dalvik 分包构建每一个 dex 文件时可能出现 java.lang.NoClassDefFoundError Java 8 methods of java.lang.Long and java.lang.Character are not desugared by D8 databinding 中 findBinding vs getBinding 的场景区别 版本构建出现 Gradle sync failed: Cannot choose between the following configurations of project gradle 配置本地离线包 解决kvm/jvm 编译时 -classpath 遇到的分割及空格的问题 databinding NoSuchMethodError with buildTool 3.4.0 AS连接真机调试出现 debug info can be unavailabe 的解决方法 版本控制篇 git 修改 commit 记录 解决git ignore 文件不生效的问题 其他 ExoPlayer在接听电话之后会导致原来设置的 Source 中静音状态消失了导致可能返回 app 续播的时候视频突然有声音 AndroidStudio 提示 Please select Android SDK 关于 Java 中字符与字节的编码关系认识 关于 emoji 编码的长度计算问题 <<视图篇>> 如何理解非主线程可以更新UI 谷歌在 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的时候这个判断并没有执行到 dialogFragment 全屏时左右留空的解决方案 在 fragment#onResume 中重新调整 window 布局 android.view.WindowManager.LayoutParams lp = window.getAttributes(); lp.width = WindowManager.LayoutParams.MATCH_PARENT; lp.height = WindowManager.LayoutParams.WRAP_CONTENT; window.setAttributes(lp); dialogFragment 全屏时状态栏出现黑色布局的解决方案 在主题中设置 <item name="android:windowIsFloating">true</item> 此时 window 为 wrap_content，如果出现左右空白，则考虑使用上个问题的方案。 当应用退回后台一段时间重返后，Fragment 切换重叠的解决方案 在线上项目中我们遇到一个场景：当应用按下 Home 退回后台，然后过一段时间之后从后台拉起我们的项目。极少数机型在主页进行多个 fragment 的切换时出现了 fragment 的重叠。经过定位之后发现，这些机型的运存偏小，性能偏差，出现这种现象的原因是由于内存的压力的原因，系统并不知后台的程序哪一个才需要保持运行，就会尝试回收内存占用较大的页面，当我们的页面被系统销毁时，fragmentActivity#onSaveInstanceState 被执行并保存了一些瞬态信息，比如界面 fragment 的视图信息。当我们再次拉起应用的时候，会让原来的 fragmentActivity 重建并重新构建了一个新的 fragment ，此时会叠加到已经被恢复的 fragment 之上导致重叠。 比较暴力的做法是不让 activity 保存状态，比如 @Override public void onSaveInstanceState(Bundle outState) { //直接不调用 super.onSaveInstanceState(outState); //或者直接传递空数据 super.onSaveInstanceState(new Bundle()); } 比较优雅的做法是，比如 @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 } } 多个fragment 保存状态时可能出现 TransactionTooLargeException 的解决方案 出现 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 工具可以在开发中实时观测页面内存变化。 recyclerview 调用 notifyItemRemoved 方法移除某个 Item 之后因为引用 position 引起 crash 的原因 notifyItemRemoved方法并不会移除列表的数据源的数据项导致数据源中的数据与列表Item数目不一致，需要同步刷新数据源。 recyclerview 局部刷新Item时会因为默认动画导致闪烁的解决方案 因为recyclerview存在ItemAnimator，且在删除/更新/插入Item时会触发，可设置不支持该动画即可。 ((SimpleItemAnimator)recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); recyclerview 中的 item 出现莫名的偏移滚动 这个问题经过定位存在于 viewholder 中的某个 view 可能提前获取到焦点。 同时在。alibaba-vlayout 库中也发现有人反馈改问题。 issues-255 解决的方法是在 Recyclerview中外层父布局中添加 android:descendantFocusability="blocksDescendants" 用于父布局覆盖 Recyclerview 优先抢占焦点。 recyclerview 内容超过一屏时，findFistCompletelyVisibleItemPosition 会返回 -1 的原因 原因是在 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()); 如何禁止 ViewPager 的滑动 重写ViewPager onTouchEvent 和 onInterceptTouchEvent 并返回false,不处理任何滑动事件 @Override public boolean onTouchEvent(MotionEvent arg0) { return false; } @Override public boolean onInterceptTouchEvent(MotionEvent arg0) { return false; } 如何仿蘑菇街/马蜂窝 Viewpager 装载图片之后切换时动态变更高度 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.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); } 如何禁止 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 未能响应 onClickListener 事件的解决方案 Edittext监听未获取焦点的Edittext的点击事件，第一次点击触发OnFocusChangeListener，在获取焦点的情况下才能响应onClickListener 使用 listview 或gridview 的处理 item 的 state_selected 事件是无效的解决方案 在xml布局中对listview或gridview设置Android:choiceMode=“singleChoice”,并使用state_activated状态来代替state_selected状态。（2016.12.10） 解决5.0以上Button自带阴影效果的方案 在xml定义的Button中，添加以下样式定义 style="?android:attr/borderlessButtonStyle" 针对 onSingleTapUp 和 onSIngleTapConfirmed 的使用区别 前者在按下并抬起时发生，后者有一个附加条件时Android会确保点击之后在短时间内没有再次点击才会触发。常用于如果需要监听单击和双击事件。 如何使用layer-list画三角形 <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> 关于属性动画中旋转 View 时部分机型出现 View 闪烁的解决方案 在大神 app 信息流快捷评论模块中，在交付快捷评论动画的时候发现，使用属性动画实现的抖动效果在部分机型上出现闪烁。而我们的实现抖动效果是通过 View.ROTATION 来实现的。经过研究，部分机型因为硬件加速的原因导致的。为动画 view 进行以下设置 view.setLayerType(View.LAYER_TYPE_HARDWARE,null); 关于 ConstraintLayout 的代码布局下的注意事项 不同于其他 ViewGroup 控制子 View 的排版，ConstraintLayout 需要构建 ConstraintSet 对象来粘合。 在手动添加子 View 的场景下，可以通过 ConstraintSet#clone(ConstraintLayout constraintLayout) 来克隆当前已有 ConstraintLayout 的排版信息，然后最后调用 ConstraintSet#applyTo(ConstraintLayout constraintLayout) 确认最终的排版信息。 TextView 在 6.0 版本下设置单行尾部缩略的坑 在大神信息流中，有一些卡片信息需要设置单行缩略。在 MTL 兼容测试过程中发现有一些机型显示异常，经过归纳及校验，这部分机型的版本都是 < 6.0。 通过在 stackoverflow 也找到了相同的问题场景 text ellipsize behavior in android version < 6.0 . 针对这部分版本的手机，我们需要在设置单行的时候把 android:maxLines="1" 改成 android:singleLine="true"。即使 IDE 提示该 API 已经过期了！ <<服务篇>> 后台手动清理应用之后，service中启动的notifications并没有消失的解决方案 从 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); } 全局的Context使用更为优雅的获取方案 由于我们在优化 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申明 <provider android:name=".ContextProvider" android:authorities="${your_application_id}.contextprovider" android:exported="false" /> <<线程篇>> 建议新起线程不要随便调用网络请求，一般的newThread没有looper队列，参考handlerThread <<网络篇>> Retorfit get 请求参数出现错误的解决方案 @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。 <<数据篇>> 如何优雅处理 sqlite 多线程读写问题 一个helper实例对应一个 db connectton，这个连接能提供读连接和写连接，如果只是调用 read-only，则默认也会有写连接。 一个helper实例可以在多个线程中使用，java层会使用锁机制保证线程同步，哪怕有100个线程，对数据度的调用也会被序列化 如果尝试从不同 connection 同时对数据库进行写操作，则有一个会失败。并不会按照第一个写完再轮到第二个写，有一些sqlite版本甚至不会有错误提示。 一般而言，如果要在多线程环境下使用数据库，则确保多个线程中使用的是同一个SQLiteDataBase对象，该对象对应一个db文件。 特殊情况，如果多个 SQLiteDataBase 打开同一个 db 文件，同时使用不同线程同时写（insert，update，exexSQL）会导致在 SQLiteStatement.native_execute 方法时可能导致异常。这个异常来自本地方法里面，仅仅在Java对有对 SQLiteDataBase 进行同步锁保护。但是多线程读（query）返回的事 SQLiteCursor保存查询条件并没有立刻执行查询，仅仅在需要时加载部分数据，可以多线程不同 SQLiteDataBase 进行读。 如果要处理上述问题，可以使用 “一个线程写，多个线程同时读，每个线程都用各自SQLiteOpenHelper。” 在android 3.0版本以上 打开 enableWriteAheadLogging。当打开时，它允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的文件，不是原数据库文件。所以写在执行时，不会影响读操作，读操作读的是原数据文件，是写操作开始之前的内容。在写操作执行成功后，会把修改合并会原数据库文件。此时读操作才能读到修改后的内容。但是这样将花费更多的内存。 如何理解 Intent 传递数据出现 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则不可以，所以需要根据业务来调整。 <<机型系统适配篇>> 如何解决 NatigationBar 被 PopupWindow 遮挡的问题 popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); 如何解决MIUI系统后台无法 toast 的问题 参考 github.com/zhitaocai/T… 项目，但是在小米3或者小米Note（4.4.4）手机上 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mContext 需要使用 ApplicationContext 才能生效。 如何解决关闭通知栏权限无法弹出 toast 的问题 由于谷歌把 Toast 设置为系统消息权限，可以参考 Android关闭通知消息权限无法弹出Toast的解决方案 维护自己的消息队列. 如何适配 vivo 等双面屏幕 在 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开放平台 文档。 如何解决华为设备产生太多 broadcast 导致crash的问题 由于部分华为中，如果app注册超过500个BroadcastReceiver就会抛出 “ Register too many Broadcast Receivers ” 异常。通过分析发现其内部有一个白名单，自己可以通过创建一个新的app，使用微信包名进行测试，发现并没有这个限制。通过反射 LoadedApk 类拿到 mReceiverResource 中的 mWhiteList 对象添加我们的包名就可以了。 可以参考 github.com/llew2011/Hu… 这个项目。 各种通知栏的适配方案 参考 网易考拉实现的适配方法 解决针对魅族推送内容限制的问题 今天收到魅族渠道的警报称“推送内容可能过长”。IM功能针对离线设备走设备商的推送，魅族推送限制了title标题1-32字符，content内容1-100字符。如果频繁推送超过限制的通知，魅族推送服务器可能不会下发推送到魅族设备。故服务端限制发送到魅族服务器的消息标题和内容长度解决。 解决从系统安装起安装应用后启动，Home 隐藏后 Launcher 重复启动的问题 判断启动页面是否是根节点(推荐) 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。 onCreate中优先拦截 intent 判断拉起参数，如果有拉起参数则直接进入主页面B，intent交付给主页面B处理 部分场景下，比如第三方消息推送，华为和小米拉起闪屏或 launcher intent无法区分，针对该做法是，限定进入闪屏/launcher的逻辑,剩余场景统一进入主页面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(); } 针对 App 多场景拉起场景下的场景判断分析 可参考我另一篇文章 对线上项目拉起应用场景的思考总结 8.0 部分 ROM 出现 Only fullscreen opaque activities can request orientation 的解决方案 由于我们项目需要处理沉浸式，所以针对 android:windowIsTranslucent 的属性默认打开的。但是线上发现部分 8.0设备出现诡异的 crash，原因是我们对于页面的 orientation 申明都统一为 portrait 。查阅 android 源码的更新发现在 8.0 源码的逻辑里面这两个逻辑竟然不兼容，随后在 8.0 版本后谷歌进行了修复。但是国内部分 ROM 看起来并没有修复这个问题。后面同事提供了一个比较取巧的方案，通过为页面指定 android:screenOrientation=“behind” 来避免 8.0 版本的问题同时兼容所有 android 版本。 9.0 android 支持明文连接（Http） 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> <<编译构建篇>> travis-ci 高版本androidO编译遇到 license 没通过编译失败的解决方案 参考 CI 讨论区 添加 dist: precise 及 before_install 项中新增 sdkmanager指令。具体可参考我的 开源项目配置 Dalvik 支持的 android 版本下进行分包执行会有一些限制 冷启动时需要安装dex文件，如果dex文件太大则可能导致处理时间太长导致 ANR 即使使用 multiDex 方案在低于 4.0 系统上可能会出现 Dalvik linearAlloc 的bug，这是因为该方案需要申请一个很大的内存，运行时可能的导致程序崩溃。这个限制在 4.0 上虽然有所改善了，但是还是可能在低于 5.0 的机器上触发。 Dalvik 分包构建每一个 dex 文件时可能出现 java.lang.NoClassDefFoundError 这个问题的原因是构建工具绘制行比较复杂决策来确定主 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' ... } } } Java 8 methods of java.lang.Long and java.lang.Character are not desugared by D8 这个问题出现在使用 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 已解决脱糖问题，可尝试升级解决。 databinding 中 findBinding vs getBinding 的场景区别 不同之处在于，findBinding将遍历父节点，而如果使用getBinding时当view不是跟节点会返回null。 版本构建出现 Gradle sync failed: Cannot choose between the following configurations of project 参考 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 配置本地离线包 离线下载 gradle 离线包保存在 url 中 修改 gradle/wrapper/gradle-wrapper.properties 调整 distributionUrl 目录指向 url 修改 build.gradle classpath 版本映射 gradle 版本 解决kvm/jvm 编译时 -classpath 遇到的分割及空格的问题 linux/mac OS 上使用 “：” 分割多个classpath路径，window使用 “；” 分割。 如果linux/mac OS 路径存在空格，暂时避免，使用多种方式尝试未果=。=。 databinding NoSuchMethodError with buildTool 3.4.0 项目从 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) 幸运的是，3.4.1已经修复了。更改 3.4.0 -> 3.4.1 就可以了。 AS连接真机调试出现 debug info can be unavailabe 的解决方法 在使用 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会在真实/虚拟设备上缓存一个无效的连接，并且由于该连接繁忙导致也无法连接到该设备。 <<版本控制篇>> git 修改 commit 记录 git reset --soft HEAD^ 撤销当前的commit，如果只是修改提示，则使用 git commit --amend 解决git ignore 文件不生效的问题 git rm -r --cached . git add . git commit -m 'update .gitignore' <<其他>> ExoPlayer在接听电话之后会导致原来设置的 Source 中静音状态消失了导致可能返回 app 续播的时候视频突然有声音 原因是多媒体焦点被通话抢夺之后播放音量被充值，解决方法可参考 github.com/google/ExoP… AndroidStudio 提示 Please select Android SDK 解决手段： File->Project Structure中修改Build tools version 关于 Java 中字符与字节的编码关系认识 // 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()) 关于 emoji 编码的长度计算问题 重点熟悉下 Unicode 编码标识的 emoji 下针对多平面 emoji 的拆分逻辑。 作者：yummyLau 链接：https://juejin.im/post/5eb65d7be51d4525602d393e
参考: 安卓使用FileProvider.getUriForFile出现问题解决 https://blog.csdn.net/luo13788507590/article/details/98615151 Couldn‘t find meta-data for provider with authority https://blog.csdn.net/haiyoumeizhuce/article/details/99981936 解决： Couldn‘t find meta-data for provider with authority xxx.fileProvider https://blog.csdn.net/qq_31877171/article/details/114314648 安卓从6.0适配到7.0以上遇到的FileProvider相关问题踩坑 https://blog.csdn.net/qq_36165311/article/details/108777003 android7.0之后版本，FileProvider适配问题 https://blog.csdn.net/oudetu/article/details/78444097 Failed to find configured root that contains https://www.cnblogs.com/fengfenghuifei/p/10725603.html FileProvider 的使用(Failed to find configured root that contains/storage/emulated/0/DCIM/ ) https://blog.csdn.net/leilifengxingmw/article/details/57405908 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0 https://blog.csdn.net/m0_46366678/article/details/113616407 java.lang.IllegalArgumentException:未能找到包含/storage/emulated/0/Pictures/的已配置根目录 https://cloud.tencent.com/developer/ask/sof/145862 java.lang.IllegalArgumentException: Failed to find configured root that contains ..... https://blog.csdn.net/liu_xueping/article/details/103980700 java.lang.IllegalArgumentException: Failed to find configured root that contains ..... https://blog.csdn.net/songzi1228/article/details/83546066