Android开发二三事
应用组件
界面 (Activity)
界面是与用户交互的入口点
Activity 生命周期
当用户浏览onCreate()
onStart()
onResume()
onPause()
onStop()
onDestroy()
其中
, onCreate()
必须实现 它会在系统创建 , Activity 时触发 。
服务 (Service)
服务是一种在后台运行的组件
数据传输 (Intent)
An intent is an abstract description of an operation to be performed.
内容提供程序 (Content Provider)
内容提供程序管理一组共享的应用数据
度量单位
DP/DIP(Device-Independent Pixels)
非设备依赖的
SP(Scaled Pixels)
可根据用户设置的字体大小缩放
手机的尺寸即屏幕对角线长
1inch = 2.54cm
。
DPI(Dots Per Inch)
对角线每英寸的光点个数
PPI(Pixels Per Inch)
对角线每英寸的像素点个数
大部分时候
DPI 与 PPI 可以划等号 。
实现
字符串资源本地化
于string.xml
1 | <string name="bar_text">Say something to %s.</string> |
下例将参数填入bar_text
1 | String str = getString(R.string.bat_text, et.getText().toString()); |
右击
string.xml
->Open Translations Editor
可打开翻译编辑界面 。
自定义选择器
通常置于res/drawable
1 | <selector ... > |
使用时如下例
1 | <ImageView android:src="@drawable/selector" ... /> |
高级对话框
以日期对话框为例
1 | val date = DatePickerDialog(this@MainActivity, |
菜单
1 | public boolean onCreateOptionsMenu(Menu menu) { |
其他菜单如上下文菜单ContextMenu
)
1 | registerForContextMenu(textView); |
底部导航 BottomNavigationView
布局
1 | <android.support.design.widget.BottomNavigationView android:id="@+id/navigation" |
选项卡
通常置于res/menu
1 | <menu ... > |
配置
下例使用底部导航作Fragment
1 | navigation.setOnNavigationItemSelectedListener { |
SlidingDrawer
(DEPRECATED!)
SlidingDrawer
(DEPRECATED!)布局
1 | <SlidingDrawer android:content="@id/content" |
配置
下例以ArrayAdapter
1 | content.adpter = ArrayAdapter( |
Fragment
的使用
配置 PagerAdapter
The PagerAdapter
that will provide fragments for each of the sections.
We use a
FragmentPagerAdapter
derivative(派生物), which will keep every loaded fragment in memory.
A FragmentPagerAdapter
returns a fragment corresponding to one of the sections / tabs / pages.
1 | inner class SectionsPagerAdapter(fm: FragmentManager): |
It may be best to switch to a
FragmentStagePagerAdpter
if this become too memory intensive(内存密集).
A PlaceholderFragment
contains a simple view.
1 | class PlaceholderFragment : Fragment() { |
配置 ViewPager
实现翻页切换
1 | <android.support.v4.view.ViewPager android:id="@+id/container" |
Set up the ViewPager
with the sections adapter.
1 | container.adapter = SectionsPagerAdapter(supportFragmentManager) |
指示器
1 | <android.support.design.widget.TabLayout android:id="@+id/tabLayout" ... /> |
Set up in onViewCreated()
.
1 | tabLayout.setupWithViewPager(container) |
在 Fragment
中嵌入子 Fragment
写于重载函数
onViewCreated
中 注意不是于 , onCreateView
中 。
1 | viewPager.adapter = object : FragmentPagerAdapter( |
ListView
的使用
渲染
使用ViewHolder
findViewById()
ListView
的每个项都将调用 getView()
。
下例使用
row.findViewById()
而不是 this.findViewById()
否则将报错 , 是因为所取控件不在本 , activity 中 即使是使用 , kotlin 的映射也无法借此获取这样的控件 。
ERROR: IllegalStateException
: ... must not be null.
1 | override fun getView(position: Int, convertView: View?, |
内容更新
1 | myAdapter.notifyDataSetChanged() |
内容辨识
以上下文菜单为例
1 | override fun onCreateContextMenu(..., ..., |
去除项目之间的分隔线
1 | <ListView android:divider="@null" ... /> |
去除点击效果
1 | <ListView android:listSelector="@android:color/transparent" ... /> |
ListView
嵌套 ListView
外层ListView
ListView
match_parent
ListView
onMeasure
1 | override fun onMeasure(...) { |
ScrollView
嵌套 ListView
滚动进度初始不位于最顶部
ScrollView
只允许有一个子控件 。
1 | <ScrollView ...> |
NestedScrollView
/ScrollView
嵌套 Layout
内层Layout
1 | <ScrollView android:fillViewport="true" ... > ... </ScrollView> |
WebView
页面
首先需要获得网络权限
1 | <user-permission android:name="android.permission.INTERNET" /> |
重写shouldOverrideUrlLoading()
1 | // In onCreate() |
更改返回键功能
1 | override fun onKeyDown(keyCode: Int, event: KetEvent): Boolean { |
下拉刷新布局 SwipeRefreshLayout
This layout should be made the parent of the view that will be refreshed as a result of the gesture and can only support one direct child.
子控件必须是可滚动的ListView
ScrollView
1 | public class MainActivity extends Activity implements |
传感器
以重力加速度传感器为例
1 | class MainActivity: ..., SensorEventListener { |
SharedPreferences
保存软件参数
获取
1 | val sharedPref = getSharedPreferences("name", MODE) |
有以下三种Context.MODE
PRIVATE
只能被自己的应用程序访问: ; WORLD_READABLE
能被其他应用读取: ; WORLD_WRITEABLE
能被其他应用读取和写入: 。
写入
1 | val editor = sharedPref.edit() |
读出
1 | val str = sharedPref.getString("key", defaultVal) |
Android 和 JS 的互相调用
1 | // In MainActivity.kt |
1 | // In onCreate() |
1 | <!-- In test.html --> |
使用 Volley 获取 JSON 数据
1 | val queue = Volley.newRequestQueue(context) |
异步任务 AsyncTask
任务创建
Params
传入后台任务的参数类型: ; Progress
显示进度的单位类型: ; Result
返回值类型: 。
1 | class TestTask extends AsyncTask<Params, Progress, Result> { |
其他可选实现的方法如下
onProgressUpdate()
切回主线程显示进度: 在调用, publishProgress(n)
; onPreExecute()
执行任务之前的操作: ; onCancelled()
通过: task.cancel(true)
。
调用
1 | new TestTask().execute(); |
欢迎界面
延时跳转
1
2
3
4
5
6
7
8
9
10
11
12class SplashActivity : AppCompatActivity() {
val handler by lazy {
Handler() // Android.os.Handler
}
override fun onCreate(...) {
...
handler.postDelayed({
startActivity<MainActivity>() // Anko(DEPRECATED!)
finish()
}, 2000)
}
}全屏风格
1
2
3
4<style name="AppTheme.FullScreen">
<item name="windowNoTitle">true</item>
<item name="android:windowFullScreen">true</item>
</style>在
SplashActivity
。 1
2<activity android:name=".SplashActivity"
android:theme="@style/AppTheme.FullScreen" ... />
第三方资源
Anko(DEPRECATED!)
DSL(Domain Specific Language)
领域特定语言
Kotlin
1 | implementation "org.jetbrains.anko:anko:$version" |
下例快速弹出提示
1 | toast("Hello!") |
下例实现跳页
1 | startActivity<NextActivity> ( |
下例直接在
Anko Layout Preview
能通过这种方式预览界面 更新 , Anko 预览需要重新 build
项目 。
在
onCreate()
中需要用 ActivityUI().setContentView(this)
设置界面 。
1 | class ActivityUI : AnkoComponent<MainActivity> { |
下例自定义DatabaseHelper
Anko
提供更安全的 ManagedSQLiteOpenHelper
过去则使用 , SQLiteOpenHelper
。
1 | class DatabaseHelper(ctx: Context, version: Int = CURRENT_VERSION): |
Ticker
数字变化动画控件
1 | implementation "com.robinhood.ticker:ticker:$version" |
创建TickerView
1 | ticker.setCharacterLists(TickerUtils.provideNumberList()) |
CircleImageView
快速制作圆形图片控件
1 | implementation "de.hdodenhof:circleimageview:$version" |
KFormMaster
快速制作表单
1 | implementation "com.thejuki:k-form-master:$version" |
下例创建邮箱登录界面
1 | form(this, recycleView) { |
Sofia
状态栏/导航栏快速更改
1 | implementation "com.yanzhenjie:sofia:$version" |
下例快速实现浸润式状态栏
1 | Sofia.with(this).statusBarBackgroundAlpha(0).invasionStatusBar() |
Android Studio 的使用
项目名称变更
- 重命名文件夹
; - 打开工程
在显示设置中反选, Compact Empty Middle Package
; - 选中包
, Shift + F6
勾选选项并, Do Factor
; Sync Now
; - 在
Project
.gradle
build
; Build
->Clean Project
。
修改 JDK 路径
File
->Project Structure
->SDKLocation
导入 Module
File
->New
->Impoer Module...
; 如下例在
build.gradle
。 1
implementation project(':module.lib')
问题集锦
依赖冲突
引用第三方库可能导致库兼容问题
ERROR: All ... library must be the exact same version specification.
或是依赖冲突问题
ERROR: Program type already present: ... .
可以在app
build.gradle
1 | configurations.all { |
新旧版本问题
getSlotFromBufferLocked
: unknown buffer.
是
ERROR: Invoke-customs are only supported starting with Android O.
可通过修改minSdkVersion
app
build.gradle
1 | compileOptions { |
Manifest Merger Failed: Attribute application@appComponentFactory value = (...) .
Android X
ERROR: No package ID FF found for ID 0xFFFFFFFF.
是
1 | // noinspection GradleDependency |
ClassNotFoundException
: java.sql.SQLType.
将mysql-connection-java
ERROR: Could not find method leftShift()
for argument.
<<
在
ERROR: Unable to find method org.gradle.api.example
.
Gradle
- 在
build.gradle
; - 在
gradle-wrapper.properties
distributionUrl
。
控件问题
SPAN_EXCLUSIVE: span cannot have a zero length.
由聚焦于EditText
1 | <EditText android:inputType="textNoSuggestions" ... /> |
规范问题
ERROR: 'A' is not a valid file-based res name character.
不允许使用大写字母命名资源文件
ERROR: Cannot perform this action after onSaveInstanceState
.
将commit()
commitAllowingStateLoss()
commitAllowingStateLoss()
Like : commit()
, but allows the commit to be executed after an activity's state is saved.
网络
CONNECT FAILED: ECONNREFUSED.
127.0.0.1(localhost) refers to the emulator itself (not the local machine). Use ip 10.0.0.2, which is bridged to your local machine.
本地测试连接数据库的
1 | val url = "jdbc:mysql://10.0.0.2:3306/$database" |
ERROR: Cannot create connection caused by NetworkOnMainThreadException
.
主线程不允许网络请求
1 | Thread(Runnable { |
FAILED RESOLUTION: Lorg/apache/http/ProtocolVersion.
在AndroidManifest.xml
application
1 | <uses-library android:name="org.apache.http.legacy" android:required="false" /> |
Android 9 以上无法使用 http 的问题
在AndroidManifest.xml
application
1 | <application android:usesCleartextTraffic="true" ... > ... </application> |
下载过慢
将下载源配置为国内镜像服务器
1 | buildscript { |
其他
Session 'app': Error Installing APK.
Build
->Clean
Installation Failed: Uninstall an existing version of the apk that is present.
OK
或 Cancel 都无法使安装正常进行 。
File
->Settings
->Build, Exception, Deployment
->Instant Run
->反选Enable Instant Run to Hot Swap Code
Instant Run