我记得我学习Android那会儿,老师也不是很会Android,所有经常是对着Google的文档读,再加上全英文的时候文档,看起来还是很吃力,经常是开着有道词典一句一句的翻译,等后来工作了,才意识到,这玩意是个好东西呀,但是那个时候已经不怎么去看API指南了,后来经常看一些技术书籍,我发现很多的写书人也大多数是看着官方文档的印子来写的,于是又重新打开了官网,这么一番下来,啧啧有味,你不光能看到各个版本的新技术,和需要注意的地方,还能看到很多我们不常用,却很实在的小get技术,再后来,也就是Google的中国版官网发布
我们来看看他有什么有趣的东西吧!
他的首页,我们可以看到这么一个目录
我们作为Android开发者,其实大部分都是必须要是了解的,但是我们可以侧重的去看一些东西
这些就是我们比较侧重的东西了,如果你是一名Android,我也希望你把这些全部去看完去熟悉,这样对你的职业生涯还是很有帮助的,我们本篇的主要内容也是介绍这些东西
作为第二大块,我准备说一下我们的API指南,因为这真是太赞了,我的很多的开发技巧都是上门学习到的,先来看下目录:
简介
平台架构
应用组件
应用资源
应用清单
用户界面
动画和图形
计算
媒体和相机
位置和传感器
连接
文本和输入
数据存储
库
管理
网络应用
最佳实践
我们可以发现的事就是,这个目录承载着我们很多的只是点,没错,很多知识,不光是我们经常用到的,而是我们不经常用到,但是他却十分的爽歪歪的东西,我们这篇文章就是来探讨这些知识点的,不要怀疑,这件事情一定很酷。好的,那,既然如此,那我们继续来简化他吧,我们把小get点都提取出来
应用在安装的时候,会为每个软件包提供唯一的 Linux 用户 ID,我们都只是Linux有用户组,同样的Android也有,如果我们需要管理员权限的话,在Android中的实现是在AndroidManifest.xml 的 manifest 标记中使用 sharedUserId 属性,举例:
<manifest
package="com.liuguilin.googlesample"
xmlns:android="http://schemas.android.com/apk/res/android"
android:sharedUserId="android.uid.system">
如果想使用JAVA8的一些特性,比如Lambda表达式,注解之类的,可以在Android Studio中配置Gradle
android {
...
defaultConfig {
...
jackOptions {
enabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
要注意的是,目前JAVA8还不支持Instant Run
Intent十分的强大,是沟通各个组件之间的桥梁,我们可以用它做很多的事情
如果我们用Intent来创建闹钟,可以这样
//创建闹钟
public void createAlarm(String message, int hour, int minutes) {
Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM)
.putExtra(AlarmClock.EXTRA_MESSAGE, message)
.putExtra(AlarmClock.EXTRA_HOUR, hour)
.putExtra(AlarmClock.EXTRA_MINUTES, minutes);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
当然,这只是跳转到设置系统闹钟,而且我们必须加上权限
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
以及过滤器
<activity ...>
<intent-filter>
<action android:name="android.intent.action.SET_ALARM" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
activity>
这样我们就可以实现点击设置系统闹钟了,而且这些属性,有很多,我们就不细讲了,有兴趣的就可以看下吧。
Intent地址
同样的,定时器也是系统的,我们可以这样做
//创建定时器
public void startTimer(String message, int seconds) {
Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER)
.putExtra(AlarmClock.EXTRA_MESSAGE, message)
.putExtra(AlarmClock.EXTRA_LENGTH, seconds)
.putExtra(AlarmClock.EXTRA_SKIP_UI, false);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
同样的特也需要权限以及过滤器,这里注意一下过滤器只是接受到这个intent的标识
<activity ...>
<intent-filter>
<action android:name="android.intent.action.SET_TIMER" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
activity>
如果添加日历的事件提醒呢,说实话,这个没多少人用,因为你需要先登录Google账号,OK,我们看下代码
//创建日历事件
public void addEvent(String title, String location, Calendar begin, Calendar end) {
Intent intent = new Intent(Intent.ACTION_INSERT)
.setData(CalendarContract.Events.CONTENT_URI)
.putExtra(CalendarContract.Events.TITLE, title)
.putExtra(CalendarContract.Events.EVENT_LOCATION, location)
.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin)
.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
}
如果你也想接收这个事件的话,你也需要添加intent过滤器
<activity ...>
<intent-filter>
<action android:name="android.intent.action.INSERT" />
<data android:mimeType="vnd.android.cursor.dir/event" />
<category android:name="android.intent.category.DEFAULT" />
intent-filter>
activity>
如需打开相机应用并接收拍摄的照片或视频,请使用 ACTION_IMAGE_CAPTURE 或 ACTION_VIDEO_CAPTURE 操作。此外,还可在 EXTRA_OUTPUT extra 中指定您希望相机将照片或视频保存到的 URI 位置,所有用起来还是很方便的,我相信,相机很多人都会,但是视频,其实没有多少人会,ok,我们来看下实现,这里我就直接贴代码了
//拍照返回
public void capturePhoto(String targetFilename) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.withAppendedPath(mLocationForPhotos, targetFilename));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, PHOTO_CODE);
}
}
//视频返回
private void dispatchTakeVideoIntent() {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takeVideoIntent, VIDEO_CODE);
}
}
这样你就可以跳转到相机了,如果想要拿到他的返回值,还得通过intent,也就是在回调里面:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "requestCode" + requestCode + "resultCode" + resultCode);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case PHOTO_CODE:
if (data != null) {
Bundle bundle = data.getExtras();
Bitmap thumbnail = (Bitmap) bundle.get("data");
if (thumbnail != null) {
ivSrc.setImageBitmap(thumbnail);
}
}
break;
case VIDEO_CODE:
if (data != null) {
Uri videoUri = data.getData();
mVideoView.setVideoURI(videoUri);
}
break;
}
}
}
联系人的话还稍微复杂一点,不过这都是通用的基础语法,最多只能说是麻烦了一点,OK,我们来看下怎么跳转到联系人,然后选择联系人
//选择联系人
public void selectContact() {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_SELECT_PHONE_NUMBER);
}
}
这里还好,跳转到联系人,然后主要是接收到的回调,我们需要自己去查找我们需要的表单
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch (requestCode) {
case REQUEST_SELECT_PHONE_NUMBER:
Uri contactUri = data.getData();
String[] projection = new String[]{ContactsContract.CommonDataKinds.Phone.NUMBER};
Cursor cursor = getContentResolver().query(contactUri, projection,
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
String number = cursor.getString(numberIndex);
Toast.makeText(this, "number:" + number, Toast.LENGTH_SHORT).show();
}
break;
}
}
}
这里其实还有很多的隐藏用法,比如插入,查询联系人等等,还有发送文件,以及我们打开指定的文件类型,这些都是我们很有必要掌握的,做文件管理器的时候就要用到,只能说某个领域专注某个模块吧,但是大的方向还是要了解下
Fragment我们这里就只说一下他的两个扩展类,DialogFragment和PreferenceFragment
DialogFragment我一直不是很清楚他的优势在哪,但是的确看到有一些好的开源项目里面有用到,这里就说一下他的简单用法
首先是show出来,并且给上dialog的tag
public void showDialog() {
mStackLevel++;
android.support.v4.app.FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
android.support.v4.app.Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
DialogFragment newFragment = MyDialogFragment.newInstance(mStackLevel);
newFragment.show(ft, "dialog");
}
这个没什么问题,主要来看下Fragment的实现,这里代码是官网的,有一点繁琐,其实多看下家就明白了,只是一个更换主题
public class MyDialogFragment extends DialogFragment {
int mNum;
public static MyDialogFragment newInstance(int num) {
MyDialogFragment f = new MyDialogFragment();
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNum = getArguments().getInt("num");
int style = DialogFragment.STYLE_NORMAL, theme = 0;
switch ((mNum - 1) % 6) {
case 1:
style = DialogFragment.STYLE_NO_TITLE;
break;
case 2:
style = DialogFragment.STYLE_NO_FRAME;
break;
case 3:
style = DialogFragment.STYLE_NO_INPUT;
break;
case 4:
style = DialogFragment.STYLE_NORMAL;
break;
case 5:
style = DialogFragment.STYLE_NORMAL;
break;
case 6:
style = DialogFragment.STYLE_NO_TITLE;
break;
case 7:
style = DialogFragment.STYLE_NO_FRAME;
break;
case 8:
style = DialogFragment.STYLE_NORMAL;
break;
}
switch ((mNum - 1) % 6) {
case 4:
theme = android.R.style.Theme_Holo;
break;
case 5:
theme = android.R.style.Theme_Holo_Light_Dialog;
break;
case 6:
theme = android.R.style.Theme_Holo_Light;
break;
case 7:
theme = android.R.style.Theme_Holo_Light_Panel;
break;
case 8:
theme = android.R.style.Theme_Holo_Light;
break;
}
setStyle(style, theme);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_dialog, container, false);
View tv = v.findViewById(R.id.text);
((TextView) tv).setText("Dialog #" + mNum );
Button button = (Button) v.findViewById(R.id.show);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((DialogFragmentActivity) getActivity()).showDialog();
}
});
return v;
}
PreferenceFragment是我觉得非常实用而且很棒的扩展类了,他能完美的实现设置的一些需求,我们来看下官网对他的描述
以列表形式显示 Preference 对象的层次结构,类似于 PreferenceActivity。这在为您的应用创建“设置” Activity 时很有用处
Ok,首先我们把一个fragment添加进去Activity
<fragment
android:name="com.liuguilin.googlesample.fragment.PrefsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="pre"/>
好的,其实这个fragment特也没干什么,你需要继承的是PreferenceFragment,然后调用添加资源
addPreferencesFromResource(R.xml.preferences);
我们再res下创建xml
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="设置">
<CheckBoxPreference
android:key="checkbox_preference"
android:summary="这是一个不正经的勾选"
android:title="勾选"/>
PreferenceCategory>
<PreferenceCategory
android:title="用户">
<EditTextPreference
android:dialogTitle="请输入"
android:key="edittext_preference"
android:summary="我想知道你的性别"
android:title="性别"/>
<ListPreference
android:dialogTitle="用戶属性"
android:entries="@array/entries_list_preference"
android:entryValues="@array/entryvalues_list_preference"
android:key="list_preference"
android:summary="身份证请保密"
android:title="请输入身份证"/>
PreferenceCategory>
<PreferenceCategory
android:title="启动设置">
<PreferenceScreen
android:key="screen_preference"
android:summary="启动预览请调整手机方向"
android:title="旋转">
<CheckBoxPreference
android:key="next_screen_checkbox_preference"
android:summary="锁定方向后不可旋转"
android:title="锁定屏幕"/>
PreferenceScreen>
<PreferenceScreen
android:summary="官网是你的家"
android:title="官网">
<intent
android:action="android.intent.action.VIEW"
android:data="http://www.android.com"/>
PreferenceScreen>
PreferenceCategory>
<PreferenceCategory
android:title="工程模式">
<CheckBoxPreference
android:key="parent_checkbox_preference"
android:summary="打开后你将拥有管理员权限"
android:title="是否打开"/>
<CheckBoxPreference
android:dependency="parent_checkbox_preference"
android:key="child_checkbox_preference"
android:layout="?android:attr/preferenceLayoutChild"
android:summary="管理员权限"
android:title="ROOT"/>
PreferenceCategory>
PreferenceScreen>
好的,运行起来是不是很酷,如果想详细的了解他的每个节点的使用,可以去看下api文档
API文档
再来看下AIDL,一般的应用并不会使用到aidl,但是aidl确实IPC通信的一个很好的辅助手段
AIDL的话,要注意的几点就是首先要相互调用需要在同一包名,还要注意就是接口的填写,我们来看文档
官网上有一段说明:
注:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。
很好的诠释了AIDL,如果你不需要跨应用的话,建议使用其他的跨进程来操作
那我们如何定义呢,首先要在main目录下创建一个aidl的文件夹,然后就是三步流程
此文件定义带有方法签名的编程接口。
Android SDK 工具基于您的 .aidl 文件,使用 Java 编程语言生成一个接口。此接口具有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现方法。
实现 Service 并重写 onBind() 以返回 Stub 类的实现。
当你知道这些步骤之后,我们就可以开始来编写了
package com.android.liuguilin;
interface IRemoteService{
int add(int a,int b);
}
这是我们定义的一个接口。我在里面只定义了一个add方法,传两个int,然后我们来看下接口服务这边的定义
public class RemoteService extends Service {
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
};
}
ok,其实这里就是实现了一个相加的逻辑,然后把binder对象返回回去,我们最后直接bindservice就好了
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIRemoteService = IRemoteService.Stub.asInterface(service);
isBinder = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIRemoteService = null;
isBinder = false;
}
};
ok,拿到对象之后就可以直接去使用了,这里注意一下,如果需要跨进程,需要在清单文件中
<service
android:name=".service.RemoteService"
android:exported="true"/>
android:resizeableActivity=["true" | "false"]
"true" ...>
低功耗蓝牙在最近的几年应该算是很流行的了,那我们怎么去开发他呢,其实Google的文档已经详细到不能再详细了,我们一起来看下吧!
不管你是普通的蓝牙还是BLE,你都需要把权限给我加上
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果你想你的程序只有支持BLE的能用,那可以这样
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true"/>
而且你在一开始,你就要判断他是否支持低功耗蓝牙
//判断是否支持BLE
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "不支持BLE设备", Toast.LENGTH_SHORT).show();
//finish();
}
然后我们就可以做一些初始化的操作了
//获取本地适配器
BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter = bluetoothManager.getAdapter();
}
//如果没有打开,请求打开
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
这里还得注意下,BLE是API18之后出现的,然后开始搜索
//搜索
private void scanLeDevice(final boolean enable) {
if (enable) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, 1000);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
//搜索回调
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(device != null){
Log.i(TAG,"devices:" + device.getName());
}
}
});
}
};
这样就搜索到了,如果需要通信,需要实现GATT客户端,具体的使用方法,大家可以看文档
BLE API DOC
到这里大家有什么详细的就自己去细看吧!
不过国内还是有中文版的
就是有点小尴尬
MD的设计风格一致很棒,很平滑的过渡动画,空间设计感,阴影等,不过如此,还有色彩,图片,图像等,我们把这些称为调色板,不过具体的代码实现需要看具体的面板,这份只是介绍他的一些思想,我们也不过多的去探索,毕竟在国内,还是很少有做原生风格的app的公司
AS目前来看,还真是很好用,但是如果在配置不是很强的电脑上,体验会查很多,而且还要配置好多项配置才行,还是有一定的麻烦,我个人对他的唯一不足的就是没有更多官方支持的Theme,这会让编程失去了很多的乐趣,好的,我们来看下我们这门课所学的知识
官方建议我们使用2.2或者更高的版本,如果你不是很了解JNI,你可以先看一会儿JNI的文档
Android Studio 用于构建原生库的默认工具是 CMake,在此之前,你要先把build.properties里的这句代码删掉
android.useDeprecatedNdk = true
而使用NDK编译的话,你需要下载几个东西
其次是创建一个支持C/C++的项目,其实在我们新建工程的时候有一个选项的,就是注入C/C++的代码
大致的步骤官网也说的很详细
当然,你手动在src下创建cpp也是可以的,只不过需要设置好配置文件,我们来看下他为我们创建了什么东西,首先就是我们的c文件了
#include
#include
extern "C"
JNIEXPORT jstring JNICALL
Java_com_liuguilin_ndksample_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
可以看到这里很简单,就定义了一个stringFromJNI方法,返回的是Hello from C++字符串,所以我们要用的话,就直接去加载他然后调用就好了
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
这个时候运行后可以看到他自动为我们编译好了so,也不需要我们自己去用NDK编译了,很方便的,这就是CMake所带来的好处
我们来看下官方给我们的流程说法
在终端使用‘gradlew lint’来对项目进行检索就好了,详细的请看他的api
65536其实是件很烦恼的事情,这个时候虽然官方建议我们尽量优化代码,但是还是为时已晚,官方给出的解决方案
如果您的 minSdkVersion 设置为 21 或更高值,您只需在模块级 build.gradle 文件中将 multiDexEnabled 设置为 true 就好了
android {
defaultConfig {
...
minSdkVersion 21
targetSdkVersion 25
multiDexEnabled true
}
...
}
但是,如果您的 minSdkVersion 设置为 20 或更低值,你就需要处理了,你不光要开启multiDexEnabled,还需要添加multidex的依赖
android {
defaultConfig {
...
minSdkVersion 15
targetSdkVersion 25
multiDexEnabled true
}
...
}
dependencies {
compile 'com.android.support:multidex:1.0.1'
}
并且,如果你没有自己实现Application,那你只需要在Application里添加
"android.support.multidex.MultiDexApplication" >
...
不过你都超过了65536,想必你的项目应该挺大的吧,那你应该有自己的Application,那你就需要这样去写了,先去继承MultiDexApplication
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(context);
Multidex.install(this);
}
这样就可以分包了
Kotlin最近虽然火了一把,官方也支持了Kotlin,但是目前来看,还是坑要多一点,而且AS要支持需要升级到3.0以及JAVA8,可以参考API
具体的用法和使用C/C++有点类似,创建项目的时候勾选Kotlin的支持
更多的语法支持,可以去Kotlin官网看看,目前个人觉得,还是把源码和View,事件处理等知识点学习好才是真理
好了,到这里就over了,可能写的不是很详细,但是也是初步的把官方文档梳理了一遍,等我下次详细的再翻阅一遍再次记述一遍,应该就很详细了,这是黄金屋,大家一定要研读!
对文章有兴趣的可以关注一下我的微信公众号或者QQ群
Android旅行的路途: