Android code wiki
Tip1:
类的全局静态变量的使用,这样可以静态变量只分配一次内存,可以不通过类的对象也就是可以通过类名直接使用该变量。(使用场景:Request_Code ,Result_Code,Log Tag,权限名字,Activity之间传递参数Name eg:bundle,公用参数名字,接受回调的参数( eg: getIntent() ),可以防止分配内存 etc)
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.permissionapp;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import java.lang.Override;
/**
* A simple activity that requests permissions and returns the result.
*/
public class PermissionActivity extends Activity {
private static final String TAG = "PermissionActivity";
private static final String ACTION_CHECK_HAS_PERMISSION
= "com.android.cts.permission.action.CHECK_HAS_PERMISSION";
private static final String ACTION_REQUEST_PERMISSION
= "com.android.cts.permission.action.REQUEST_PERMISSION";
private static final String ACTION_PERMISSION_RESULT
= "com.android.cts.permission.action.PERMISSION_RESULT";
private static final String EXTRA_PERMISSION
= "com.android.cts.permission.extra.PERMISSION";
private static final String EXTRA_GRANT_STATE
= "com.android.cts.permission.extra.GRANT_STATE";
private static final int PERMISSION_ERROR = -2;
private static final int PERMISSIONS_REQUEST_CODE = 100;
private String mPermission;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent received = getIntent();
Log.d(TAG, "Started with " + received);
final String action = received.getAction();
mPermission = received.getStringExtra(EXTRA_PERMISSION);
if (ACTION_REQUEST_PERMISSION.equals(action)) {
Log.d(TAG, "Requesting permission " + mPermission);
requestPermissions(new String[] {mPermission}, PERMISSIONS_REQUEST_CODE);
} else if (ACTION_CHECK_HAS_PERMISSION.equals(action)) {
Log.d(TAG, "Checking permission " + mPermission);
sendResultBroadcast(checkSelfPermission(mPermission));
} else {
Log.w(TAG, "Unknown intent received: " + received);
finish();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode != PERMISSIONS_REQUEST_CODE ||
permissions.length != 1 ||
!permissions[0].equals(mPermission)) {
Log.d(TAG, "Received wrong permissions result");
sendResultBroadcast(PERMISSION_ERROR);
} else {
Log.d(TAG, "Received valid permission result: " + grantResults[0]);
sendResultBroadcast(grantResults[0]);
}
}
private void sendResultBroadcast(int result) {
Log.d(TAG, "Sending result broadcast: " + result);
Intent broadcast = new Intent(ACTION_PERMISSION_RESULT);
broadcast.putExtra(EXTRA_GRANT_STATE, result);
sendBroadcast(broadcast);
finish();
}
}
Tip 2:
namespace的小技巧,在Android 中最常用到的namespace 就是在XML里面的android:***,这样的写法非常方便,现在要说的不是android 这个namespace,是另外一个namespace tools.它非常有用。
tools的URI的全称是”http://schemas.android.com/tools”,常用的方式是声明整个URI以后用前缀 tools 来在XML文件里面使用。tools有关的所有属性不会影响应用的运行和应用的大小,所有的tools属性会被gradle 过滤掉的当编译打包的时候。
快速添加tools的步骤:
在XML的根里面添加,只要敲出 toolsNS 和 TAB (Android Studio)就会自动添加tools这个namespace的全URI.
tools使用方法:
Android Studio的XML里面现在还不提供对tools所有属性的自动补全,最简单的办法 就是 复制android:**,然后换一下namespace,把android 换成tools即可。
有时候为了开发方便而不影响用户的体验可以使用它,在XML里面声明一个TextView,但是为了预览更方便会设置android:text=“我是预览的文字,我一直在,不管是否编译”,这样就会预览到文字的效果,但是,如果只是在XML预览的时候能看到,编译运行以后就看不到,怎么实现呢?这时候就用到了tools这个神奇的工具,可以这样写tools:text="我是预览的文字,我不是一直在,编译我就消失啦” 来代替 android:text=“我是预览的文字,我一直在,不管是否编译”。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="我是预览的文字,编译我就消失啦" />
</LinearLayout>
同理,为了预览方便而编译以后不需要的时候任何场景都可以使用tools来实现。有时候为了预览的时候某些属性enable,但是运行的时候unenable。可以进行android namespace所有属性覆盖的,除了自定义View的属性。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fastScrollAlwaysVisible="true"
tools:fastScrollAlwaysVisible="false" />
</LinearLayout>
关于@TargetApi,在代码里面,经常会遇见的一个注解,用tools也是在XML里面可以实现的,比如:tools:targetApi,可以用integer 或者版本的名字 来声明版本信息。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_height="match_parent"
tools:targetApi="M"
tools:text="Mastering ToolsNs" />
</LinearLayout>
当Lint检查 string resources是否正确的时候,经常会有警告信息,如果有强迫症并且应用没有走国际化,那么你可以这样来。
<resources xmlns:tools="http://schemas.android.com/tools"
tools:locale="es">
关于预览fragment或者自定义view。可以在一个布局文件里面这样写。下面是预览Booksfragment,不需要编译运行,也不需要模拟器就可以看到预览的效果啦。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<fragment
android:name="com.alexsimo.mastertoolsnamespace.BooksFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/fragment_books" />
</LinearLayout>
更复杂的多层级预览,也是可以达到的。比如预览一个Activity里面的一个fragment的一个ListView对应的每个Item,如果,不编译运行在模拟器或者自己的设备上,应该是看不到效果的,但是,有了tools,一切都变得简单了。
activity_main:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<fragment
android:name="com.alexsimo.mastertoolsnamespace.BooksFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/fragment_book" />
</LinearLayout>
fragment_book:
(tools:listitem=“”
tools:listheader,tools:listfooter,预览ListView是非常有用的,但是对于RecyclerView 没有header和footer)
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView 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:id="@+id/list"
android:name="com.alexsimo.mastertoolsnamespace.BooksFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context="com.alexsimo.mastertoolsnamespace.BooksFragment"
tools:listitem="@layout/fragment_book_list_item" />
fragment_book_list_item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView"
android:layout_width="150dp"
android:layout_height="150dp"
tools:src="@android:drawable/ic_media_play" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem"
tools:text="My book title" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem"
tools:text="My book description" />
</LinearLayout>
</LinearLayout>
这时候预览activity_main
,如下:
关于预览layout 的 menus ,一般在Activity中定义 Activity.onCreateOptionsMenu(),为了预览可以用 tools:menu="comma separated menu IDs” ,设置Toolbar navigation mode,可以这样,tools:actionBarNavMode="standard|list|tabs”。
From: http://tools.android.com/tips/layout-designtime-attributes
Tip3:
关于软键盘的问题,常用的软键盘各种模式,比如,为了适应页面大小,可以android:windowSoftInputMode="adjustResize"
如果不喜欢XML配置,喜欢代码设置,那么你可以这样来:
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
自定义View展示PDF文件,PDF文件可以编辑,那么view点击编辑状态,软键盘弹出,这时候设置adjustResize就无效了,因为这个页面是全屏的,关于Fullscreen mode,g官方解释是这样的:
If the window's layout parameter flags include FLAG_FULLSCREEN, this value for softInputMode will be ignored; the window will not resize, but will stay fullscreen.
总而言之和键盘的adjustResize冲突了,这时候键盘就出问题了。
这时候可以监听键盘,然后手动计算设置高度了。
// Threshold for minimal keyboard height.
final int MIN_KEYBOARD_HEIGHT_PX = 150;
// Top-level window decor view.
final View decorView = activity.getWindow().getDecorView();
// Register global layout listener.
decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
private final Rect windowVisibleDisplayFrame = new Rect();
private int lastVisibleDecorViewHeight;
@Override
public void onGlobalLayout() {
// Retrieve visible rectangle inside window.
decorView.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame);
final int visibleDecorViewHeight = windowVisibleDisplayFrame.height();
// Decide whether keyboard is visible from changing decor view height.
if (lastVisibleDecorViewHeight != 0) {
if (lastVisibleDecorViewHeight > visibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX) {
// Calculate current keyboard height (this includes also navigation bar height when in fullscreen mode).
int currentKeyboardHeight = decorView.getHeight() - windowVisibleDisplayFrame.bottom;
// Notify listener about keyboard being shown.
listener.onKeyboardShown(currentKeyboardHeight);
} else if (lastVisibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX < visibleDecorViewHeight) {
// Notify listener about keyboard being hidden.
listener.onKeyboardHidden();
}
}
// Save current decor view height for the next call.
lastVisibleDecorViewHeight = visibleDecorViewHeight;
}
});
From:https://pspdfkit.com/blog/2016/keyboard-handling-on-android/
Tip4: 关于遍历
在开发中,遍历集合,搜集信息,是最常见的代码块,遍历中,如果中间出现异常,是不是遍历就终止了,但是如果遍历出现异常还要能继续执行剩下的条目,怎么办呢,这时候就用到了异常处理机制,如下:
String routes = intent.getStringExtra(packageName + ".routes");
if (routes != null) {
String[] routeArray = routes.split(",");
for (int i = 0; i < routeArray.length; i++) {
String[] prefixAndMask = routeArray[i].split("/");
try {
InetAddress address = InetAddress.getByName(prefixAndMask[0]);
int prefixLength = Integer.parseInt(prefixAndMask[1]);
builder.addRoute(address, prefixLength);
} catch (UnknownHostException|NumberFormatException|
ArrayIndexOutOfBoundsException e) {
continue;
}
}
}
建议上面这种写法,catch捕获以后,continue;同时,关于异常的另外一个用处,当return用,可以直接返回,如下:
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new RuntimeException("Operation not supported");
}
Tip5: 关于调用ShareCompat Intent,代码如下:
// Create share intent
Intent shareIntent = ShareCompat.IntentBuilder.from(this)
.setText(getShareText(coupon))
.setType("image/jpeg")
.setStream(coupon.mImageUri)
.setChooserTitle(getString(R.string.redeem_using))
.createChooserIntent();
startActivity(shareIntent);
关于格式化展示信息的时候,一般都是用String的format来进行,这里介绍另外一种:
if (TextUtils.isEmpty(SENDER_NAME)) {
return getString(R.string.message_format_without_sender,
coupon.mTitle, coupon.mSubtitle);
} else {
// Otherwise, use the other string template and pass in the {@link #SENDER_NAME} too
return getString(R.string.message_format_with_sender, SENDER_NAME,
coupon.mTitle, coupon.mSubtitle);
}
这里会调用Context里面的getString()方法来实现,前面的格式化的format如下,然后传入对于需要格式化的标题和子标题就OK了
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!--
Template string with coupon title, subtitle, and sender name for the text to use
when the coupon is shared with other apps. [CHAR LIMIT=NONE]
-->
<string name="message_format_without_sender">Excited to redeem my coupon! <xliff:g id="coupon_title">%s</xliff:g> - <xliff:g id="coupon_subtitle">%s</xliff:g> #justforus</string>
<!--
Template string with coupon title, subtitle, and sender name for the text to use
when the coupon is shared with other apps. [CHAR LIMIT=NONE] 写注释是个好习惯
-->
<string name="message_format_with_sender">Excited to redeem my coupon with <xliff:g id="coupon_title">%s</xliff:g>! <xliff:g id="coupon_title">%s</xliff:g> - <xliff:g id="coupon_subtitle">%s</xliff:g> #justforus</string>
</resources>
Tip5: 关于Adapter
/**
* Adapter for grid of coupons.
*/
private static class CouponAdapter extends BaseAdapter {
private LayoutInflater mInflater;
private List<Coupon> mAllCoupons;
/**
* Constructs a new {@link CouponAdapter}.
*
* @param inflater to create new views
* @param allCoupons for list of all coupons to be displayed
*/
public CouponAdapter(LayoutInflater inflater, List<Coupon> allCoupons) {
if (allCoupons == null) {
throw new IllegalStateException("Can't have null list of coupons");
}
mAllCoupons = allCoupons;
mInflater = inflater;
}
@Override
public int getCount() {
return mAllCoupons.size();
}
@Override
public Coupon getItem(int position) {
return mAllCoupons.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//缓存策略的另外一种写法
View result = convertView;
if (result == null) {
//注意mInflater的来源,是在Activity的setContextView中这样写的:
// Fetch the {@link LayoutInflater} service so that new views can be created
// LayoutInflater inflater = (LayoutInflater) getSystemService(
// Context.LAYOUT_INFLATER_SERVICE);
result = mInflater.inflate(R.layout.grid_item, parent, false);
}
// Try to get view cache or create a new one if needed
ViewCache viewCache = (ViewCache) result.getTag();
if (viewCache == null) {
viewCache = new ViewCache(result);
result.setTag(viewCache);
}
// Fetch item
Coupon coupon = getItem(position);
// Bind the data
viewCache.mTitleView.setText(coupon.mTitle);
viewCache.mSubtitleView.setText(coupon.mSubtitle);
viewCache.mImageView.setImageURI(coupon.mImageUri);
return result;
}
}
/**
* Cache of views in the grid item view to make recycling of views quicker. This avoids
* additional {@link View#findViewById(int)} calls after the {@link ViewCache} is first
* created for a view. See
* {@link CouponAdapter#getView(int position, View convertView, ViewGroup parent)}.
*/
private static class ViewCache {
/** View that displays the title of the coupon */
private final TextView mTitleView;
/** View that displays the subtitle of the coupon */
private final TextView mSubtitleView;
/** View that displays the image associated with the coupon */
private final ImageView mImageView;
/**
* Constructs a new {@link ViewCache}.
*
* @param view which contains children views that should be cached.
*/
private ViewCache(View view) {
mTitleView = (TextView) view.findViewById(R.id.title);
mSubtitleView = (TextView) view.findViewById(R.id.subtitle);
mImageView = (ImageView) view.findViewById(R.id.image);
}
}
/**
* 关于适配器里面数据bean对象问题,如果只是纯粹展示,而不需要改变bean对象的属性,那么推荐下面这种方式,如果需要改变
* bean对象的属性,那么还是用常见的get set方法实现.
*/
private static class Coupon {
/** Title of the coupon. */
private final String mTitle;
/** Description of the coupon. */
private final String mSubtitle;
/** Content URI of the image for the coupon. */
private final Uri mImageUri;
/**
* Constructs a new {@link Coupon}.
*
* @param titleString is the title
* @param subtitleString is the description
* @param imageAssetFilePath is the file path from the application's assets folder for
* the image associated with this coupon
*/
private Coupon(String titleString, String subtitleString, String imageAssetFilePath) {
mTitle = titleString;
mSubtitle = subtitleString;
mImageUri = Uri.parse("content://" + AssetProvider.CONTENT_URI + "/" +
imageAssetFilePath);
}
}
Tip6: 关于应用的全局常量
package com.example.android.apprestrictionenforcer;
public interface Constants {
/**
* Package name of the AppRestrictionSchema sample.
*/
public static final String PACKAGE_NAME_APP_RESTRICTION_SCHEMA
= "com.example.android.apprestrictionschema";
}
为什么以接口的形式定义,而不用class定义呢,这里要说明一下用接口的好处,因为Java语言中要求,接口中定义的变量默认是public static final型,且必须给其初始值,所以实现类中不能重新定义,也不能改变其值,如果用到全局常量的定义的时候,这时候定义Constants,就限制了常量的类型,static final,多了一步校验。
题外话:接口更多的是在系统架构设计方法发挥作用,主要用于定义模块化之间的通信契约。(符合这种理念)
而抽象类在代码实现方面发挥作用,可以实现代码的重用。
Tip7: 关于网络
检查网络状态代码片:
/**
* Check whether the device is connected, and if so, whether the connection
* is wifi or mobile (it could be something else).
*/
private void checkNetworkConnection() {
// BEGIN_INCLUDE(connect)
ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
if (activeInfo != null && activeInfo.isConnected()) {
wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
if(wifiConnected) {
Log.i(TAG, getString(R.string.wifi_connection));
} else if (mobileConnected){
Log.i(TAG, getString(R.string.mobile_connection));
}
} else {
Log.i(TAG, getString(R.string.no_wifi_or_mobile));
}
// END_INCLUDE(connect)
}
获取屏幕的一切信息,来自这个类:
getResources().getDisplayMetrics() 得到
DisplayMetrics 这个类,就可以获取到一切有关设备屏幕大小,密度等信息了...
// eg: Calculate radiuses in px from dp based on screen density
float density = getResources().getDisplayMetrics().density;
关于自定义View中随机颜色的写法:
定义十六进制的颜色
public final int[] COLORS = {
0xFF33B5E5, 0xFFAA66CC, 0xFF99CC00, 0xFFFFBB33, 0xFFFF4444,
0xFF0099CC, 0xFF9933CC, 0xFF669900, 0xFFFF8800, 0xFFCC0000
};
这样就可以根据id动态获取到颜色了
int color = COLORS[id % COLORS.length];
设置背景
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
setBackground(states);
else
setBackgroundDrawable(states);
Tip7: 关于Build
apply plugin: 'com.android.application'
def getGitVersion() {
try {
return 'git rev-parse --short HEAD'.execute().text.trim()
} catch (Throwable th) {
return "";
}
}
android {
compileSdkVersion 23
buildToolsVersion buildToolsVer
useLibrary 'org.apache.http.legacy'
defaultConfig {
minSdkVersion 9
targetSdkVersion 23
buildConfigField "String", "GIT_REVISION", "\"${getGitVersion()}\""
buildConfigField "String", "BUILD_DATE", "\"${new Date().toLocaleString()}\"";
}
signingConfigs {
debug { storeFile file("debug.keystore") }
release {
storeFile file('release.keystore')
storePassword 'thisiskeystorepassword'
keyAlias 'nim_demo'
keyPassword 'thisiskeypassword'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
manifestPlaceholders = [ AMAP_KEY:"09fd4efd3e28e9bf1f449ecec7d34bfe" ]
}
release {
minifyEnabled true
zipAlignEnabled true
proguardFile('proguard.cfg')
signingConfig signingConfigs.release
manifestPlaceholders = [ AMAP_KEY:"ee20324fba1c7f4ad7a4a207e7f08e8d" ]
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res', 'res-avchat', 'res-chatroom']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs', 'libs-sdk']
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
dexOptions {
incremental true
preDexLibraries false
jumboMode true
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar', exclude: ['android-support-*.jar'])
compile project(path: ':uikit')
}
在Activity中引用build中定义的变量:
在AndroidMainfest.xml中引用配置的key: