Android Basic - xapool/xapool.github.io GitHub Wiki

目录

四大组件

  • Activity
  • Service
  • Content Provider
  • Broadcast Receiver

view

  • View 类是所有控件类的父类
  • findViewById 返回值是一个 View, 经过一次强制类型转换(向下转型), 成为具体的控件
  • 监听器:为控件绑定监听器对象,把监听器的对象做为参数传递
  • ImageView 中,scaleType 是缩放类型
  • 关于时间的控件,TimePicker DatePicker 数字时钟 模拟时钟,月份是从零开始
  • SeekBar 和 RatingBar 是 ProgressBar 的子类

布局

  • 布局是可以嵌套的
  • 线性布局, layout_weight 属性为各个子视图分配权重
  • 相对布局,需用 xml 属性指定当前控件相对于另一个控件的位置

Button

  • 按钮的监听器是 OnClickListener,是一个 click 动作

RadioButton

  • 可以对 RadioGroup 和 RadioButton 都设置监听器,RadioButton 的监听器和 Button 一样

CheckBox

  • CheckBox 复选框也是继承于 Button
  • 单选多选框的监听器是 OnCheckedChangeListener,是一个选择动作

ToggleButton

  • ToggleButton 开关按钮具有选中和未选中两种状态,不同的状态可以设置不同的显示文本

Switch

  • 是 KK 新增的一个控件,类似于 ToggleButton,但具有更强的交互性,不仅支持点击,还可以滑动

Toast

  • Toast 的弹出并不影响用户的交换,只是一个简单的提示,不能获得用户焦点
  • toast 的 setGravity 方法,可以设置其显示的位置

Style & Theme

  • 样式是一组属性,可以定义一个控件或窗口的外观和格式,一般定义在 styles.xml 中
  • 样式可以互相继承,parent="@android:style/****, @android 表示继承系统的某样式
  • 继承自己定义的样式,也可以直接在给 style 命名时指定 要继承的样式.新样式
  • style="@style/***,可以直接在控件中引用样式
  • 同在代码中设定控件属性一样,都是采取就近原则
  • 主题主要用来定制样式风格统一的页面设置一些属性,类似无标题,或者标题栏背景统一
  • 定义样式和定义主题都是通过 style 标签,写法相同
  • 两者应用场景不同,style 是在控件或者布局标签中使用
  • theme 在 AndroidManifest 的 Activity 或者 application 标签中使用,指定 android:theme 属性

适配器控件

  • AdapterView,通过适配器加载数据的一系列控件,叫做适配器控件
  • 适配器控件不能直接设置文本什么的,需要适配器来过渡
    1. 准备需要加载展示的数据源,如定义在 xml 中的数据,通过 getResources().getStringArray(R.array.name)
    2. 将数据源的数据加载到适配器中, 如 ArrayAdapter,可以存放 list 或 set 资源 或 数组
    3. 将适配器中的数据加载到控件中, 通过适配器控件对象的 setAdapter(adapter)
  • 数据源可以通过多种方式来定义
    1. xml 文件中定义加载的数据资源,通过 ArrayAdapter.createFormResource() 方法加载资源
    2. 使用 Adapter 对象,把 List 中的数据资源加载到 Spinner 中
  • 在适配器控件的监听器中获取被点击的 item 是哪个对象的方法有几种
    1. 从数据源中获取,若是一个 list 列表,则使用 List 的 get 方法
    2. 从适配器中获取,adapter.getItem(position)
    3. 从 parent 适配器控件中获取,parent.getItemAtPosition(position).toString()
    4. 或直接从适配器控件中获取 适配器控件对象.getItemAtPosition(position).toString()

Spinner

  • 表现为一种列表,主要作用是让用户选择,控件右边一倒三角形
  • 控件内容是格式相同的资源列表,是一个适配器控件
  • 监听器是 OnItemSelectedListener,是对每个 item 的监听

AutoCompleteTextView

  • 是 EditText 的一个子类,也是一个适配控件,也需要自定义数据源
  • 当用户输入时,以下拉形式来显示用户输入的内容的建议项
  • 监听器是 OnItemClickListener

ListView

  • 是一个滚动控件,遵循适配器控件的使用方式
  • 单击监听器是 OnItemClickListener,长按监听器是 OnItemLongClickListener
  • 绘制工作原理,首先适配器会调用 getCount() 方法,获得加载的数据条目个数,一屏放得下时,就调用 getView() 方法全部绘制出来,若放不下,会被存放在一个 RecycleBin 缓冲组件中
  • 然后每当屏幕下方一个 item 被滚动到屏幕的时候,就在调用 getView 方法一个个绘制出来
  • 而顶部滚出屏幕的 item 的布局视图,会被一个个存放在 getView() 方法的 convertView 参数中,
  • 因为每次调用 getView() 时都重新加载实例化布局视图和执行 findViewById 方法,存放的 convertView 也会不断的占用内存,会降低性能
  • 因此,复用 convertView,同时避免了每次重新加载实例化布局和内存的浪费
  • 采用 ViewHolder 类来存放布局视图中的所有控件,避免了每次执行 findViewById,缩短运行时间

RecyclerView

  • balabala。。。
  • 可通过 addOnScrollListener() 方法来对 RecyclerView 的滚动事件设置监听,其监听器有两个可回调的方法
    • onScrollStateChanged : 滚动状态变化时回调,只在滚动开始和结束时回调
    • onScrolled : 滚动时回调,在所有的滚动时间中都在回调

GridView

  • 是一个一表格显示资源的控件,在纵向横向上都可以滚动
  • 使用方式上和 ListView 没有太大的区别

SimpleAdapter

  • ArrayAdapter 的数据源是 List(当然数组也行), 一般适用于数据源里是 String 时
  • 若 List 存储的是某个实体类的对象的时候,一般都需要对 ArrayAdapter 进行继承和覆写
  • SimpleAdapter 的数据源是 List<Map<String, ?>>,存储的是键值对,适合复杂的情况
  • SimpleAdapter 是一个不需要继承覆写 Adapter 类就可以使用自定义布局,键值对等操作
  • 因为在实例化一个 SimpleAdapter 的过程中,就需要把这些作为参数传入

CursorAdapter

  • 避免了遍历 cursor 来获取数据源,使用 CursorAdapter 可以直接使用 cursor 做为数据源
  • SimpleCursorAdapter 是 CursorAdapter 的子类,需要传递数据源 cursor,和 SimpleAdapter 用法相似
  • 当使用 SimpleCursorAdapter 的时候,cursor 这个数据源中必须包含 _id 这个字段
  • 如果对数据库进行查询时,并没有查询 id 这个字段,适配器控件使用这个适配器时就会报错
  • 当然也可是直接继承覆写 CursorAdapter 类,覆写其中的构造函数,newView() bindView() 方法
  • 在 newView() 中返回布局填充器获取到的适配器控件的布局, 在 bindView() 中得到布局中的控件,并从 cursor 遍历取得数据,并绑定到控件上

自定义 Adapter

  • getView 方法返回一个 View 对象,这个对象是每个 item 布局视图
  • 继承和覆写 Adapter,都需要对最重要的 getView() 方法进行覆写
  • 在 getView() 方法中,使用布局填充器,就可以获取自定义布局
View view = LayoutInflater.from(getContext()).inflate(layoutResourceId, parent, false)

LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.list_item, null);

View view = getLayoutInflater().inflate(R.layout.list_item, null);

菜单

多种菜单

  • 相对于 ActionBar, ToolBar 更灵活,不只是固定在屏幕的顶部
  • Menu 的分类,Menu 有 OptionsMenu ContextMenu PopupMenu
  • OptionsMenu ContextMenu, 都是覆写 onCreate***Menu() 方法,PopupMenu 是实例化一个 PopupMenu 对象,然后都是使用菜单布局填充项加载菜单布局 getMenuInflater().inflate()
  • OptionsMenu 是在 ActionBar/ToolBar 右边有一个竖着的三个点,点击弹出菜单项
  • 创建 ContextMenu 菜单,还需要和控件注册绑定,registerForContextMenu(),之后就可以长按控件弹出菜单项,是一个非完全遮挡上一控件的菜单项。通常 ContextMenu 都是和 ListView 或者 GridView 这种适配器控件绑定在一起
  • OptionsMenu 监听器,onOptionsItemSelected()
  • ContextMenu 监听器,onContextItemSelected()
  • PopupMenu 也常和控件配合使用,比如点击一个按钮弹出菜单项,监听器是 OnMenuItemClickListener(), PopupMenu 调用 popupMenu.show() 方法才回显示出来

OptionsMenu

PopupWindow

  • PopupWindow 是一个弹窗,相比于 AlertDialog 来说,PopupWindow 可以自定义显示位置,布局自定义也比较方便,AlertDialog 是非阻塞线程的,而 PopupWindow 是阻塞线程的
  • 根据 PopupWindow 显示的位置,分为两种,一种是下拉式,是在某个控件的下面。一种是浮动式,显示在其他控件的上面
  • 下拉式,mWindow.showAsDropDown(anchor, xoff, yoff, gravity)
  • 浮动式,mWindow.showAtLocation(contentView, Gravity.BOTTOM, 0, 0)
  • 调用这两个方法之后,弹窗才会显示出来

对话框

  • Dialog 对话框有,AlertDialog ProgressDialog

AlertDialog

  • AlertDialog 这个类并不能直接实例化对象,需要使用 AlertDialog.Builder 这个静态内部类
  • 使用这个静态内部类的对象调用 create() 方法可返回一个 AlertDialog 对象,然后就可以调用 Dialog 的一些方法了,如 isShowing() dismiss() 等
  • AlertDialog 的 PositiveButton、NegativeButton 的监听器是 DialogInterface.OnClickListener

CustomDialog

  • 自定义的 AlertDialog 对话框,可以分别自定义对话框标题的布局和内容的布局
  • 分别通过 setCustomTitle(view) setView(view) 来自定义对话框标题和内容布局

ListDialog

  • AlertDialog 的对话框内容是可供选择的项
  • 通过 setSingleChoiceItems() setMultiChoiceItems()方法设置选择列表和监听

Material Design

ToolBar

  • 在 LL 版本时引入,之前的是 ActionBar,相对于 AcitonBar 来说,更加灵活、美观

NavigationView

DrawerLayout

CoordinatorLayout

Activity

Activity

  • 每添加一个 Activity 都需要在 AndroidManifest.xml 中声明注册
  • 共有七个生命周期函数, 除了 onRestart() 方法,其它方法都是两两相对的
  • 只有在内存被回收时,也就是进程被杀掉时,才会重新执行 onCreate()
  • Activity 对象状态:Resumed Paused Stopped Destroyed
  • 系统内存回收的优先级,Destroyed > Stopped > Paused
  • Resumed, Activity 对象处于运行状态,Pasued,另一个 Activity 位于前端,但是本 Activity 还可见,Stopped,另一个 Activity 位于前端,完全遮挡住本 Activity
  • 在本 Activity Pause 到 Stop 这个过程之间,会执行另一个 Activity 的创建,最后才执行本 Activity 的 Stop 方法
  • 创建过程, Create > Start > Resume
  • 非完全遮挡, 创建过程 > Pause > 另一个非完全遮挡的 Activity 创建或恢复过程
  • 完全遮挡,创建过程 > Pause > 另一个完全遮挡的 Activity 创建或恢复过程 > Stop
  • 非完全遮挡恢复过程,非完全遮挡 > Resume
  • 完全遮挡恢复过程,完全遮挡 > Restart > Start > Resume
  • 非完全遮挡消亡过程,非完全遮挡 > Stop > Destroy
  • 完全遮挡消亡过程,完全遮挡 > Destroy
  • 返回桌面,最后一层活动 Pause > Stop > Destroy
  • finish() 方法,就是执行 onDestroy,进程是依然存在的
  • 生命周期函数在多线程开发时,一般需要根据需求进行覆写
  • 在 Activity 中的一个方法中(包括 onCreate)启动一个或多个其它 Activity 时,该方法会被完全执行完之后,这个 Activity 才会进入其它生命周期状态

Intent

  • Intent 是 Android 程序中各组件之间进行交互的一种重要方式
  • 一般被用于启动其它组件,启动时还可以指明当前组件想要执行的动作 action,和要传递的数据 data
  • 如启动活动 startActivity(Intent intent),启动服务 startService(Intent intent)/stopService(Intent intent),发送广播 sendBroadcast(Intent intent)
  • Action,使用 setAction() 方法指明当前组件想要执行的动作或使用 Intent 的构造方法,Intent 有多个构造方法的重载。Android 系统内置了很多 actions,帮助开发者监听或发送一些动作
  • data,使用 putExtra() 系列方法向 Intent 对象中存储数据,从而在不同组件之间传递
  • 在另一个组件中,用 getIntent() 方法,得到启动这个组件的 Intent 实例,并使用其 get***Extra() 系列方法,从 Intent 实例中取出数据,***是数据类型
  • 此外,通过 Intent 还能返回数据给上一个活动。这里启动另一个活动时使用 startActivityForResult(intent, 1) 方法,并覆写 onActivityResult() 方法,来取得要启动的活动返回的数据。这个 1 就是 requestCode,在 onActivityResult() 用于判断数据来源
  • 在另一个活动中,通过一个 Intent 实例,intent.putExtra() 放置数据,然后使用 setResult(RESULT_OK, intent),返回数据,最后使用 finish() 关闭活动。这个 RESULT_OK 就是 resultCode,在 onActivityResult() 中在检查数据来源后,用于判断处理结果是否成功。此外,一般还需要覆写 onBackPressed() 方法,防止用户是按返回键回到上一个活动

显式 Intent

  • 生成一个 Intent 对象,然后调用 setClass 方法设置所要启动的 Activity 或 Service
  • 或直接在 new 的时候利用构造函数传入要启动的 *.class,最后调用 startActivity/startService 方法来进行启动
  • 因为这种 Intent 的 “意图” 非常明显,所以被称为显式 Intent

隐式 Intent

  • 并不明确指出要启动哪一个具体组件,而是设定 action 和 category 等信息
  • 然后交由系统去分析这个 Intent,并帮我们找出合适的具体组件来响应
  • 能够响应这个隐式 Intent 的组件,就是配置了 <intent-filter> 标签中的内容,指定了同样的 <action><category>
  • 只有 <action><category> 中的内容同时能够匹配的上 Intent 中指定的 action 和 category 时,这个组件才能够响应该 Intent
  • android.intent.category.DEFAULT,是一种默认的 category,若 Intent 不添加 category,则会自动添加这个 category 到 Intent 对象中
  • 每个 Intent 中只能指定一个 aciton,所以是 setAction() 方法,但却能指定多个 category,所以是 addCategory() 方法
  • 如发送广播,启动系统文件管理器浏览器等,大都使用的是隐式 Intent
  • 此外 Intent 还有一个 setData() 方法,传递一个 Uri 对象,用于指定当前 Intent 正在操作的数据,而这些数据通常都是以字符串的形式传入到 Uri.parse() 方法中解析完成
  • 对应的在 AndroidManifest 组件的 <intent-filter> 标签中再配置一个 <data> 标签,用于更精确地指定当前活动能够响应什么类型的数据,<data> 标签中主要可以配置以下内容
    • android:scheme,用于指定数据的协议部分,如 http
    • android:host,用于指定数据的主机名部分,如 www.baidu.com
    • android:port,用于指定数据的端口部分
    • android:path,用于指定主机名和端口之后的部分
    • android:mimeType,用于指定可以处理的数据类型,允许使用通配符的方式进行指定
  • 合起来就是一个完整的如,http://www.baidu.com:80/xxxx。只有 <data> 标签中指定的内容和 Intent 中携带的 data 完全一致时,当前活动才能够响应该 Intent
  • 因此,也一般在 <data> 标签中都不会指定过多的内容,如上面的例子,一般也就指定 android:scheme="http",数据的协议部分
  • 除 http 协议外,还有 geo、tel 协议等,协议名和后面的部分都是以冒号隔开,如
// 打开浏览器,并打开网址
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
// 拨打电话界面
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10010"));
startActivity(intent);

PreferenceActivity

  • 是一个实现程序设置界面及参数存储的一个 Activity,使用 addPreferencesFromResource(R.xml.preference) 来加载要显示的界面,xml 文件在 res/xml 目录中,不同于 setContentView
  • 因此这个 preference 的 xml 和 Activity 的布局也不相同,preference 使用了自己的类标签
    • PreferenceScreen 持久化设置界面的顶级容器,代表一屏,里面可以嵌套屏幕点击跳转到另外一屏
    • PreferenceCategory 当前屏的分组容器,用来给设置进行分组分栏
    • CheckBoxPreference、ListPreference、EditTextPreference SwitchPreference、MultiSelectListPreference RingtonPreference 等控件
  • 上述组件都有 key、title、summary、defualtValue 属性,也可以继承这些组件类或 Preference 类来自定义首选项控件
  • 在 xml 中可以直接使用 intent 标签,来启动一个 Activity
  • 这些组件使用 SharedPreferences 进行数据的持久存储,并不需要手动进行提交存储
  • 各组件可通过 dependency 属性标签来设置要依赖的组件
  • 在 PreferenceActivity 中使用 findPreference() 方法获取 xml 中的目标 View
  • 组件的监听器有
    • OnPreferenceClickListener,返回 true 表示点击事件已处理,并且在回调该方法前就以对新值进行了存储
    • OnPreferenceChangeListener,是响应 preference 的值被改变的事件,当返回值为 true 时,才去更新 preference 的值,若返回 false 则需要自行进行对值的存储与否
    • OnPreferenceTreeClickListener 属于 android.support.v7。返回值为 true 代表点击事件已成功捕捉,无须执行默认动作或者返回上层调用链,如不跳转至 xml 中默认的 Intent。若为 false 代表执行默认动作并且返回上层调用链,如跳转至 xml 中默认的 Intent。一般具体使用时需要做一个判断,判断是哪个组件的点击事件
  • 如果一个 Preference 控件同时注册了这三个监听器,则先调用 onPreferenceChange() 的方法,对新值的存储做处理,然后再调用 onPreferenceClick()方法,若该方法返回 true,那就不再调用 onPreferenceTreeClick 方法;若 onPreferenceClick 方法返回的是 false,则继续调用 onPreferenceTreeClick 方法
  • 除了 PreferenceActivity,还有 PreferenceFragment,一般也是结合使用,如跳转到另一屏或多屏设置界面使用 PreferenceFragment
  • PreferenceFragment

返回栈 back stack

  • 返回栈中存放的是 Activity 对象,界面显示的是栈顶端的 Activity 对象
  • 应用中,按返回键时返回的是同一个返回栈的活动,当栈里没有其它活动时,才返回到另一个返回栈
  • 按下返回键,最顶端的 Activity 出栈,执行的是 onDestroy()

Activity 四种启动模式

  • standard singleTop singleTask singleInstance
  • 这四种启动模式,都是和返回栈的栈的管理相关的
  • standard 模式,默认启动模式,每启动一个 Activity 都会创建一个实例,启动本身也是
  • singleTop, 在启动的时候如果栈顶已经是该活动,则不会再创建实例
  • singleTask, 每次启动时都会在返回栈中查找是否存在该活动的实例,若有,直接使用,放到栈顶位置,若无,则再创建实例
  • singleInstance, 启用一个新的返回栈来管理这个活动,这样这个 Activity 实例就不在原先的返回栈当中了

Fragment

  • 碎片,可以理解为存在于 Activity 中的一个或多个子 Activity,来合理利用大屏幕的空间
  • Fragment 可以静态和动态创建,静态创建就是在 Acvivity 的布局中使用 fragment 标签指定 这个 fragment 的布局,并指定 fragment 实现类的类名。把 fragment 当中一个控件来用
  • Fragment 的实现类需要覆写 onCreateView() 方法,返回布局填充器填充的布局视图
  • 动态创建,和静态创建的不同之处在于在 Activity 的布局中使用 FrameLayout 标签代替直接指定 fragment 的标签,使用 FrameLayout 做为 fragment 的一个容器,然后在 Activity 代码中根据需求动态加载 fragment 到这个容器中

动态加载 Fragment 技巧

  • 可以使用限定符,来指定具体使用哪个布局目录下的布局文件
  • 目录名 layout-***, 有 small normal large xlarge,也就是小中大超大
  • 或者屏幕分辨率,ldpi mdpi hdpi xhdpi xxhdpi,或方向,land port
  • 或者使用最小宽度限定符 layout-sw600dp, 表示屏幕宽度大于 600dp 时,使用该目录下的布局文件

fragment 生命周期

  • 共有 11 个声明周期方法,fragment 的生命周期整体上伴随着 Activity 的生命周期
  • Activity 做为 Fragment 的宿主,在创建过程中, Activity 方法先执行,Fragment 后执行. 销毁的过程中,Fragment 方法先执行,Activity 方法后执行
  • 创建过程,在 Activity 执行 onCreate 方法后,fragement 会依次连续执行 onAttach onCreate onCreateView onActivityCreated, 然后 Activity 执行 onStart 后,fragement 执行 onStart,最后 Activity 执行 onResume 后,Fragment 执行 onResume
  • 但是若某个 Fragment 是静态创建的,则这个 Fragment 的 onAttach onCreate onCreateView 先执行,然后在 Activity 执行 onCreate 方法后,这个 fragment 才执行 onActivityCreated 方法,因为静态创建是直接从布局加载的
  • 销毁过程,fragment 执行 onPause 后, Activity 执行 onPause,然后 fragment 执行 onStop 后,Activity 执行 onStop,然后 fragment 依次连续执行 onDestroyView onDestroy onDetach, 最后 Activity 执行 onDestroy
  • 以上声明周期指的是伴随着 Activity 的周期,一个单纯的 Fragment 的周期就是以上周期剥离 Activity 的周期
  • 但是若是通过 addToBackStack() 方法,将某个 Fragment 加入了返回栈当中,那么当这个 fragmnet 被 replace() 时就不会执行 onDestroy onDetach 两个方法,在按下返回键这个 fragment 重回栈顶位置时,依次连续执行 onCreateView onActivityCreated onStart onResume 方法

fragment 间通信

  • 可通过 Bundle 对象, put 要传递的信息,在 Activity 中动态加载 fragment 时使用该碎片实例的 setArguments(args) 方法传递,在 Fragment 中使用 getArguments() 得到一个 Bundle 对象,然后从该对象中取值
  • 在 Activity Fragment 中,分别通过 findFragmentById getActivity() 得到对方的实例

ViewPager

  • 也是一个适配器控件,常用 PagerAdapter FragmentPagerAdapter 两个适配器
  • 是 v4 support 包才有的一个控件,因此在布局中要写出完整的包名
  • 继承 PagerAdapter 抽象类,并覆写 getCount() instantiateItem() destroyItem() isViewFormObject() 方法,数据源泛型一般是 View, 如 ImageView 等,ViewPager 做为一个容器来容纳这些子 View
  • 添加监听的方法 addOnPageChangeListener(), 监听接口是 OnPageChangeListener() 或 SimpleOnPageChangeListener()
  • FragmentPagerAdapter 抽象类是 PagerAdapter 的子类,是结合 Fragment 使用的一个适配器
  • 在这里 Fragment 要使用 v4 包中的,直接用 Fragment 做为数据源泛型,ViewPager 好比是 Fragment 的容器
  • PagerTabStrip 控件是 ViewPager 的导航栏,

Service

多线程

  • MainThread 与 Worker Thread,MainThread 又叫 UI 线程
  • 在主线程之外是不允许修改 View 控件属性的,只有个别的允许修改,如 ProgressBar
  • 对耗时较长的操作或者可能产生阻塞的代码放到 Worker Thread 中,否则会产生 ANR
  • 原则,在主线程中不能够访问网络
  • 创建和启动线程和 Java 中类似

异步消息处理 Handler

  • Handler、Message、MessageQueue、Looper,是 Android 的消息传递和处理机制
  • 经常用于线程间、服务和 Activity 之间的通信
  • Handler 负责把消息(Message)对象放到消息队列(MessageQueue)中去,Looper 负责从消息队列中取出消息对象,Looper 把取出的消息对象交给与该对象对应的 Handler 处理
  • 一个 Message 对象,对应一个 Handler 对象
  • 在主线程或 Activity 中继承 Handler 类(或内部匿名类)并覆写 handleMessage() 方法,在 WorkerThread 或服务中使用这个继承类对象的 sendMessage(msg) 发送消息
  • 这个继承类中被覆写的 handleMessage 方法,会自动处理这个继承类的实例化对象发送来的消息
  • 若是从主线程往子线程发送消息,需在子线程中来处理消息(可使用匿名内部类来覆写 handleMessage), 在主线程中发送消息。不同的是需要在实例化 Handler 对象前准备 Looper.prepare() 对象和之后执行 Looper.loop()
  • Handler 的 post(Runnable r) 方法,可以把 Runnable 的线程体从子线程传到主线程中执行
  • mHandler.post(Runnable r) 把线程体加入消息队列后,要等到程序其它代码执行完成后,最后才会被取出执行,也许是编译器优化改变了代码执行顺序
  • 每个线程中,只会有一个 MessageQueue 和 Looper 对象,这两个总是在同一个线程当中,因为 Looper 在创建的时候,构造函数中就创建了 MessageQueue
  • Handler 创建时会采用当前线程的 Looper 来构造消息循环系统,main 线程在创建的时候系统就自动为其创建了 Looper, 但是子线程是没有 Looper 的,所以在子线程中实例化 Handler 对象时,要首先在线程体中 Looper.prepare() 创建 Looper 对象,否则就会报找不到 Looper 的错误

AsyncTask

  • 抽象类,对 Handler 做好了封装,需要去继承和覆写其中的几个方法来完成对任务的定制
  • 更进一步简化与 UI 线程进行交互的后台线程的实现
  • 缺点,对错误的处理没有更好的办法。当设备旋转(Activity 会重启)或退出 Activity 时,Task 还没执行完成时,会造成空指针异常
  • 因此你需要确保保持一个 AsyncTask 的引用,并亲自管理 AsyncTask 的取消操作,当 Activity 正在被销毁的时候调用 AsyncTask.cancel() 取消这个 Task,而且要在 onPostExecute() 里面更新 UI 的时候,确保 Activity 是在一个可达状态
  • 为了简单应对上面的缺点,可以使用 AsyncTaskLoader

AsyncTaskLoader

  • Abstract Loader that provides an AsyncTask to do the work
  • 一般使用在数据源处于不断更新并且请求刷新数据源是个耗时操作的情况下还需要 UI 去同步更新相关数据的场景
  • 拥有一个数据源改变的通知机制,当数据源变化时会发出通知,自动加载数据,不需要手动再次查询
  • 因为 AsyncTaskLoader 跟随 Activity 的生命周期,不必亲自管理 Task 的取消操作等
  • 缺点,也因此只能在 Activity 或 Fragment 中使用,并且不能使用 AsyncTask 的 progress
  • 最终方案,使用 RxJava
  • 使用方法,使用过程中要注意导入的包. 假设这里的泛型是个 Cursor
    • 实现 LoaderCallbacks 接口, 并实现其中的三个方法,在 onCreateLoader() 方法中使用第三步的实现类实例化并返回 Loader 对象,在 onLoadFinished() 方法中,对得到的数据源进行处理
    • 初始化 Loader,getLoaderManager().initLoader(),代码就是从此处开始执行的
    • 继承并覆写 AsyncTaskLoader 类

Service

  • Service 不是一个单独的进程和线程,也是工作在主线程之中的,如若避免 ANR,也需要创建新线程
  • 同 Activity 一样每个服务都需要在 AndroidManifest.xml 中声明注册
  • 服务中最常用的生命周期方法, onCreate onBind onStartCommand onDestroy
  • 也可以在 Manifest 中指定 android:process,让服务运行在一个新的进程中

startService

  • 启动服务的方式有两种,一种是 startSevice 一种是 bindService
  • onCreate 方法只有在服务第一次被创建时才会调用,onStartCommand 在每次调用 startService 启动服务时都会被调用,但每个服务都只会存在一个实例. 一般把创建新线程的代码写在其中
  • 采用此 startSevice 启动服务,并不会执行生命周期中的 onBind 方法, 生命周期是 onCreate ——> onStartCommand --> onDestory
  • 服务的启动和停止也是借助 Intent 来实现,由 startService(intent) stopService(intent) 方法来控制,两个方法最后调用的是服务这个类中覆写的生命周期方法
  • stopService 调用的是 onDestroy,可以在其它组件中调用 stopService 方法,来停止服务
  • 服务类自己停止的方法是,任何位置调用 stopSelf() 方法
  • 服务启动后即可在后台无限期运行,即使启动它的组件已被销毁也不受其影响,直到其被显式停止

bindService

  • 绑定服务用于活动和服务之间进行通讯,绑定过程:
    1. 首先在 Service 中定义一个 Binder 的实现类,里面添加要用到的方法
    2. 在 onBind 方法中返回 Binder 实现类的对象
    3. 在 Activity 中调用 bindService() 方法, 在调用这个方法的时候需要传入一个 ServiceConnection 接口实现类的一个对象
  • 仅当与另一个应用组件绑定时,绑定服务才会运行,回调 onBind() 方法
  • 当绑定成功后,就可以在 ServiceConnection 实现类中的 onServiceConnected() 方法获取到一个 Binder 实现类的对象,使用该对象就可以调用 Binder 实现类中的方法,实现通讯。在 Binder 实现类中可以添加一个返回 Service 实例的方法,这样就能调用 Service 中的方法了
  • 采用 bindService() 方法启动服务,并不会执行生命周期中的 onStartCommand 方法, 生命周期是 onCreate -> onBind -> onUnbind -> onDestory,若服务已经通过另种方式被启动,则不会再执行 onCreate 方法
  • 调用 bindService 后,在同一个活动中再调用 bindService 都不会有在执行任何方法了. 如果在另一个活动中调用 bindService, 不会再执行 onBind 方法,只会执行 onServiceConnected() 方法,证明获取到的是同一个 Binder 实现类的对象
  • 调用 unbindService,会先后执行 onUnbind onDestory 方法
  • 采用这种方式启动服务后,当所有与其绑定的组件都取消绑定(组件都被销毁或者是其都调用了unbindService()方法),服务才会停止(执行 onDestory)。
  • 若一个服务同时调用了 startService() 和 bindService() 方法进行启动,需同时调用 stopService() 和 unbindService() 方法(都不分先后),onDestory 方法才会执行,单调用 unbindService 只会执行 onUnbind 方法,若此时再调用 bindService() 方法,则直接执行 ServiceConnection() 方法,但是若 onUnbind 返回的是 true, 则会调用 onRebind 方法,再执行 ServiceConnection() 方法
  • 可以既使用 startService 又使用 bindService 方法来启动一个服务,这样这个服务既可以和组件通讯,又可以让组件被销毁后,服务依然运行,如后台下载服务
  • 注意要覆写 onDestory 方法,在里面执行 unbindService() 方法来释放资源,否则组件被销毁时会报溢出异常
  • 注意,四大组件中, Broadcast Receiver 无法直接启动和绑定服务
  • 绑定服务用于活动和服务之间进行通讯,除了实现 Binder 类,还可以使用 Messenger 类,使用 AIDL 来通讯,实际上 Messenger 底层采用内的也是 AIDL

Service 生命周期

  • 因此,服务的生命周期被分为三种情况:
    1. startService() 方法启动的服务
    2. bindService() 方法启动的服务
    3. 两者都使用的方式启动的服务

IntentService

  • IntentService 是 Service 的一个子类,覆写了服务生命周期中的几个方法
  • 可以创建一个异步、会自动停止的服务,防止程序员忘记开线程和停止服务
  • 使用该服务,需继承 InternService,并覆写其中的 onHandleIntent() 方法
  • onHandleIntent() 已在子线程中运行,在服务结束时会自动停止

transact onTransact

  • 在 Service 中 Binder 的实现类里,覆写 onTransact() 方法
  • 在 Activity 中,绑定服务后,调用 Binder 实现类对象的 transact() 方法
  • 这样通过 transact() 传递参数到 onTransact() 中,而 transact() 则等待 onTransact()方法的回复, 这样就完成了从 Activity 到 Service 的一个请求响应过程
  • 传递的数据存储在一个 Parcel 容器中

IPC

  • 进程间通信

Binder

  • Binder 是 Android 的 IPC 的方式,贯穿于系统各个层面,整体采用 C/S 架构
  • 实质就是把对象从一个进程映射到另一个进程中,从而透明的实现跨进程的调用
  • 下面说到的 Binder 都是指应用层的,在 Service 中使用。有普通 Binder 和 跨进程 Binder, 普通 Binder 并不涉及 IPC

序列化反序列化

  • 将一个对象转换成可存储或可传输的状态,序列化后的对象可以在网络上进行传输,或者存储到本地
  • Android 中有 Serializable 和 Parcelable 两种方式,这两个类都是接口
  • Serializable 是 Java 中常用的方式,使用简单但开销大,需要大量的 IO 操作,适用于对象持久化存储和序列化后通过网络传输。序列化反序列化后是不同的两个对象
  • Parcelable 是 Android 特有的,使用起来比较麻烦,需要实现其中的 writeToParcel 等其它方法,但是效率高,因为是在内存中进行序列化和反序列化,使用的是连续的内存空间,传输也大都只是在内存中进行传输。在系统中如 Intent 把一个对象当做数据 put/get 时,采用的就是此方式
  • Parcelable 序列化和反序列化时,如果数据本身是 IBinder(Binder 本身就是 IBinder 的实现类) 类型,那么反序列化的结果就是原对象,而不是新建的对象
  • 也就是普通 Binder 和跨进程的 Binder, 在所有客户端获取到的是同一个 Binder 实现类的实例。但是,在实现类的方法中传递的引用参数指向的对象,在序列化反序列化之后是不同的两个对象,无论这个引用是不是 Biner 类型

AIDL

  • Android Interface Definition Language,Android 接口定义语言
  • 是用来定义一个接口,后缀是 .aidl,SDK 会自动为此接口生成一个 Binder 类
  • 默认支持,基本数据类型、String 类型、CharSequence 类型、List 类型、Map 类型
  • 其中 List 类型可以使用泛型,而 Map 类型不可以。两个集合中的所有元素也必须是 AIDL 支持的
  • 除了这些默认支持的数据类型,使用其它的类型都需要导入,哪怕是在同一目录下的类,这点和 java 不一样。而且这些类得是 AIDL 文件,或者是是实现了 Parcelable 接口的实现类
  • 这个实现了 Parcelable 接口的实现类需对应一个 AIDL 文件来声明,这样其它 AIDL 文件才可以使用这个类。需实现 Parcelable 接口是为了让其对象能都序列化和反序列化,然后才能在跨进程的内存之间进行传输。默认支持的基本数据类型其实本身就已实现了该接口
  • String ,CharSequence 的定向 tag 默认且只能是 in
  • 在 AIDL 定义方法接口中,除了默认支持的基本数据类型和 AIDL 类型的接口类,其它的类型做为抽象方法的参数时,需要在参数列表最前面加上定向 tag
  • 定义一个 AIDL 性质的的接口(使用了非默认支持的数据类型时):
    1. 需要该类实现 Parcelable 接口,并且需要一个 AIDL 文件来声明这个类可被其它 AIDL 文件使用
    2. 使用 AIDL 写一个接口,要导入第一步的数据类型,才可使用并做为参数传递
  • 接口定义好后,在服务端实现该接口(一般采用匿名内部类,写在服务中),在 onBind() 方法里返回该实现类的对象,然后在客户端进行 binService(),获取到这个实例(客户端也需要把 AIDL 相关文件拷贝到自己源码目录中),就达到了跨进程通信的目的,这里和 bindService 差不多
  • 总之,AIDL 是一种描述语言,来描述进程间的接口,用来 IPC。 SDK 会自动为此接口生成对应的 Binder 类(也是一个接口),在服务端实现这个 Binder 类,在客户端进行调用
  • 我们也可以完全不使用 AIDL,完全手动写一个用于跨进程的 Binder 实现类,AIDL 只不过是谷歌简化了我们的过程

Binder AIDl Messenger 比较

  • Messenger 传递的是消息,是轻量级的 IPC,无法实现方法的调用,而且服务端无法并发处理请求,只能串行的一个一个的来处理,若客户端有大量的并发请求时,Messenger 就不合适了
  • Messenger 底层实现还是 AIDL
  • 使用了 AIDL 的 Binder 相对于普通 Service 中的 Binder(只是继承 Binder 类,这种不涉及进程间通信),比较重型,适用于多个客户端,多线程的情况下使用。因为这种情况下服务端或客户端 Binder 中的方法运行在一个 Binder 线程池中,每次请求(调用)的方法都是在线程池中一个单独的线程中执行,也因此接口实现中的方法和共享资源要考虑同步的问题
  • 同时,发起调用的客户端当前线程会被挂起,处于阻塞状态等待服务端进程返回数据,因此若服务端方法执行比较耗时,应避免在 UI 线程中发起调用请求,避免 ANR

网络访问

  • 使用 HttpURLConnetcion、OkHttp 等库进行网络访问
  • 网络访问请求应放到子线程中去
  • 网络访问需要在 Manifest 中申请权限
  • 现在使用 Volley 和 Retrofit

解析 XML

  • 有几种解析方式, Pull 解析、SAX 解析、DOM 解析 ,Pull 解析是一次完全加载XMl到内存
  • SAX 边解析边加载,由事件处理函数做相应动作,可以随时终止解析,但相比于 Pull 用法比较复杂
  • SAX, Simple API for XML
  • SAX 常用接口 ContentHandler 定义了事件处理函数,用来解析 XMl, 但该接口方法众多,若直接实现该接口,写起来比较麻烦
  • DefaultHandler 是 ContentHandler 接口的一个实现类,定义了很多空函数,继承这个类就避免了实现很多不常用方法,被称为适配器模式
  • 常用方法 startDocument() endDocument() startElement() endElement() characters()
  • 程序执行完 startElement(),也就是解析完节点标签时,会自动执行 characters() 来解析该节点标签内容,然后执行 endElement()。过程循环执行,直到解析完所有

解析 JSON

  • JavaScript Object Notation, 相比于 xml 体积更小
  • [{},{}] 每一个大括号代表着一个 JSON 对象,中括号代表着这些对象所组成的 JSON 数组
  • Gson 解析,最外层是大括号时,需要创建一个与 JSON 数据对应的 JavaBean 实体类,用作存储需要解析的数据,然后使用 GSON 对象的 fromJson() 方法, 就把JSON 数据中最外围的这个对象的值传递给了 jb 对象,最后通过 jb 对象取值
JavaBean jb = gson.fromJson(String json, JavaBean.class) 
  • 如果 JSON 是一个对象数组,也就是最外层是中括号时,需要借助 TypeToken 将期望解析成的数据类型传入到 formJson()
Type listType = new TypeToken<List<JavaBean>>(){}.getType();
List<JavaBean> jb = gson.fromjson(jsonData, listType);
  • 这样就把对象数组中每个对象的值传递给了一个 JavaBean 对象,list 中存储的是一个个 JavaBean 对象,最后 foreach 遍历这个 list,取出每个对象, 通过对象取值
  • 如果最外层是大括号,里层又有中括号大括号嵌套的话,就得上面两种情况结合使用

存储

  • 持久化数据存储
  • 存储方式有,SharedPreference存储 内部存储 外部存储 数据库存储 网络存储
  • 内部存储指的是存储在自己的 data 分区,外部存储是 sdcard 上
  • 数据存储应该根据需求判断是否需要放到子线程中,避免 ANR
  • 同网络访问一样,外部存储访问 sdcard 时,需申请相应权限

SharedPreference

  • 使用键值对的方式来存储数据,是一个 map 结构的 xml 文件,放在自身应用数据目录下 shared_prefs 的目录中
  • 一般通过 Context 类的 getSharedPreferences() 等方法,实例化 SharedPreference 的对象,此外还有 Activity 类的 getPreferences() 或 PreferenceManager.getDefaultSharedPreferences() 方法来获取对象,PreferenceManager.getDefaultSharedPreferences() 一般用于 PreferenceActivity
  • SharedPreference 对象的 edit() 方法可返回一个 SharedPreferences.Editor 对象
  • 通过这个对象可以 put 数据,最后要 editor.commit() 数据
  • SharedPreference 对象的 get***() 方法可通过键来取出值

文件存储

内部存储

  • openFileOutput() openFileInput() 方法,分别返回一个 FileOutputStream FileInputStrem 对象,就可以用 read/write 方法读写文件了。 deleteFile(fileName), 删除文件,返回一个布尔值
  • 文件保存在自身应用的 files 目录下

外部存储

  • 外部存储属于危险权限,需要在代码中动态申请外部存储写的权限
  • 使用 Java 的 IO 流存储操作

数据库存储

  • Android 的 SQLiteOpenHelper 帮助类是一个抽象类,需要继承和覆写
  • 覆写其中的 onCreate() onUpgrade() 方法,和构造方法
  • 然后使用 SQLiteOpenHelper 的实例调用 getReadableDatabase()/getWriteableDatabase() 方法就能创建数据库了,并返回一个 SQLiteDatabase 对象
  • 此时会执行重写的 onCreate() 方法,通常在这里去处理一些创建表的逻辑
  • 数据库存储在 /data/data/<pakage_name>/database 目录下
  • 构造函数会通过传递过来的数据库版本,来决定是否执行 onUpgrade() 方法
  • 增删改查中除了查询,其它的都可以通过 db.execSQL(sql) 执行 SQL 语句来实现,因为该方法并无返回值
  • 增删改查分别对应 SQLiteDatabase 的 insert delete upate query 方法, 并且都有返回值,创建数据库是通过 execSQL 方法来执行 SQL 语句
  • 查询时的返回值是一个 cursor,需要对该返回值进行遍历
  • 添加更新数据,需要一个 ContentValues 的对象,其实是一个 HashMap, put 数据,最后执行 insert 或 update 方法,涉及多条数据时要一条一条的来并清空 HashMap,使用 clear() 方法

Content Provider

  • 实现跨程序共享数据,通过内容提供器对其数据提供外部访问接口
  • 其底层实现也是 Binder

ContentResolver

  • 提供内容的叫内容提供者,接受内容的叫内容解析者 ContentResolver
  • 使用 getContentResolver() 方法得到一个 ContentResolver 的实例
  • 这个实例可以使用类似数据库的 CRUD 方法,需传入内容提供者的 URI,也就是 Content URI
  • 同样,使用 query() 方法可返回一个 cursor, 来获取数据

自定义内容提供者

  • 继承并覆写 ContentProvider 类,并覆写其中的抽象方法,共有六个,CRUD 和 onCreate() getType()。这六个方法,除了 onCreate() 运行在主线程中外,其它方法都运行在 Binder 线程池中
  • 通常在 onCreate() 方法中完成对数据库的创建和升级操作
  • 在 getType() 方法中根据传入的 URI 参数返回相应的 MIME 类型(媒体类型),如果不关注这个方法,可直接返回 nul 或 */*
  • 做为四大组件之一,需要在 AndroidManifest 中注册,指定 URI 的 authority
  • 实例化一个 UriMatcher 静态的对象,在静态代码块中使用 UriMather 的 addURI() 方法,把 authority path 和自定义 code 传递过去
  • 这样在 CURD 这几个方法中,就可以通过 UriMatcher 的 match() 方法返回的自定义代码来匹配具体要访问的是哪个 path(这个 path 可能是不同的表,也可能是同一个表中不同的字段)

CursorLoader

  • 是 AsyncTaskLoader 的子类
  • 和 AsyncTaskLoader 用法类似,不过 CursorLoader 的针对的就是 Cursor 数据源
  • 就不需要如 AsyncTaskLoader,来继承和覆写这个类
  • 直接在 onCreateLoader() 方法中实例化并返回 CursorLoader 的对象,在实例化过程中传入 uri, 查询参数. 由 CursorLoader 类来完成异步查询

Broadcast

  • 广播是 Android 对一些系统事件进行广播
  • 广播发送者并不关心接受者是否接收到,也不管接收者接收到后作何处理
  • 广播分为有序广播和无序广播
  • 接收者被称为广播接收器 BroadcastReceiver
  • 系统内置了很多的 Broadcast Acitons,用于帮助开发者监听接收系统内发生的各种事件

无序广播

有序广播

BroadcastReceiver

  • BroadcastReceiver,用于监听被广播的事件(Intent)
  • 实现广播接收器需要继承 BroadcastReceiver 并覆写其中的 onReceive() 方法;做为四大组件之一,每个接收器还需要进行注册,注册方式又分为静态注册和动态注册
  • onReceive() 方法中,可以通过 Intent 的形参 intent.getExtras() 方法等到一个 Bundle 实例,通过该实例可以获取广播的一些具体内容

静态注册

  • 静态注册,就是指在 AndroidManifest 中,如 Activity 一样进行注册,并在 <receiver> 标签中设置 <intent-filter> 来过滤要接收的事件
  • 发送广播,直接使用 sendBroadcast(Intent intent),intent 对象需要设置同样的 action
  • 这种注册方式,在应用被正常关闭后,也能接收到广播(需要应用打开过一次,首次安装未打开过的应用是接收不到广播的)

动态注册

  • 动态注册,是在代码中使用 registerReceiver(receiver, filter)/unregisterReceiver(receiver) 进行随时的注册。filter 是一个 IntentFileter 对象,设置 action、category、data 等属性,用来过滤要接收的广播
  • 缺点是因为注册在代码中,无法实现开机启动就接受广播等,但优点是灵活,可以用来更新 Activity 的 UI

生命周期

  • BroadcastReceiver 继承类的实例,在每次接收到广播后,都会重新生成一个实例,在 onReceive() 方法执行完成后,该实例不在被使用

动态权限

  • Android 6.0 将权限分为普通权限和危险权限
  • 危险权限,如 联系人相关、电话相关、短信相关、日历相关、相机、传感器、地理位置、麦克风、外部存储
  • 使用危险权限,是属于动态权限,需要在代码中动态申请

距离单位

  • px,pixel, 像素单位
  • dpi, dots per inch, 对角线每英寸上的像素点数,表示屏幕的细腻度
  • dp, dip, device independent pixels,设备无关像素
  • sp, 放大的像素,与 dp px 没有直接的关系,用于字体,可随系统字体的设置改变
  • 外边距是控件和外面的距离,内边距是指控件内容和控件的四周的距离

Context

  • 背景,上下文,Android 中一般指的是一个 Activity 或 Application 对象
  • Activity 是 Context 子类,也就是说,所有的 Activity 对象都可以向上转型为 Context 对象
  • 在 Activity 中要传递 Context 时,一般都是使用 this,或者 ***Activity.this
  • 在其他地方使用时,一般使用 View.getContext() 来获取 Activity 对象,Activity.getApplicationContext() 来获取 Application 对象
  • 也可以继承 Application 类并覆写其中的 onCreate() 方法,添加自己的 getContext() 静态方法,最后在 AndroidManifest 中的 application 标签中指定 'android:name'。调用自己的方法来获取 Context
  • 总之,凡是跟 UI 相关的,都应该使用 Activity 做为 Context 来处理;其他的一些操作,Service、Activity、Application 等实例都可以,当然了,得注意 Context 引用的持有,防止内存泄漏

Android Studio 快捷键

  • Cmd + delete 删除行
  • Alt + delete 删除单词
  • Alt + 左右箭头 在单词间移动光标, + shift 是选中
  • Cmd + 左右箭头 行首行尾移动,+ shift 是选中
  • Alt + 上 选中块,下是取消选中
  • Cmd + alt + l 格式化代码
  • Ctrl+ Alt + O 优化导入的类和包
  • Cmd + shift + enter 补全
  • Cmd + 点击 在新窗口中查看方法定义
  • Ctrl + J 在当前窗口查看方法文档
  • Cmd + P 查看方法参数
  • Cmd + Y 在当前窗口查看方法定义
  • Ctrl + O 覆写方法,或查看类中所有方法
  • Ctrl + I, 只覆写抽象方法
  • Cmd + Alt + T 选中行进行包裹
  • Cmd + Shift + Delete 移除包裹
  • Cmd + N 自动生成代码
  • Alt + T 翻译,由第三方插件完成
  • Cmd + Shift + z Redo
  • Cmd + . 展开收起代码
  • shift + enter 在当前行下面添加一行
  • Cmd + Alt + 回车,在当前行上面添加一行
  • Cmd + Shift + Delete 定位到上一次编辑的位置
  • Cmd + Alt + 左右 ,定位到上一次浏览的位置
  • Cmd + U 定位到父类或父类中的实现
  • Cmd + Shift + E 最近修改的文件
  • Cmd + E 最近访问的文件
  • Alt + F1 用 Finder 打开文件
  • Alt + 上/下 调整选择范围
  • Ctrl + G 多处选择
  • Alt + 鼠标拖动 块选择
  • Cmd + D 复制当前行,并粘贴到下一行
  • Alt + Shift + Up/Down 上下移动行
  • Cmd + Shift + Up/Down 移动方法
  • 输入 myList.for, 然后按下 tab 键,自动生成 foreach 代码
  • Ctrl + Shift + J 合并行和文本
  • Ctrl + Alt + Space 手动唤出自动补全
  • Ctrl + Shift + Space 手动唤出只匹配的自动补全
  • Alt + enter 自动修正,可用于导入包,强制类型转换
  • Ctrl + shift + 斜杠 长注释
  • 输入"/**",回车键,自动生成方法的注释模板
  • Cmd + Alt + F 本地变量成员变量转换
  • 双击 Shift , everywhere search
  • Cmd + Shift + U,大小写转换
  • Cmd + 回车, 拆分行,添加字符串
  • Cmd + Shift + D, 唤出 Exynap 插件
  • Cmd + J 自动代码
  • keyboard shortcuts

查找

  • F4 跳转至该变量定义的位置
  • Alt + F7 查看当前元素在工程中的引用
  • Cmd + F7 查看当前元素在当前文件中的引用
  • Cmd + Alt + F7 窗口化查看当前元素在工程中的引用
  • Cmd + Shift + F7 高亮显示匹配的字符
  • Ctrl + Alt + H,显示一个方法被调用的所有可能路径
  • Ctrl + H 查看类的继承关系
  • Ctrl + O 查看类中所有方法,覆写方法
  • Cmd + F12 显示整个文件结构,方法和参数
  • Cmd + Alt + B 跳转到实现
  • Shift + F2 跳转到下/上一个错误语句处
  • Cmd + Shift + A 查找某个操作
  • Cmd + Shift + O 查找文件

调试技巧

  • Menu → Analyze → Analyze Data Flow to Here 或 Cmd + Shift + A 查找此操作,分析选中的变量、参数或字段传递到此处的路径
  • 处在断点状态时,光标放在变量处,按Alt + F8,即可查看变量的内容或按住 Alt 键,单击字段或变量
  • 在断点上右键,取消Suspend的勾选,然后勾选上Log evaluated Expression,并在输入框中输入你要打印的日志信息,避免了修改代码

版本和 API 对应

⚠️ **GitHub.com Fallback** ⚠️