Android 程序员必须知道的 53 个知识点

1. Android 单实例运行方法

   我们都知道 Android 平台没有任务管理器,而内部App 维护者一个 Activityhistory stack 来实现窗口显示和销毁,对于常规从快捷方式运行来看都是startActivity 可能会使用FLAG_ACTIVITY_NEW_TASK 标记来打开一个新窗口,比如Launcher,所以考虑单任务的实现方法比较简单,首先Android123 纠正下大家一种错误的方法就是直接在androidmanifest.xml application节点中加入android:launchMode="singleInstance"这句,其实这样将不会起到任何作用,Apps 内部维护的历史栈作用于 Activity,我们必须在 activity 节点中加入android:launchMode="singleInstance" 这句才能保证单实例,当然一般均加在主程序启动窗口的 Activity
2. px 像素如何转为dip设备独立像素:::
最近有网友问如何将 px 像素转为 dip 独立设备像素,由于 Android 的设备分辨率众多,
目前主流的为 wvga,而很多老的设备为 hvga 甚至低端的 qvga,对于兼容性来说使用 dip 无非是比较方便的,由于他和
分辨率无关和屏幕的密度大小有关,所以推荐使用。
px= (int) (dip*density+0.5f) //
这里android 开发网提示大家很多网友获取density(密度)的方法存在问题,
从资源中获取的是静态定义的,一般为1.0 对于 HVGA 是正好的,而对于 wvga 这样的应该从WindowsManager 中获取,
WVGA 
 1.5 这里可以再补充一下 dipsip 的知识
3. Android 中动态改变ImageView大小大小
很多网友可能发现在 layout.xml 文件中定义了 ImageView 的绝对大小后,无法动态修改以后的大小显示,其实Android
平台在设计 UI 控件时考虑到这个问题,为了适应不同的Drawable 可以通过在 xml 的相关 ImageView 中加入
android:scaleType="fitXY" 
这行即可,但因为使用了缩放可能会造成当前 UI 有所变形。
使用的前提是限制 ImageView 所在的层,可以使用一个内嵌的方法限制显示。
4. 如何判断Android手机当前是否联网?
如果拟开发一个网络应用的程序,首先考虑是否接入网络,在Android 手机中判断是否联网可以通过
ConnectivityManager 
类的isAvailable()方法判断,
首先获取网络通讯类的实例
ConnectivityManagercwjManager=(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
使用cwjManager.getActiveNetworkInfo().isAvailable(); 来返回是否有效,如果为 True 则表示当前 Android 手机已
经联网,可能是 WiFi  GPRSHSDPA 等等,具体的可以通过ConnectivityManager 类的getActiveNetworkInfo()方法判
断详细的接入方式,需要注意的是有关调用需要加入
<uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
这个权限,android 开发网提醒大家在真机上 Market  Browser 程序都使用了这个方法,来判断是否继续,同时在一些
网络超时的时候也可以检查下网络连接是否存在,以免浪费手机上的电力资源。
5. Drawable、、Bitmap、、Canvas 和和Paint 的关系
很多网友刚刚开始学习 Android 平台,对于 DrawableBitmapCanvas  Paint 它们之间的概念不是很清楚,其实它
们除了 Drawable 外早在 Sun  J2ME 中就已经出现了,但是在 Android 平台中,Bitmap Canvas 相关的都有所变化。
首先让我们理解下 Android 平台中的显示类是 View,但是还提供了底层图形类android.graphics,今天所说的这些均
 graphics 底层图形接口。
Bitmap - 
称作位图,一般位图的文件格式后缀为bmp,当然编码器也有很多如 RGB565RGB888。作为一种逐像素的显
示对象执行效率高,但是缺点也很明显存储效率低。我们理解为一种存储对象比较好。
Drawable - 
作为 Android 平下通用的图形对象,它可以装载常用格式的图像,比如GIFPNGJPG,当然也支持BMP
当然还提供一些高级的可视化对象,比如渐变、图形等。
Canvas - 
名为画布,我们可以看作是一种处理过程,使用各种方法来管理BitmapGL 或者 Path 路径,同时它可以配
 Matrix 矩阵类给图像做旋转、缩放等操作,同时Canvas 类还提供了裁剪、选取等操作。
Paint - 
我们可以把它看做一个画图工具,比如画笔、画刷。他管理了每个画图工具的字体、颜色、样式。如果涉
及一些 Android 游戏开发、显示特效可以通过这些底层图形类来高效实现自己的应用。
6. Activity 切换导致的onCreate重复执行
部分网友会发现 Activity 在切换到后台或布局从横屏 LANDSCAPE 切换到 PORTRAIT,会重新切换 Activity 会触发一次
onCreate 
方法,
我们可以在androidmanifest.xml 中的activit 元素加入这个属性
android:configChanges="orientation|keyboardHidden" 
即可,
比如
<activity android:name=".android123"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
同时在 Activity  Java 文件中重载onConfigurationChanged(Configuration newConfig)这个方法,
这样就不会在布局切换或窗口切换时重载onCreate 等方法。
代码如下:
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
if (this.getResources().getConfiguration(). orientation ==Configuration.ORIENTATION_LANDSCAPE)
{
//land
}
else if (this.getResources().getConfiguration().orientation ==Configuration.ORIENTATION_PORTRAIT)
{
//port
}
}
7. Android 的的ImageButton问题问题
很多网友对 Android 提供的 ImageButton有个疑问,当显示 Drawable 图片时就不会再显示文字了,
其实解决的方法有两种,第一种就是图片中就写入文字,但是这样解决会增加程序体积,同时硬编码方式会影响多国语
言的发布。第二种解决方法很简单,通过分析可以看到ImageButton  layout,我们可以直接直接继承,
添加一个 TextView,对齐方式为右侧即可实现 ImageButton支持文字右侧显示。
8. Android 代码优化技术
1.Java 内存控制
对于字符串操作而言如果需要连加这样的操作建议使用StringBuilder,经过调试不难发现如果你的字符串每次连加,
使用 String 需要的内存开销会远大于StringBuilder,然后 Android 手机常规的运行内存大约在 128MB 左右,对于运行多任
务就需要考虑了,Android开发网提示因为 Java GC 不需要手动释放那么分配的时候就要格外的小心,频繁的GC操作仍然
是很影响性能的,在调试时我们可以通过logcat 查看内存释放情况。
2.
循环使用
平时在访问一个属性的时候效率远比一个固定变量低,如果你的循环估计次数常常大于5
假设xxx.GetLength()方法的值一般大于 5
推荐这样写,
比如
for(int i=0;i<xxx.GetLength();i++)
这里xxx.GetLength 在每次循环都要调用,必然会影响程序效率,在游戏开发中显得更为明显,
改进的方法应该为
int j=xxx.GetLength()
for(int i=0;i<j;i++)
3.
图片的优化
 Android 平台中 2 维图像处理库BitmapFactory 做的比较智能,为了减少文件体积和效率,常常不用很多资源文件,
而把很多小图片放在一个图片中,有切片的方式来完成,在J2ME 中我们这样是为了将少文件头而解决MIDP 这些设备的问题,
 Android 中虽然机型硬件配置都比较高,有关Android G1 硬件配置可以参考G1手机参数以及评测,但是当资源多时这样
的运行效率还是令人满意的,至少Dalvik 优化的还不是很够。
9. Android 开发进阶之NIO非阻塞包(一一)
对于 Android 的网络通讯性能的提高,我们可以使用Java 上高性能的 NIO (NewI/O) 技术进行处理,NIO 是从 JDK 1.4
开始引入的,NIO  N 我们可以理解为 Noblocking 即非阻塞的意思,相对应传统的I/O,比如 Socket  accpet()read()
这些方法而言都是阻塞的。 NIO 主要使用了 Channel  Selector 来实现,Java  Selector 类似 Winsock  Select 模式,
是一种基于事件驱动的,整个处理方法使用了轮训的状态机,如果你过去开发过Symbian 应用的话这种方式有点像活动对象,
好处就是单线程更节省系统开销,NIO的好处可以很好的处理并发,对于Android 网游开发来说比较关键,对于多点 Socket
连接而言使用 NIO 可以大大减少线程使用,降低了线程死锁的概率,毕竟手机游戏有UI 线程,音乐线程,网络线程,管理
的难度可想而知,同时 I/O 这种低速设备将影响游戏的体验。
NIO 
作为一种中高负载的 I/O 模型,相对于传统的 BIO(Blocking I/O)来说有了很大的提高,处理并发不用太多的线程,
省去了创建销毁的时间,如果线程过多调度是问题,同时很多线程可能处于空闲状态,大大浪费了CPU 时间,同时过多的线
程可能是性能大幅下降,一般的解决方案中可能使用线程池来管理调度但这种方法治标不治本。使用NIO 可以使并发的效率
大大提高。当然 NIO  JDK 7 中的 AIO 还存在一些区别,AIO 作为一种更新的当然这是对于Java 而言,如果你开发过 Winsock
服务器,那么 IOCP 这样的 I/O 完成端口可以解决更高级的负载,当然了今天Android123 主要给大家讲解下为什么使用NIO
 Android 中有哪些用处。
NIO 
我们分为几个类型分别描述,作为Java 的特性之一,我们需要了解一些新的概念,比如ByteBuffer 类,Channel
SocketChannel
ServerSocketChannelSelector SelectionKey。有关具体的使用,Android 开发网将在明天详细讲解。
网友可以在 Android SDK文档中看下 java.nio java.nio.channels 两个包了解。
http://www.android123.com.cn/androidkaifa/695.html
了解下这种技术,看看在马上要做的项目中是否用得到
10. Android Theme 和和Styles 内部定义解析
昨天我们讲到的有关在AndroidManifest.xml 中定义Activity  theme 方法来实现无标题的方法,在使用xml 让你的
Activity 
无标题方法一文中讲到的,很多网友不明白为什么这样做,其实在Android123 以前的文章中多次提到了 styles
样式定义方法,
今天 Android 开发网再次把一些网友回顾了解下android 样式的内部定义。
在一个工程的 res/values/theme.xml中我们可以方便的定义自己的风格主题,
比如下面的 cwjTheme 中我们使用了基于 android 内部的白色调的背景 Theme.Light
设置windowsNoTitle  true 代表没有标题,背景颜色我们使用了android 内部定义的透明,
同时设置 listView 控件的样式为 cwjListViewxml 样式代码如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="cwjTheme" parent="android:Theme.Light">
<item name="android:windowNoTitle">true</item>
<itemname="android:windowBackground">@android:color/transparent</item>
<itemname="android:listViewStyle">@style/cwjListView</item>
</style>
有关 ListView 控件我们自定义的风格就是修改下系统listview 这个控件的每行分隔符样式,这里我们在工程下
res/drawable 
文件夹下放一个图片名为list_selector 图片,这样我们的 cwjListView的代码可以这样写
<style name="cwjListView"parent="@android:style/Widget.ListView">
<itemname="android:listSelector">@drawable/list_selector</item>
</style>
</resources>
通过定义 style 可以设置更多,比如让 cwjListView的字体颜色就加入textAppearance 属性,
比如
<itemname="textAppearance">@android:style/TextAppearance</item>
等等。
11.Android JSON 解析示例代码
来自 Google 官方的有关 Android 平台的 JSON 解析示例,如果远程服务器使用了json 而不是 xml 的数据提供,在 Android
平台上已经内置的 org.json 包可以很方便的实现手机客户端的解析处理。下面Android123 一起分析下这个例子,帮助
Android 
开发者需要有关 HTTP 通讯、正则表达式、JSON 解析、appWidget 开发的一些知识。
public class WordWidget extends AppWidgetProvider { //appWidget
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
context.startService(new Intent(context, UpdateService.class)); //
避免 ANR,所以 Widget 中开了个服务
}
public static class UpdateService extends Service {
@Override
public void onStart(Intent intent, int startId) {
// Build the widget update for today
RemoteViews updateViews = buildUpdate(this);
ComponentName thisWidget = new ComponentName(this, WordWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
manager.updateAppWidget(thisWidget, updateViews);
}
public RemoteViews buildUpdate(Context context) {
// Pick out month names from resources
Resources res = context.getResources();
String[] monthNames = res.getStringArray(R.array.month_names);
Time today = new Time();
today.setToNow();
String pageName = res.getString(R.string.template_wotd_title,monthNames[today.month],
today.monthDay);
RemoteViews updateViews = null;
String pageContent = "";
try {
SimpleWikiHelper.prepareUserAgent(context);
pageContent = SimpleWikiHelper.getPageContent(pageName, false);
} catch (ApiException e) {
Log.e("WordWidget", "Couldn't contact API", e);
} catch (ParseException e) {
Log.e("WordWidget", "Couldn't parse API response", e);
}
//
正则表达式处理,有关定义见下面的SimpleWikiHelper 
Pattern pattern = Pattern.compile(SimpleWikiHelper. WORD_OF_DAY_REGEX);
Matcher matcher = pattern.matcher(pageContent);
if (matcher.find()) {
updateViews = new RemoteViews(context.getPackageName(),R.layout.widget_word);
String wordTitle = matcher.group(1) ;
updateViews.setTextViewText(R.id.word_title, wordTitle);
updateViews.setTextViewText(R.id.word_type, matcher.group(2));
updateViews.setTextViewText(R.id.definition, matcher.group(3).trim()); 
String definePage = res.getString(R.string.template_define_url,Uri.encode(wordTitle));
Intent defineIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(definePage)); //
这里是打开相
应的网页,所以 Uri  http  urlaction  view 即打开 web 浏览器
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0 /* norequestCode */,
defineIntent, 0 /* no flags */);
updateViews.setOnClickPendingIntent(R.id.widget, pendingIntent); //
单击 Widget 打开
Activity
} else {
updateViews = new RemoteViews(context.getPackageName(),R.layout.widget_message);
CharSequence errorMessage = context.getText(R.string.widget_error);
updateViews.setTextViewText(R.id.message, errorMessage);
}
return updateViews;
}
@Override
public IBinder onBind(Intent intent) {
// We don't need to bind to this service
return null;
}
}
}
有关网络通讯的实体类,以及一些常量定义如下:
public class SimpleWikiHelper {
private static final String TAG = "SimpleWikiHelper";
public static final String WORD_OF_DAY_REGEX =
"(?s)\\{\\{wotd\\|(.+?)\\|(.+?)\\|([^#\\|]+).*?\\}\\}";
private static final String WIKTIONARY_PAGE =
"http://en.wiktionary.org/w/api.PHP?action=query&prop=revisions&titles=%s&"+
"rvprop=content&format=json%s";
private static final String WIKTIONARY_EXPAND_TEMPLATES =
"&rvexpandtemplates=true";
private static final int HTTP_STATUS_OK = 200;
private static byte[] sBuffer = new byte[512];
private static String sUserAgent = null;
public static class ApiException extends Exception {
public ApiException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
} public ApiException(String detailMessage) {
super(detailMessage);
}
}
public static class ParseException extends Exception {
public ParseException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}
public static void prepareUserAgent(Context context) {
try {
// Read package name and version number from manifest
PackageManager manager = context.getPackageManager();
PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
sUserAgent = String.format(context.getString(R.string.template_user_agent),
info.packageName, info.versionName);
} catch(NameNotFoundException e) {
Log.e(TAG, "Couldn't find package information in PackageManager", e);
}
}
public static String getPageContent(String title, boolean expandTemplates)
throws ApiException, ParseException {
String encodedTitle = Uri.encode(title);
String expandClause = expandTemplates ? WIKTIONARY_EXPAND_TEMPLATES :"";
String content = getUrlContent(String.format(WIKTIONARY_PAGE, encodedTitle,expandClause));
try {
JSONObject response = new JSONObject(content);
JSONObject query = response.getJSONObject("query");
JSONObject pages = query.getJSONObject("pages");
JSONObject page = pages.getJSONObject((String) pages.keys().next());
JSONArray revisions = page.getJSONArray("revisions");
JSONObject revision = revisions.getJSONObject(0);
return revision.getString("*");
} catch (JSONException e) {
throw new ParseException("Problem parsing API response", e);
}
}
protected static synchronized String getUrlContent(String url) throwsApiException {
if (sUserAgent == null) {
throw new ApiException("User-Agent string must be prepared");
}
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet(url);
request.setHeader("User-Agent", sUserAgent) ; //
设置客户端标识
try {
HttpResponse response = client.execute(request);
StatusLine status = response.getStatusLine();
if (status.getStatusCode() != HTTP_STATUS_OK) {
throw new ApiException("Invalid response from server: "+status.toString());
}
HttpEntity entity = response.getEntity();
InputStream inputStream = entity.getContent(); //
获取 HTTP 返回的数据流
ByteArrayOutputStream content = new ByteArrayOutputStream();
int readBytes = 0;
while ((readBytes = inputStream.read(sBuffer)) != -1) {
content.write(sBuffer, 0, readBytes); //
转化为字节数组流
}
return new String(content.toByteArray()); //
从字节数组构建 String
} catch (IOException e) {
throw new ApiException("Problem communicating with API", e);
}
}
}
有关整个每日维基的 widget 例子比较简单,主要是帮助大家积累常用代码,
了解 Android 平台 JSON 的处理方式,毕竟很多 Server 还是 Java 的。
12.Android 中使用定时器TimerTask类介绍
 Android 平台中需要反复按周期执行方法可以使用Java 上自带的 TimerTask 类,
TimerTask 
相对于 Thread 来说对于资源消耗的更低,
除了使用 Android 自带的AlarmManager 使用 Timer 定时器是一种更好的解决方法。
我们需要引入 importjava.util.Timer; import java.util.TimerTask;
private Timer mTimer = new Timer(true);
private TimerTask mTimerTask;
mTimerTask = new TimerTask()
{
public void run()
{
Log.v("android123","cwj");
}
};
mTimer.schedule(mTimerTask, 5000,1000); //
 1 秒后每秒执行一次定时器中的方法,
比如本文为调用 log.v 打印输出。
如果想取消可以调用下面方法,取消定时器的执行
while(!mTimerTask.cancel());
mTimer.cancel();
最后 Android123 提示大家,如果处理的东西比较耗时还是开个线程比较好,
Timer 
还是会阻塞主线程的执行,更像是一种消息的执行方式。
当然比 Handler  postDelay 等方法更适合处理计划任务。
13.Android 应用应用Icon大小在不同分辨率下定义
对于 Android 平台来说,不同分辨率下 Icon 的大小设计有着不同的要求,对于目前主流的HDPI  WVGA 级别来说,通常 hdpi
的应用 icon 大小为 72x72,而标准的 mdpi  hvga  48x48,对于目前 HTC  Motorola 推出的一些 QVGA 的使用了 ldpi,图
标为 32x32
常见的 Android 图标大小设计规范如下表所示:
Launcher
::::
36 x 36 px
48 x 48 px
72 x 72 px
MenuMenuMenu
::::
36 x 36 px
48 x 48 px
72 x 72 px
Status Bar

24 x 24 px
32 x 32 px
48 x 48 px
TabTabTab
::::
24 x 24 px
32 x 32 px
48 x 48 px
Dialog
::::
24 x 24 px
32 x 32 px
48 x 48 px
List View
::::
24 x 24 px
32 x 32 px
48 x 48 px
对于 android 界面设计的安全色,如下:
而对于系统自带默认程序的图标,下面为png的透明格式,直接鼠标右键另存为即可:
看看 sdk 文档上的关于界面图标的详细说明。
14.Android 控件美化Shape你会用吗?
如果你对 Android 系统自带的 UI 控件感觉不够满意,可以尝试下自定义控件,我们就以Button 为例,很早以前 Android123
就写到过 AndroidButton 按钮控件美化方法里面提到了xml  selector 构造。当然除了使用 drawable 这样的图片外今天
Android 
开发网谈下自定义图形 shape 的方法,对于 Button 控件 Android 上支持以下几种属性 shapegradientstroke
corners 
等。
我们就以目前系统的 Button  selector 为例说下:
<shape>
<gradient
android:startColor="#ff8c00"
android:endColor="#FFFFFF"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#dcdcdc" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
对于上面,这条 shape 的定义,分别为渐变,
 gradient 
startColor 
属性为开始的颜色,
endColor 
为渐变结束的颜色,
下面的 angle 是角度。接下来是 stroke 可以理解为边缘,
corners 
为拐角这里 radius 属性为半径,最后是相对位置属性padding
对于一个 Button 完整的定义可以为
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<gradient
android:startColor="#ff8c00"
android:endColor="#FFFFFF"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#dcdcdc" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item> <item android:state_focused="true" >
<shape>
<gradient
android:startColor="#ffc2b7"
android:endColor="#ffc2b7"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#dcdcdc" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item> <item>
<shape>
<gradient
android:startColor="#ff9d77"
android:endColor="#ff9d77"
android:angle="270" />
<stroke
android:width="2dp"
android:color="#fad3cf" />
<corners
android:radius="2dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
</selector> 
注意Android123 提示大家,以上几个 item 的区别主要是体现在state_pressed 按下或state_focused
得焦点时,当当来判断显示什么类型,而没有state_xxx 属性的 item 可以看作是常规状态下。
15. Android 开发者应该保持以下特质
Android123 推荐新手应该遵循
1. 
深读 SDK 文档
2. 
深读 SDK  APIDemo  Samples
3. 
掌握 Git 开源代码
4. 
多了解 Android 开源项目,学习别人的手法写程序。
16. Android 数组排序常见方法
Android 的数组排序方式基本上使用了Sun 原生的 Java API 实现,常用的有 Comparator 接口实现 compare 方法和
Comparable 
接口的 compareTo 方法,我们对于一个数组列表比如ArrayList 可以通过这两个接口进行排序和比较,这里
Android123 
给大家一个例子
private final Comparator cwjComparator = new Comparator() {
private final Collator collator = Collator.getInstance();
public final int compare(Object a, Object b) {
CharSequence a = ((Item) a).sName;
CharSequence b = ((Item) b).sID;
return collator.compare(a, b);
}
};
我们的 ArrayList 对象名为 mList,则执行排序可以调用方法Collections.sort(mList, cwjComparator);
17.Android 控件控件TextProgressBar进度条上显文字
Android 系统的进度条控件默认的设计的不是很周全,比如没有包含文字的显示,那么如何在Android 进度条控件上显
示文字呢来自 Google 内部的代码来了解下,主要使用的addView 这样的方法通过覆盖一层 Chronometer秒表控件来实现,
整个代码如下
public class TextProgressBar extends RelativeLayout implementsOnChronometerTickListener {
public static final String TAG = "TextProgressBar";
static final int CHRONOMETER_ID = android.R.id.text1;
static final int PROGRESSBAR_ID = android.R.id.progress;
Chronometer mChronometer = null;
ProgressBar mProgressBar = null;
long mDurationBase = -1;
int mDuration = -1;
boolean mChronometerFollow = false;
int mChronometerGravity = Gravity.NO_GRAVITY;
public TextProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TextProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TextProgressBar(Context context) {
super(context);
} //Android 
开发网提示关键部分在这里
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
int childId = child.getId();
if (childId == CHRONOMETER_ID && child instanceof Chronometer) {
mChronometer = (Chronometer) child;
mChronometer.setOnChronometerTickListener(this);
// Check if Chronometer should move with with ProgressBar
mChronometerFollow = (params.width == ViewGroup.LayoutParams.WRAP_CONTENT);
mChronometerGravity = (mChronometer.getGravity() &Gravity.HORIZONTAL_GRAVITY_MASK);
} else if (childId == PROGRESSBAR_ID && child instanceof ProgressBar) {
mProgressBar = (ProgressBar) child;
}
}
@android.view.RemotableViewMethod
public void setDurationBase(long durationBase) {
mDurationBase = durationBase;
if (mProgressBar == null || mChronometer == null) {
throw new RuntimeException("Expecting child ProgressBar with id " +
"'android.R.id.progress' and Chronometer id 'android.R.id.text1'");
}
// Update the ProgressBar maximum relative to Chronometer base
mDuration = (int) (durationBase - mChronometer.getBase());
if (mDuration <= 0) {
mDuration = 1;
}
mProgressBar.setMax(mDuration);
}
public void onChronometerTick(Chronometer chronometer) {
if (mProgressBar == null) {
throw new RuntimeException(
"Expecting child ProgressBar with id 'android.R.id.progress'");
}
// Stop Chronometer if we're past duration
long now = SystemClock.elapsedRealtime();
if (now >= mDurationBase) {
mChronometer.stop();
} int remaining = (int) (mDurationBase - now);
mProgressBar.setProgress(mDuration - remaining);
if (mChronometerFollow) {
RelativeLayout.LayoutParams params;
params = (RelativeLayout.LayoutParams) mProgressBar.getLayoutParams();
int contentWidth = mProgressBar.getWidth() - (params.leftMargin +params.rightMargin);
int leadingEdge = ((contentWidth * mProgressBar.getProgress()) /
mProgressBar.getMax()) + params.leftMargin;
int adjustLeft = 0;
int textWidth = mChronometer.getWidth() ;
if (mChronometerGravity == Gravity.RIGHT) {
adjustLeft = -textWidth;
} else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) {
adjustLeft = -(textWidth / 2);
}
leadingEdge += adjustLeft;
int rightLimit = contentWidth - params. rightMargin - textWidth;
if (leadingEdge < params.leftMargin) {
leadingEdge = params.leftMargin;
} else if (leadingEdge > rightLimit) {
leadingEdge = rightLimit;
}
params = (RelativeLayout.LayoutParams) mChronometer.getLayoutParams();
params.leftMargin = leadingEdge;
mChronometer.requestLayout();
}
}
}
18. Android 内存管理-SoftReference的使用
很多时候我们需要考虑 Android 平台上的内存管理问题,Dalvik VM 给每个进程都分配了一定量的可用堆内存,当我们
处理一些耗费资源的操作时可能会产生OOM 错误(OutOfMemoryError)这样的异常,Android123 观察了下国内的类似 Market
客户端设计,基本上都没有采用很好的内存管理机制和缓存处理。
如果细心的网友可能发现 AndroidMarket 客户端载入时,每个列表项的图标是异步刷新显示的,但当我们快速的往下
滚动到一定数量比如 50 个,再往回滚动时可能我们看到了部分App 的图标又重新开始加载,当然这一过程可能是从SQLite
数据库中缓存的,但是在内存中已经通过类似SoftReference 的方式管理内存。
 Java 中内存管理,引用分为四大类,强引用HardReference、弱引用WeakReference、软引用SoftReference和虚引
PhantomReference。它们的区别也很明显,HardReference对象是即使虚拟机内存吃紧抛出OOM 也不会导致这一引用的对
象被回收,而WeakReference 等更适合于一些数量不多,但体积稍微庞大的对象,在这四个引用中,它是最容易被垃圾回收
的,而我们对于显示类似 AndroidMarket 中每个应用的 App Icon 时可以考虑使用SoftReference 来解决内存不至于快速回
收,同时当内存短缺面临 Java VM 崩溃抛出 OOM 前时,软引用将会强制回收内存,最后的虚引用一般没有实际意义,仅仅观
 GC 的活动状态,对于测试比较实用同时必须和ReferenceQueue 一起使用。
对于一组数据,我们可以通过HashMap 的方式来添加一组SoftReference 对象来临时保留一些数据,同时对于需要反复
通过网络获取的不经常改变的内容,可以通过本地的文件系统或数据库来存储缓存,希望给国内做App Store 这样的客户端
一些改进建议。
19. 反射在Android开发中的利弊
由于 Android 2.2的推出,很多新的 API 加入导致很多项目移植需要考虑使用Java 的反射机制 Reflection 来动态调用,
动态调用的好处就是不需要使用引用文件,直接通过JDK 中声明好的方法直接调用,本身原理基于 JVM 的,从 Java 1.5 开始
支持,原理上就是根据类名而不实例化对象的情况下,获得对象的方法或属性而直接调用。
Android 
开发时反射能帮助我们多少?
1. 
有些网友可能发现 Android  SDK 比较封闭,很多敏感的方法常规的用户无法编译,我们如果翻看了代码直接在反
射中声明动态调用即可。比如很多internal  I 开头的 AIDL 接口均可以通过反射轻松调用。
2. 
反射对于 Android123 来说更重要的是考虑到应用的兼容性,我们目前主要兼容从Android 1.5  2.2 的项目,API
Level 
 3  8 可以方便的扩充,调用前我们预留一个标志位声明该API 的最低以及最高的 API Level 为多少可以调用。
3. 
对于调试 Java 的反射是功臣了,在 Logcat 中我们可以看到出错的地方肯定有类似java.lang.reflect.XXX 的字样,
这种自检机制可以帮助我们方便的调试Android 应用程序。
反射的缺点有哪些:
(1). 
因为是动态执行的,效率自然没有预编译时引用现有的库效率高,就像平时我们Win32 开发时,可以不用 h 文件,
直接通过GetProcAddress 一样去动态获取方法的地址。当然效率要根据复杂程度而决定,一般稍微复杂的处理性能损失可
能超过 20%,对于一些复杂的涉及 Java 自动类型转换判断,执行时间可能是直接引用的上千倍,所以最终我们调试时必须考
虑性能问题。
(2). 
因为反射是动态的,所以需要处理很多异常,不然Dalvik 崩溃出 Force Close的概率会大很多,很简单的一个反
射就需要至少 3 个异常捕获,本身 try-catch 效率就不是很高,自然进一步影响运行效率,对于Android 开发我们必须考虑
这些问题。
(3). 
反射因为导致代码臃肿,自然稍微复杂的几个方法实用反射将会导致代码可读性和维护性降低,如果很抽象的调
 Android 开发网强烈不推荐这种方法。最后要说的是 Reflection 并不是 Java 的专利,微软的.Net 也同样支持,同时
更多的动态语言如 Ruby 等均支持这一特性。
20.AsyncTask 对比对比Thread加加Handler
很多网友可能发现 Android 平台很多应用使用的都是 AsyncTask,而并非 Thread  Handler 去更新 UI
这里 Android123 给大家说下他们到底有什么区别,我们平时应该使用哪种解决方案。
 Android 1.5开始系统将 AsyncTask 引入到 android.os 包中,过去在很早 1.1  1.0 SDK 时其实官方将其命名为
UserTask
,其内部是 JDK 1.5 开始新增的 concurrent 库,做过 J2EE 的网友可能明白并发库效率和强大性,比Java 原始的
Thread 
更灵活和强大,但对于轻量级的使用更为占用系统资源。
Thread 
 Java 早期为实现多线程而设计的,比较简单不支持concurrent 中很多特性在同步和线程池类中需要自己去实
现很多的东西,对于分布式应用来说更需要自己写调度代码,而为了Android UI 的刷新 Google 引入了 Handler  Looper
机制,它们均基于消息实现,有事可能消息队列阻塞或其他原因无法准确的使用。
Android 
开发网推荐大家使用 AsyncTask 代替Thread+Handler 的方式,不仅调用上更为简单,经过实测更可靠一些,
Google 
 Browser 中大量使用了异步任务作为处理耗时的I/O 操作,比如下载文件、读写数据库等等,它们在本质上都离不
开消息,但是 AsyncTask 相比 Thread  Handler 更为可靠,更易于维护,但 AsyncTask 缺点也是有的比如一旦线程开启即
dobackground 
方法执行后无法给线程发送消息,仅能通过预先设置好的标记来控制逻辑,当然可以通过线程的挂起等待标志
位的改变来通讯,对于某些应用Thread  Handler 以及 Looper 可能更灵活。
21. Android Drawable 叠加处理方法
大家可能知道 Bitmap 的叠加处理在 Android 平台中可以通过 Canvas 一层一层的画就行了,
 Drawable 中如何处理呢除了使用BitmapDrawable  getBitmap 方法将 Drawable 转换为 Bitmap 外,今天 Android123
给大家说下好用简单的LayerDrawable 类,LayerDrawable顾名思义就是层图形对象。
下面直接用一个简单的代码表示:
Bitmap bm = BitmapFactory.decodeResource(getResources(),R.drawable.cwj);
Drawable[] array = new Drawable[3];
array[0] = new PaintDrawable(Color.BLACK); //
黑色
array[1] = new PaintDrawable(Color.WHITE); //
白色
array[2] = new BitmapDrawable(bm); //
位图资源
LayerDrawable ld = new LayerDrawable(array); //
参数为上面的 Drawable 数组
ld.setLayerInset(1, 1, 1, 1, 1); //
第一个参数 1 代表数组的第二个元素,为白色
ld.setLayerInset(2, 2, 2, 2, 2); //
第一个参数 2 代表数组的第三个元素,为位图资源
mImageView.setImageDrawable(ld);
上面的方法中LayerDrawable 是关键,Android 开发网提示setLayerInset
方法原型为 public voidsetLayerInset (int index, int l, int t, int r, int b) 其中第一个参数为层的索引号,
后面的四个参数分别为 lefttopright  bottom
对于简单的图片合成我们可以将第一和第二层的PaintDrawable 换成BitmapDrawable 即可实现简单的图片合成。
22. onRetainNonConfigurationInstance 和和getLastNonConfigurationInstance
很多网友可能知道 Android 横竖屏切换时会触发 onSaveInstanceState
而还原时会产生onRestoreInstanceState
但是 Android  Activity 类还有一个方法名为
onRetainNonConfigurationInstance 
 getLastNonConfigurationInstance 这两个方法。
我们可以通过
onRetainNonConfigurationInstance 
代替 onSaveInstanceState,比如距离 2
@Override
public Object onRetainNonConfigurationInstance()
{
//
这里需要保存的内容,在切换时不是bundle 了,我们可以直接通过 Object 来代替
return obj;
}
在恢复窗口时,我们可以不使用onRestoreInstanceState,而代替的是getLastNonConfigurationInstance 方法。
我们可以直接在 onCreate 中使用,比如
Object obj = getLastNonConfigurationInstance(); 
最终 obj 的内容就是上次切换时的内容。
这里 Android123 提醒大家,每次 Activity 横竖屏切换时 onCreate 方法都会被触发。
23. Android 中中String资源文件的format 方法方法
很多时候我们感性 Google 在设计 Android 时遵守了大量 MVC 架构方式,可以让写公共代码、美工和具体逻辑开发人员
独立出来。
有关 Android 的资源文件values/strings.xml 中如何实现格式化字符串呢?
这里 Android123 举个简单的例子,以及最终可能会用到哪些地方。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">cwj_Demo</string>
<string name="hello">android 
开发网</string>
</resources>
上面是一段简单的字符串资源文件,没有用到格式化,因为比较简单直接描述了意思,
当我们设计一个类似 Delete xxxFile ? 的时候,我们可能需要在 Java 中动态获取 xxx 的名称,所以定义资源时使
用格式化可以轻松解决,不需要一堆String 去拼接或StringBuffer 一个一个 append 这样的愚蠢方法,
看例子
<string name="alert">Delete %1$s File</string>
这里%1$s 代表这是一个字符串型的,如果是整数型可以写为%1$d,类似 printf 这样的格式化字符串函数,
当然如果包含了多个需要格式化的内容,则第二个可以写为%2$s%2$d 了,那么最终在 Java 中如何调用呢看下面的
例子:
例一整数型的
<string name="alert">I am %1$d years old</string> 
定义的是这样的
当然,我们杜绝意外情况,比如冒出个secret 这样的 string 类型的,注意上面是%1$d 不是%1$s,所以默认标准的合并
成为
int nAge=23;
String sAgeFormat = getResources().getString(R.string.alert);
String sFinalAge = String.format(sAgeFormat, nAge); 
这样执行完后,就组成了 I am 23years old,是不是很方便啊.
当然了,下面看下 String 字符串时的情况.
例二字符串型的
String sName="cwj"
String sCity="Shanghai"
资源定义为
<string name="alert2">My name is %1$s , I am form%2$s</string>
 Java 中只需要 StringsInfoFormat = getResources().getString(R.string.alert2);
String sFinalInfo=String.format(sInfoFormat, sName, sCity);
我们看到了整个,整个定义类似MFC CString::Format  Mac OS 中的 NSLog,但是需要显示类似 C#中那样显示的标
出参数的数字,比如%1 %n,这里数字代表参数的第 n 个。
本行最终 sFinalInfo 显示的内容为 My name iscwj , I am form Shanghai 
当然了你有什么不懂的地方可以来函至android123@163.com
24. Android 工程内嵌资源文件的两种方法
Android 软件一般处理大的资源通过sdcard 比如在线下载资源到 sdcard
 apk 中内嵌资源或二进制文件时一般使用下面的两种方法:
方法一
res/raw 
目录下存放,比如 cwj.dat 一个二进制文件,我们可以读取可以直接
InputStream is=context.getResources().openRawResource(R.raw.cwj);
方法二
工程根目录下的 assets 文件夹中存放,比如assets/cwj.dat 这样我们使用下面的代码
AssetManager am = context.getAssets();
InputStream is = am.open(cwj.dat);
这里 Android123 提示大家 Google  Android 系统处理 Assert 有个 bug,在AssertManager 中不能处理单个超过1MB 
文件,不然会报异常具体数值大家可以测试下传个稍大的文件,我们在两年前的文章中有提到,而第一种raw 没这个限制可
以放个 4MB  Mp3 文件没问题。
25. Android 自定义View以及以及layout 属性全攻略
对于 Android 系统的自定义 View 可能大家都熟悉了,对于自定义View 的属性添加,以及 Android  Layout 的命名空
间问题,很多网友还不是很清楚,今天Android123 一起再带大家温习一下
CwjView myView=new CwjView(context);
如果用于游戏或整个窗体的界面,
我们可能直接在 onCreate setContentView(myView); 当然如果是控件,我们可能会需要从Layout  xml 中声明,比

<cn.com.android123.CwjView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
当然,我们也可以直接从父类声明比如
<View class="cn.com.android123.CwjView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
上面我们仅用了父类 View 的两个属性,均来自 android 命名空间,而名称为layout_width layout_height,我们自
定义的控件可能有更多的功能,比如
<cn.com.android123.CwjView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
cwj:age="22"
cwj:university="sjtu"
cwj:city="shanghai"
/>
我们可以看到上面的三个属性,是我们自定义的。
作为标准 xml 规范,可能还包含了类似
xmlns:android="http://schemas.android.com/apk/res/android" 
这样的语句,对于定义完整的View,我们的命名空
间为 cwj
这里可以写为
xmlns:cwj=http://schemas.android.com/apk/res/cn.com.android123.cwjView 

xmlns:cwj=http://schemas.android.com/apk/res/android 
都可以。
对于定义的 cwj 命名空间和 ageuniversity 以及 city 的三个属性我们如何定义呢在工程的 res/values 目录中我们
新建一个cwj_attr.xml 文件,编码方式为 utf-8 是一个好习惯,内容如下
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<declare-styleable name="CwjView">
<attr name="age" format="integer" />
<attr name="city" format="string" />
<attr name="university" format="string" />
</declare-styleable>
</resources>
这里我们可能对 format 不是很熟悉,目前 Android 系统内置的格式类型有 integer 比如 ProgressBar的进度值,float
比如 RatingBar 的值可能是 3.5 颗星,boolean 比如 ToggleButton的是否勾选,string 比如 TextView  text 属性,
当然除了我们常见的基础类型外,Android的属性还有特殊的比如 color 是用于颜色属性的,可以识别为#FF0000等类
型,当然还有 dimension 的尺寸类型,比如 23dip15px18sp 的长度单位,还有一种特殊的为reference,一般用于
引用@+id/cwj@drawable/xxx 这样的类型。当然什么时候用 reference 我们就以定义一个颜色为例子,<attr
name="red" format="color|reference" /> 
这里我们用了逻辑或的运算符,定义的红色是颜色类型的,同时可以被引
当然,对于我们自定义的类中,我们需要使用一个名为obtainStyledAttributes 的方法来获取我们的定义。在我
们自定义 View 的构造方法(Contextcontext, AttributeSet attrs)的重载类型中可以用
public CwjView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.cwj_attr);
mAge = a.getInteger(R.styleable.CwjView_age, 22);
mCity = a.getString(R.styleable.CwjView_city, "shanghai");
mUniversity= a.getString(R.styleable.CwjView_university, "sjtu");
a.recycle(); 
//Android123 提示大家不要忘了回收资源
}
这样类的全局成员变量 mAgemCity 就获取了我们需要的内容,当然根据layout 中的数值我们自定义的 CwjView需要
动态的处理一些数据的情况,可以使用AttributeSet 类的getAttributeResourceValue 方法获取。
public CwjView(Context context, AttributeSet attrs)
{
super(context, attrs);
resId = attrs.getAttributeResourceValue("cn.com.android123.CwjView","age", 100);
resId = attrs.getAttributeResourceValue("cn.com.android123.CwjView","city", "shanghai");
//resID 就可以任意使用了
}
以上两种方法中,参数的最后一个数值为默认的,如果您有不明白的地方可以来函到android123@163.com 我们会在第
一时间回复。
26. 自定义Android主题风格theme.xml方法方法
 Android 中可以通过自定义主题风格方式来实现个性化以及复用,
首先我们创建 theme.xml 主题文件,保存位置为工程的res/values/theme.xml 
这里我们可以可以为主题起一个名称,比如CWJ,这里去除了 xml 的文件头
<?xml version="1.0" encoding="utf-8"?>
这行,
我们在工程中只需在androidmanifest.xml 文件的Activity 节点中加入
android:theme="@style/Theme.CWJ" 
属性,则这个 Activity 就使用了这种主题风格,
整个 xml 的关键代码如下:
<resources>
<style name="Theme.CWJ" parent="android:Theme">
<itemname="android:windowBackground">@drawable/android123</item>
</style>
</resources>
其中上面的代码中,我们定义设置全局android:windowBackground 即背景值为/res/drawable中的 android123 图片为
背景,更多的属性定义可以参考view  layout xml 属性设置,比如我们设置所有字体颜色、大体大小和样式,可以在style
节点中加入
<item name="android:textColor">#fff</item>
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
当然我们可以将上面的 android123 的图片改进下,使用一个 xml 文件替代,比如使用 bitmap 对象,则
/res/drawable/android123.xml 
的完整代码变为
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/cwj_image"
android:tileMode="repeat" />
这里我们使用了一个 bitmap 对象来解析 cwj_image 图片,当然这里可以识别各种类型的图片,
其中android:tileMode bitmap 的内部属性,其中 tileMode 设置为 repeat 代表重复,这样可以节省 bitmap 资源,
比如我们的背景是一层楼,那么全屏可以显示同样的为层效果,而图片仅是一层大小,对于资源利用相对更高。 
当然 bitmap 的属性 tileMode 的值为 repeat 外还有其他的值比如 clampmirror,这些值并没有在 SDK 中并没有找到定
义,通过上次 Android 开发网的 Android 自定义 View 以及 layout 属性全攻略一文,我们可以联想到 bitmap 属于
android.graphics.Bitmap 
包,由于是android 框架,所以下载 git  base 包,找到该类,类的实例化时android123已经
 Android 自定义 View 以及 layout 属性全攻略说的很清楚,所以我们定位到res\values 中找到 attr.xml 有关 bitmap 
定义即可,有关 bitmap 的更多属性如 antialiasfilter  dither 都可以找到使用。
27. android 调试工具monkey压力测试实战
很多 Android 开发者可能因为没有充分测试自己的软件造成很容易出现FC(Force Close)的问题,这里我们可以通过使
 Android 固件中自带的 monkey 工具来做软件的压力测试,monkey 工具可以模拟各种按键,触屏,轨迹球、activity等事
件,这里 Android123 提示大家说白了 monkey 就是一个小猴子随机狂玩你的android 软件,看看会不会产生异常。具体
的使用我们通过 Android SDK给我们的 adb 调试桥链接设备或模拟器,进入Linux Shell 状态,当然我们可以输入 adb shell
获取设备的 shell,也可以直接通过 adb 命令执行,比如说 adb shellmonkey 来查看 monkey 工具中的参数说明,如

我们要测试的apk 文件要在 android 设备中已经安装,当然模拟器中也可以测试的。执行adb shell monkey -p
cn.com.android123.cwj -v 100 
我们执行这句的中包含了参数,这里代表已安装软件的packageName,而 v 代表查看 monkey
生成的详细随机事件名,最后的数字100 为我们测试的随机事件数量为100.有关更多的测试方法,请查看上图中的参数,整
个测试比较简单单很有效,不妨试试。
28. 自定义View
有关 Android 的自定义 View 的框架今天我们一起讨论下,对于常规的游戏,
我们在 View 中需要处理以下几种问题:
1.
控制事件
2.
刷新 View
3. 
绘制 View
1. 
对于控制事件今天我们只处理按键事件onKeyDown,以后的文章中将会讲到屏幕触控的具体处理onTouchEvent以及
Sensor 
重力感应等方法。
2. 
刷新 view 的方法这里主要有invalidate(int l, int t, int r, int b) 刷新局部,四个参数分别为左、上、右、
下。整个 view 刷新invalidate(),刷新一个矩形区域invalidate(Rect dirty) ,刷新一个特性Drawable
invalidateDrawable(Drawable drawable) 
,执行 invalidate 类的方法将会设置 view 为无效,最终导致 onDraw 方法被重
新调用。由于今天的 view 比较简单,Android123 提示大家如果在线程中刷新,除了使用handler 方式外,可以在Thread 
直接使用postInvalidate 方法来实现。
3. 
绘制 View 主要是 onDraw()中通过形参 canvas 来处理,相关的绘制主要有 drawRectdrawLinedrawPath 等等。
view 
方法内部还重写了很多接口,其回调方法可以帮助我们判断出view 的位置和大小,
比如
onMeasure(int, int) Called to determine the size requirements for this view andall of its children. 

onLayout(boolean, int, int, int, int) Called when this view should assign asize and position to all of its
children 
onSizeChanged(int, int, int, int) Called when the size of this view haschanged. 具体的作用,
大家可以用 Logcat 获取当 view 变化时每个形参的变动。
下面 cwjView 是我们为今后游戏设计的一个简单自定义View 框架,我们可以看到在 Android 平台自定义 view 还是很简
单的,同时 Java 支持多继承可以帮助我们不断的完善复杂的问题。
public class cwjView extends View {
public cwjView(Context context) {
super(context);
setFocusable(true); //
允许获得焦点
setFocusableInTouchMode(true); //
获取焦点时允许触控
}
@Override
protected Parcelable onSaveInstanceState() { //
处理窗口保存事件
Parcelable pSaved = super.onSaveInstanceState();
Bundle bundle = new Bundle();
//dosomething
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) { //
处理窗口还原事件
Bundle bundle = (Bundle) state;
//dosomething
super.onRestoreInstanceState(bundle.getParcelable("cwj"));
return;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) //
处理窗口大小变化事件
{
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec); //
如果不让父类处理记住调用setMeasuredDimension
}
@Override
protected void onLayout (boolean changed, int left, int top, int right, intbottom)
{
super.onLayout (changed,left,top, ight,bottom) ;
}
@Override
protected void onDraw(Canvas canvas) {
Paint bg = new Paint();
bg.setColor(Color.Red);
canvas.drawRect(0, 0, getWidth()/2, getHeight()/2, bg); //
 view 的左上角四分之一填充为红色
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event); //
让父类处理屏幕触控事件
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) { //
处理按键事件,响应的轨迹球事件为public boolean 
onTrackballEvent (MotionEvent event)
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
break;
case KeyEvent.KEYCODE_DPAD_CENTER: //
处理中键按下
break;
default:
return super.onKeyDown(keyCode, event);
}
return true;
} } 
上面我们可以看到 onMeasure 使用的是父类的处理方法,如果我们需要解决自定义View 的大小,可以尝试下
面的方法
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
height = View.MeasureSpec.getSize(heightMeasureSpec);
width = View.MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(width,height);//
这里面是原始的大小,需要重新计算可以修改本行
//dosomething
}
29. Canvas 和和Paint实例实例
昨天我们在 Android 游戏开发之旅三 View 详解中提到了 onDraw 方法,
有关详细的实现我们今天主要说下Android  Canvas  Paint 对象的使用实例。
Canvas 
类主要实现了屏幕的绘制过程,其中包含了很多实用的方法,比如绘制一条路径、区域、贴图、画点、画线、渲
染文本,下面是 Canvas 类常用的方法,当然 Android 开发网提示大家很多方法有不同的重载版本,参数更灵活。

void drawRect(RectF rect, Paint paint) //
绘制区域,参数一为 RectF 一个区域
void drawPath(Path path, Paint paint) //
绘制一个路径,参数一为 Path 路径对象
void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) //
贴图,参数一就是我们常规的Bitmap 
象,参数二是源区域(Android123 提示这里是 bitmap),参数三是目标区域(应该在 canvas 的位置和大小),参数四是 Paint
画刷对象,因为用到了缩放和拉伸的可能,当原始Rect 不等于目标 Rect 时性能将会有大幅损失。
void drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //
画线,参数一起始点
 x 轴位置,参数二起始点的 y 轴位置,参数三终点的 x 轴水平位置,参数四 y 轴垂直位置,最后一个参数为Paint画刷对
象。
void drawPoint(float x, float y, Paint paint) //
画点,参数一水平 x 轴,参数二垂直 y 轴,第三个参数为 Paint
对象。
void drawText(String text, float x, float y, Paint paint) //
渲染文本,Canvas 类除了上面的还可以描绘文字,
参数一是 String 类型的文本,参数二 x 轴,参数三 y 轴,参数四是 Paint 对象。
void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paintpaint) //
在路径上绘制文
本,相对于上面第二个参数是Path 路径对象从上面来看我们可以看出 Canvas 绘制类比较简单同时很灵活,实现一般的
方法通常没有问题,同时可以叠加的处理设计出一些效果,不过细心的网友可能发现最后一个参数均为Paint 对象。如果我
们把 Canvas 当做绘画师来看,那么 Paint 就是我们绘画的工具,比如画笔、画刷、颜料等等。
Paint 
类常用方法:
void setARGB(int a, int r, int g, int b) 
设置 Paint 对象颜色,参数一为alpha 透明通道
void setAlpha(int a) 
设置alpha 不透明度,范围为 0~255
void setAntiAlias(boolean aa) //
是否抗锯齿
void setColor(int color) //
设置颜色,
这里 Android 内部定义的有 Color 类包含了一些常见颜色定义
void setFakeBoldText(boolean fakeBoldText) //
设置伪粗体文本
void setLinearText(boolean linearText) //
设置线性文本
PathEffect setPathEffect(PathEffect effect) //
设置路径效果
Rasterizer setRasterizer(Rasterizer rasterizer) //
设置光栅化
Shader setShader(Shader shader) //
设置阴影
void setTextAlign(Paint.Align align) //
设置文本对齐
void setTextScaleX(float scaleX) //
设置文本缩放倍数,1.0f 为原始
void setTextSize(float textSize) //
设置字体大小
Typeface setTypeface(Typeface typeface) //
设置字体,Typeface 包含了字体的类型,粗细,还有倾斜、颜色等。
void setUnderlineText(boolean underlineText) //
设置下划线
最终 Canvas  Paint  onDraw 中直接使用
@Override
protected void onDraw(Canvas canvas) {
Paint paintRed=new Paint();
paintRed.setColor(Color.Red);
canvas.drawPoint(11,3,paintRed); //
在坐标 11,3 上画一个红点
}
下一次 Android123 将会具体讲到强大的 Path 路径,和字体 Typeface 相关的使用。
30. View 类详解
 Android 游戏开发之旅二中我们讲到了View  SurfaceView的区别,
今天 Android123  View 类开始着重的介绍 Android 图形显示基类的相关方法和注意点。
自定义 View 的常用方法:
onFinishInflate() 
View 中所有的子控件均被映射成 xml 后触发
onMeasure(int, int) 
确定所有子元素的大小
onLayout(boolean, int, int, int, int) 
 View 分配所有的子元素的大小和位置时触发
onSizeChanged(int, int, int, int) 
 view 的大小发生变化时触发
onDraw(Canvas) view 
渲染内容的细节
onKeyDown(int, KeyEvent) 
有按键按下后触发
onKeyUp(int, KeyEvent) 
有按键按下后弹起时触发
onTrackballEvent(MotionEvent) 
轨迹球事件
onTouchEvent(MotionEvent) 
触屏事件
onFocusChanged(boolean, int, Rect) 
 View 获取或失去焦点时触发 
onWindowFocusChanged(boolean) 
当窗口包含的view 获取或失去焦点时触发
onAttachedToWindow()
view 被附着到一个窗口时触发
onDetachedFromWindow() 
view 离开附着的窗口时触发,Android123 提示该方法和
onAttachedToWindow() 
是相反的。
onWindowVisibilityChanged(int) 
当窗口中包含的可见的view 发生变化时触发
以上是 View 实现的一些基本接口的回调方法,一般我们需要处理画布的显示时,
重写 onDraw(Canvas)用的的是最多的:
@Override
protected void onDraw(Canvas canvas) {
//
这里我们直接使用 canvas 对象处理当前的画布,比如说使用Paint 来选择要填充的颜色 
Paint paintBackground = new Paint();
paintBackground.setColor(getResources().getColor(R.color.xxx)); //
 Res 中找到名为 xxx  color 颜色定义
canvas.drawRect(0, 0, getWidth(), getHeight(), paintBackground); //
设置当前画布的背景颜色为paintBackground
中定义的颜色,以 0,0 作为为起点,以当前画布的宽度和高度为重点即整块画布来填充。
具体的请查看 Android123 未来讲到的 Canvas  Paint,在 Canvas 中我们可以实现画路径,图形,区域,线。
 Paint 作为绘画方式的对象可以设置颜色,大小,甚至字体的类型等等。当然还有就是处理窗口还原状态问题(
般用于横竖屏切换),除了在 Activity 中可以调用外,开发游戏时我们尽量在View 中使用类似
@Override
protected Parcelable onSaveInstanceState() {
Parcelable p = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putInt("x", pX);
bundle.putInt("y", pY);
bundle.putParcelable("android123_state", p);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
dosomething(bundle.getInt("x"), bundle.getInt("y")); //
获取刚才存储的 x  y 信息
super.onRestoreInstanceState(bundle.getParcelable("android123_state"));
return;
}
 View 中如果需要强制调用绘制方法onDraw,可以使用invalidate()方法,它有很多重载版本,同时在线程中的
postInvailidate()
方法将在Android 游戏开发之旅六中的自定义 View 完整篇讲到。
31. View 和和SurfaceView
 Android 游戏当中充当主要的除了控制类外就是显示类,在J2ME 中我们用 Display  Canvas 来实现这些,而 Google
Android 
中涉及到显示的为 view 类,Android 游戏开发中比较重要和复杂的就是显示和游戏逻辑的处理。这里我们说下
android.view.View 
android.view.SurfaceViewSurfaceView是从 View 基类中派生出来的显示类,直接子类有
GLSurfaceView 
 VideoView,可以看出 GL 和视频播放以及 Camera 摄像头一般均使用 SurfaceView,到底有哪些优势呢?
SurfaceView 
可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了SurfaceHolder 类,使用 getHolder
方法获取,相关的有:
Canvas lockCanvas()
Canvas lockCanvas(Rect dirty) 

void removeCallback(SurfaceHolder.Callback callback)

void unlockCanvasAndPost(Canvas canvas) //
控制图形以及绘制,
而在SurfaceHolder.Callback 接口回调中可以通过下面三个抽象类可以自己定义具体的实现,比如第一个更改格式和
显示画面。
abstract void surfaceChanged(SurfaceHolder holder, int format, int width, intheight)
abstract void surfaceCreated(SurfaceHolder holder)
abstract void surfaceDestroyed(SurfaceHolder holder)
对于 Surface 相关的,Android 底层还提供了 GPU 加速功能,所以一般实时性很强的应用中主要使用SurfaceView
不是直接从 View 构建,同时 Android123 未来后面说到的 OpenGL 中的GLSurfaceView 也是从该类实现。
32. Android 程序内存管理必读
很多开发者都是从 J2ME  J2EE 上过来的,对于内存的使用和理解并不是很到位,Android开发网本次给大家一些架构
上的指导,防止出现豆腐渣工程的出现。Android作为以 Java 语言为主的智能平台对于我们开发一些高性能和质量的软件来
说了解 Android 程序内存管理机制是必须的。Android  Dalvik VM 在基础方面和 Sun JVM 没有什么大的区别仅仅是字节码
的优化,我们要知道什么时候用gc 什么时候用 recycle 以及到底用不用finalization,因为 Java 对内存的分配只需要 new
开发者不需要显示的释放内存,但是这样造成的内存泄露问题的几率反而更高。
1.
对于常规开发者而言需要了解Java 的四种引用方式,比如强引用,软引用,弱引用以及虚引用。一些复杂些的程序
在长期运行很可能出现类似 OutOfMemoryError的异常。
2.
并不要过多的指望 gc,不用的对象可以显示的设置为空,比如obj=null,这里 Android123 提示大家,java  gc 使
用的是一个有向图,判断一个对象是否有效看的是其他的对象能到达这个对象的顶点,有向图的相对于链表、二叉树来说开
销是可想而知。
3.Android 
为每个程序分配的对内存可以通过Runtime 类的totalMemory() freeMemory() 两个方法获取VM 的一些内存
信息,对于系统 heap 内存获取,可以通过 Dalvik.VMRuntime类的getMinimumHeapSize() 方法获取最小可用堆内存,同时
显示释放软引用可以调用该类的gcSoftReferences() 方法,获取更多的运行内存。
4.
对于多线程的处理,如果并发的线程很多,同时有频繁的创建和释放,可以通过concurrent 类的线程池解决线程创
建的效率瓶颈。
5. 
不要在循环中创建过多的本地变量。有关 Android  Java 的系统性能分析,Android123 将在以后的文章中详细讲
述如何调试 Java 分析内存泄露以及 Android 上的 gdb 调试器分析得出内存性能改进。
33. Android 中内嵌字体实现个性化
 Android 中我们的应用可以灵活的内嵌自己的字体文件,实现各个手机上可以正常的显示个性化文字,我们都知道
TextView 
 setTypeface方法可以设置目标文字的显示特性,比如字体、颜色、粗体、斜体等。我们直接找一个TrueTypeFont
的字体文件即.ttf,对于 Win32 系统的用户可以直接在Windows/fonts 文件夹中能找到很多。比如微软雅黑就不错,可是体
积太大,由于 Android  Assets 类有单个文件 1MB 体积的限制,我们先找个英文字体做测试。这里我们将字体文件
android123.ttf 
放到工程的 assets 文件夹的 fonts 目录中。
Typeface tf = Typeface.createFromAsset(getAssets(),"fonts/android123.ttf");
TextView tv = (TextView)findViewById(R.id.text);
tv.setTypeface(tf); //
设置TextView 的风格
tv.setText("CWJ Test");
tv.setTextSize(12);
tv.setTextColor(Color.RED);
34. 获取和设置ListView的选择项
获取当前选中项 int curPos= listView.getFirstVisiblePosition(); 当然是用 getItemAtPosition(int nPos)方法
也可以,设置当前选择位置listView.setSelectedPosition(lastPos) ; 对于基于 AbsListView 为基类的 ListView 等控
件均可以使用这种方法。
35. android.text.format 文件大小和日期解析类
很多网友可能直接将自己的 J2ME 项目生硬的移植到 Android 平台,其实 Google 为我们提供好了文件大小和时间日期解
析类,它位于android.text.format 这个包中,它提供了强大的标准化解析方法:
1. IP 
地址解析类android.text.format.Formatter 中提供了String formatIpAddress(int addr) 这个方法可以轻
松方便的将 socket 中的 int 型转成类似 127.0.0.1  IP 格式,需要注意的是 Linux 平台的字节顺序,即小字节序、低字节
little-endian
2. 
文件大小解析类细心的网友可能还看到了android.text.format.Formatter 中的formatFileSize 方法,该方法
String formatFileSize (Context context, long number) 
,第二个参数是 long 型,一般为 File 对象的最后修改时间或
创建时间的方法,最终返回类似12KB5Bytes 的值,20MB 的字符串。
3. 
日期时间解析类,该类位于android.text.format.DateFormat 这个package 中,该类提供了 Java 中的三种时间对
象,Android123 提示大家下面三种方法为静态可以直接调用,如下:
final static CharSequence format(CharSequence inFormat, Date inDate)//传入 Date 对象
Given a format string and a Date object, returns a CharSequencecontaining the requested date.
final static CharSequence format(CharSequence inFormat, CalendarinDate) //Calendar 对象
Given a format string and a Calendar object, returns a CharSequencecontaining the requested date.
final static CharSequence format(CharSequence inFormat, longinTimeInMillis) //long 对象
Given a format string and a time in milliseconds since Jan 1,1970 GMT, returns a CharSequence containing the
requested date.
我们可能看到了第一个参数均为inFormat 这是一个CharSequence 接口的 String 类型,它提供了灵活的时间格式解析
字符串描述,Android 开发网提示大家注意大小写要区分,如
April 6, 1970 at 3:23am 
例子,那么inFormat 参数的写法和最终执行的结果如下对照,下面就以Android123  CWJ
生日为例子如下
"MM/dd/yy h:mmaa" -> "11/03/87 11:23am"
"MMM dd, yyyy h:mmaa" -> "Nov 3, 1987 11:23am"
"MMMM dd, yyyy h:mmaa" -> "November 3, 1987 11:23am"
"E, MMMM dd, yyyy h:mmaa" -> "Tues, November 3, 198711:23am"
"EEEE, MMMM dd, yyyy h:mmaa" -> "Tuesday, Nov 3, 198711:23am"
对于判断一个时间是否为 24 小时制式可以通过android.text.format.DateFormat 类的static
boolean is24HourFormat(Context context)
方法来判断。
36. Android 代码性能优化技巧
目前来说 Android 2.2 JIT 性能有了本质的提高,不过对于老版本的程序提高Java 执行效率还有很多语言特点来说,
今天 Android123 提到的不是语法糖,而是基础的问题,对于Java 1.5 之后将会有明显的改进。下面的例子来自SDK: static
class Foo {
int mSplat;
}
Foo[] mArray = ... 
上面的静态类Foo 的执行效果和性能,我们分三个方法zeroone  two 来做对比。
public void zero() { //
大多数人可能简单直接这样写
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray.mSplat;
}
}
public void one() { //
通过本地对象改进性能
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum += localArray.mSplat;
}
}
public void two() { //
推荐的方法,通过Java 1.5 的新语法特性可以大幅改进性能
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}
zero() is slowest, because the JIT can't yet optimize away the cost of gettingthe array length once for
every iteration through the loop. one() is faster. It pulls everything out intolocal variables, avoiding the
lookups. Only the array length offers a performance benefit. two() is fastestfor devices without a JIT, and
indistinguishable from one() for devices with a JIT. It uses the enhanced forloop syntax introduced in version
1.5 of the Java programming language.
37. Android 开发注意点PartOne
Android 已经的很多细节问题我们通过平台开发总结不断完善这个列表,如果你有相关的内容可以联
android123@163.com . 一、AssetManager- 已知单个文件处理不能大于 1MB,所以如果资源很大,建议使用Zip 
式压缩存放。二、ScrollView 中嵌入 ListView - 这个作法可能会出现你的 ListView 仅仅显示 1 行半。三、Android
自带的 Zip 处理类对文件名编码无法识别,也没有提供显示的设置方法,在zlib 中写死了。四、使用一些资源对象记
住关闭,比如对于文件流对象最后FileOutputStream os = xxx; try {
//dosomething
} finally {
os.close(); //
显示的使用 finally 关闭文件对象。
对于 Cursor 而言,在移动位置时首先判断Cursor 是否为空,最终使用完仍然需要close 方法,
如果重用,可以使用 deactivate 方法释放当前资源,通过 requery 方法再次查询。五、SDK 中标记为 deprecated字样
的,常规情况下是有更好的方法可以替代,短期内可以放心使用。这些方法一般高版本的SDK 都可以向上兼容,目前尚未发
 Android 放弃某些 API 的支持。六、Notification Intent 无法传递到目标的 ActivityService  Broardcast 
有测试过,中途需要通过PendingIntent,可能这里出现了问题。
38. Android 上上HTTP协议通讯状态获取
通常情况下轻量级的 Http 传输 Android 平台可以直接使用 Sun Java HttpURLConnection 类方法处理,比如果自己定
义一次请求 header 可以通过setRequestProperty 设置,而我们需要获取的Http Web Server 状态可以通过
HttpURLConnection.getResponseCode() 
的方法获取。当然Http 协议返回值常见的有 200 为成功,400 为请求错误,404
为未找到,500 为服务器内部错误,403 无权查看,302 为重定向等等。对于 Android 平台提供更完善的 Apache类有
HttpClient 
HttpPostHttpResponseHttpGet  HttpEntity,其中对于数据报头 header 构造通过 HttpEntity,而返回
状态值可以通过HttpResponse 获取。有关 Android 客户端和 Server 通讯类相关的开发我们将会在以后文章中做大量实
例介绍。
39. Android 布局布局Java代码构造法
一般情况下对于 Android 程序布局我们往往使用 XML 文件来编写,这样可以提高开发效率,但是考虑到代码的安全性以
及执行效率,可以通过 Java 代码执行创建,虽然 Android 编译过的 xml 是二进制的,但是加载 xml 解析器的效率对于资源
占用还是比较大的,一般一个简单的TextView,比如<TextView
android:id="@+id/textControl "
android:layout_width="100px"
android:layout_height="wrap_content" /> 
可以等价于下面的 Java 代码:LinearLayout.LayoutParams
textParams = new LinearLayout.LayoutParams(100, LayoutParams.WRAP_CONTENT); //
宽度为 100px,高为自适应最小的
高度 //setOrientation(VERTICAL); 设置布局为垂直TextView textControl = new TextView(this);//如果从一
 XXXLayout.,比如LinearLayout  View 的基类时这里 this 应该换成为创建改类的 Context
textControl.setText("Android 
开发网欢迎您");
addView( textControl, textParams ); 
当然 Java 处理效率比XML 快得多,但是对于一个复杂界面的编写,可能需
要一些套嵌考虑,如果你思维灵活的话,使用Java 代码来布局你的 Android 应用程序是一个更好的方法。
40. 测试测试Android软件性能主要方法
对于 Android 平台上软件的性能测试可以通过以下几种方法来分析效率瓶颈,目前Google  Android 软件开发过程中
已经引入了多种测试工具包,比如Unit 测试工程,调试类,还有模拟器的Dev Tools 都可以直接反应执行性能。 1.
模拟器上的 Dev Tools 可以激活屏幕显示当前的 FPSCPU 使用率,可以帮助我们测试一些3D 图形界面的性能。2. 
般涉及到网络应用的程序,在效率上和网速有很多关系,这里需要多次的调试才能实际了解。3. 对于逻辑算法的效率
执行,我们使用 Android 上最普遍的,计算执行时间来查看:long start = System.currentTimeMillis();
//android 
开发网提示这里做实际的处理do something
long duration = System.currentTimeMillis() - start; 
最终 duration 保存着实际处理该方法需要的毫秒
数。这里类似 Win32 上的GetTickCount,在 Win 32  Symbian 上都提供了高精度的性能计数器和低阶计时器,这里在Dalvik
VM 
上的 Java 层这种方法对于一般的应用足以。4. GC 效率跟踪,如果你执行的应用比较简单,可以在DDMS 中查看下 Logcat
 VM 释放内存情况,大概模拟下那些地方可以缓存数据或改进算法的。5. 线程的使用和同步,Android 平台上给我们
提供了丰富的多任务同步方法,但在深层上并没有过多的比如自旋锁等高级应用,不过对于Service  appWidget而言,他
们实际的产品中都应该以多线程的方式处理,以释放CPU 时间,对于线程和堆内存的查看这些都可以在DDMS 中看到。
多的调试和性能测试方法 Android123 将在以后的内容中出现。
41. Splash Screen 开场屏在Android 中的实现
很多网友可能发现近期 Tencent 推出的手机 QQ Android 版包含了一个开场屏 Splash Screen载入效果,通常游戏或大
型软件打开时可能需要一个释放解析资源的过程,需要一个前台的动画播放和后台的逻辑处理线程配合,当然对于简单的软
件也可以加一个 SplashScreen 作为美化。在 Android 平台上如何实现呢首先创建一个 Activirty,在SetContentView
时直接通过 ImageView 创建一个全屏的图片,Android123 提示大家还要考虑好分辨率和当前设备一致,onCreate添加代码
如下: newHandler().postDelayed(new Runnable(){ // 为了减少代码使用匿名 Handler 创建一个延时的调用
public void run() {
Intent i = new Intent(SplashScreen. this, Main.class); //
通过 Intent 打开最终真正的主界面 Main
这个 Activity
SplashScreen.this.startActivity(i); //
启动 Main 界面
SplashScreen.this.finish(); //
关闭自己这个开场屏
}
}, 5000); //5 
秒,够用了吧
42. Android 的的Activity你知多少呢?
看到这个标题很多网友肯定回答,我知道Activity  Android 上的窗口基类,了解 Activity 的生命周期比如 onCreate
onStop 
等,呵呵,按照这样说 Android123 还知道 Activity 的实现其实是从ApplicationContext,而ApplicationContext
是从 Context 这个抽象类派生而来的,当然我们看到显示的是View 或者 ViewGroup,当然今天说的不是这些东西,而是很多
网友来问的 Android 为什么不设计一个任务管理器,当然从Android 1.5 开始ActivityManager 类提供了restartPackage
可以关闭一个程序,需要加上<uses-permissionandroid:name="android.permission.RESTART_PACKAGES"/>这个权限,不
过我们注意到,长按 Home 键可以看到以前程序的运行,同时可以快速的切换回来。这就是Android 独有的程序生命周期管
理机制 Activity 历史栈。我们在一个普通的程序主窗口中打开了一个窗口 B,而窗口 B 打开了窗口 C,但是按下 Back
键后结果出乎了预期,是的这就是Activity  historystack 的原因,在数据结构中栈是 FIFO 的,阻止我们不愿意看的情
况的发生则可以在打开新 Activity 时加上标记FLAG_ACTIVITY_NO_HISTORY,代码如下:Intent i= new Intent(this,
cwj.class);
i.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); //Android 
开发网提示大家相关的还有
Intent.FLAG_ACTIVITY_CLEAR_TOP
,都试试
startActivity(i); 
当然更多的程序Activity 控制可以再androidmanifest.xml 中定义。
43. JSONObject 在在Android上的应用
如果你过去开发过 AJAX 应用,相信对 JSONObject 不会陌生吧,作为基于 JavaScript 的数据交换格式,可以直接代替
Xml
,这里 Android  1.0 开始就完全支持 JSONObject。在平时应用中直接引入 importorg.json.JSONObject;即可方便使
用。当然同类的还有 SOAP在常规使用方便 JSONObject 对象可以实现类似 Bundle  Parcel 可以封装数据,代替一个
XML 
 ITEM,但最大的优势是可以执行一些简单的方法,比如说getStringhasputgetBooleangetInt 等数据类型的
存取操作。Android123 提示大家对于常规的项目开发,今天本文不考虑Server 端的布局,在 Android 平台上处理这些比较
简单,主要是一些 http 的请求处理。可以直接引入 importorg.apache.http.xxx 来实现web server 层的数据交换,如果
你没有专业的 Server 开发技术,可以通过简单的 Web 配合 JSON 方式快速实现自己的交互式应用。
44. Android 高性能文件类MemoryFile
很多网友抱怨 Android 处理底层 I/O 性能不是很理想,如果不想使用NDK 则可以通过 MemoryFile 类实现高性能的文件
读写操作。MemoryFile 顾名思义就是内存文件的意思,如果你过去从事过Win32 开发,那么它的原理就是MapViewOfFile()
当然开发过 Linux 的网友可能很快就联想到了 mmap(),是的该类就是他们的托管代码层封装,位于android.os.MemoryFile
这个位置,从 Android 1.0开始就被支持。 MemoryFile 适用于哪些地方呢对于 I/O 需要频繁操作的,主要是和外部
存储相关的 I/O 操作,MemoryFile 通过将 NAND  SD 卡上的文件,分段映射到内存中进行修改处理,这样就用高速的RAM
代替了 ROM  SD 卡,性能自然提高不少,对于Android 手机而言同时还减少了电量消耗。Android123提示网友该类实现的
功能不是很多,直接从 Object 上继承,通过 JNI 的方式直接在 C 底层执行。主要的构造方法MemoryFile(String name, int
length) 
,这里第二个参数为文件大小,需要说明的是Android  MemoryFile 和传统的 mmap 还有一点点区别,毕竟是手机,
它内部的内存管理方式 ashmem 会从内核中回收资源。毕竟目前部分低端机型的RAM 也比较吃紧。synchronized
boolean allowPurging(boolean allowPurging) //
允许 ashmem 清理内存,线程安全同步的方式。
void close() //
关闭,因为在 Linux 内部 mmap 占用一个句柄,不用时一定要释放了
InputStream getInputStream() 
返回读取的内容用Java 层的 InputStream保存
OutputStream getOutputStream() 
把一个OutputSream 写入到 MemoryFile 
boolean isPurgingAllowed() //
判断是否允许清理
int length() //
返回内存映射文件大小下面就是我们熟悉的,读写细节,主要是对字符数组的操作,这里大家要计算好
每个文件类型的占用,同时考虑到效率对于自己分配的大小考虑粒度对齐。
int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) 
具体的实际应用,Android 开发网
将在下次和大家讲到。
45. TextUtils 类类-Android字符串处理类
对于字符串处理 Android 为我们提供了一个简单实用的TextUtils 类,如果处理比较简单的内容不用去思考正则表达式
不妨试试这个在android.text.TextUtils 的类,主要的功能如下:是否为空字符 static
boolean isEmpty(CharSequence str) 
拆分字符串 public static String[] split (String text, Stringexpression) 
Android 
开发网提示大家仔细看例子如下String.split() returns [''] when the string to be split is empty. This
returns []. This does not remove any empty strings from the result. For examplesplit("a,", "," ) returns {"a",
""}. 
拆分字符串使用正则 publicstatic String[] split (String text, Pattern pattern) 确定大小写是否有效在当
前位置的文本TextUtils.getCapsMode(CharSequence cs, int off, int reqModes) 使用 HTML 编码这个字符串 static
String TextUtils.htmlEncode(String s)
46. InputSream 输入流转String字符串,,,Android 开发工具类
 Android 平台上使用 Java 层处理 I/O 时主要使用流,这里 Android 开发网给大家一个方便的类,可以处理InputStream
输入流转为 String 字符串,在效率上,我们使用了字符串拼接StringBuilder 类减少内存碎片以及BefferedReader类实现
一个缓存。 privateString Stream2String(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 16*1024);//
强制缓存大小为
16KB
,一般 Java 类默认为 8KB
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) ! = null) { //
处理换行符
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
} }
47. layout 资源包含,,,android开发必读
有时候我们在一个 Android 程序中可能会复用布局文件,这时可以在一个xml 文件中复用过去的布局文件,但是和常规
的使用不同的是,需要加上类似包含头文件一样的include 关键字,比如下面我们需要包含layout 文件夹下的view.xml 
局文件,需要<includelayout="@layout/view" /> 这样下,完整的如下,大家可以试一试。<?xml version="1.0"
encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cwj"
/>
<include layout="@layout/view" />
<include android:id="@+id/block" layout="@layout/item"/> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/android123"
/>
</LinearLayout>
48.Android 控件开发之ToggleButton原理原理
 Android 平台上比较有特色的就是ToggleButton 控件,虽然它的功能和 CheckBox 有些类似,但是他们的用处还是有
一定的区别比如ToggleButton 原本有图片装饰,通过ToggleButton 可以很清楚的显示某些状态。它们均从Button为基类
CompoundButton 中实现,其真假事件从 Checkable 来实现。
public abstract class CompoundButton extends Button implementsCheckable {
private boolean mChecked; 
//状态是否选中
private int mButtonResource;
private boolean mBroadcasting;
private Drawable mButtonDrawable; 
//按钮的图标
private OnCheckedChangeListener mOnCheckedChangeListener; //选中状态改变监听
private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
private static final int[] CHECKED_STATE_SET = {
R.attr.state_checked
};
public CompoundButton(Context context) {
this(context, null);
}
public CompoundButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CompoundButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a =context.obtainStyledAttributes(attrs,com.android.internal.R.styleable.CompoundButton,
defStyle, 0);
Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
if (d != null) {
setButtonDrawable(d);
}
boolean checked = a.getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);
setChecked(checked);
a.recycle(); 
//显式的GC
}
public void toggle() {
setChecked(!mChecked);
}
@Override
public boolean performClick() {
toggle();
return super.performClick();
}
public boolean isChecked() {
return mChecked;
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState(); 
//更新当前状态的按钮图标
if (mBroadcasting) {
return;
}
mBroadcasting = true;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
}
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
mBroadcasting = false;
}
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
mOnCheckedChangeWidgetListener = listener;
}
public static interface OnCheckedChangeListener {
void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
}
public void setButtonDrawable(int resid) {
if (resid != 0 && resid == mButtonResource) {
return;
}
mButtonResource = resid;
Drawable d = null;
if (mButtonResource != 0) {
d = getResources().getDrawable(mButtonResource);
}
setButtonDrawable(d);
}
public void setButtonDrawable(Drawable d) {
if (d != null) {
if (mButtonDrawable != null) {
mButtonDrawable.setCallback(null);
unscheduleDrawable(mButtonDrawable) ;
}
d.setCallback(this);
d.setState(getDrawableState());
d.setVisible(getVisibility() == VISIBLE, false);
mButtonDrawable = d;
mButtonDrawable.setState(null);
setMinHeight(mButtonDrawable.getIntrinsicHeight());
}
refreshDrawableState();
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean populated = super.dispatchPopulateAccessibilityEvent(event);
if (!populated) {
int resourceId = 0;
if (mChecked) {
resourceId = R.string.accessibility_compound_button_selected;
} else {
resourceId = R.string.accessibility_compound_button_unselected;
}
String state = getResources().getString(resourceId);
event.getText().add(state);
event.setChecked(mChecked);
}
return populated;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final Drawable buttonDrawable = mButtonDrawable;
if (buttonDrawable != null) {
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
final int height = buttonDrawable.getIntrinsicHeight();
int y = 0;
switch (verticalGravity) {
case Gravity.BOTTOM:
y = getHeight() - height;
break;
case Gravity.CENTER_VERTICAL:
y = (getHeight() - height) / 2;
break;
}
buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);
buttonDrawable.draw(canvas);
}
}

@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
@Override
protected void drawableStateChanged() { 
//android123提示状态改变时需要更换按钮的图标
super.drawableStateChanged();
if (mButtonDrawable != null) {
int[] myDrawableState = getDrawableState();
mButtonDrawable.setState(myDrawableState);
invalidate();
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mButtonDrawable;
}
static class SavedState extends BaseSavedState {
boolean checked;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
checked = (Boolean)in.readValue(null);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeValue(checked);
}
@Override
public String toString() {
return "CompoundButton.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " checked=" + checked + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR = newParcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
setFreezesText(true);
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState); 
ss.checked = isChecked();
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.checked);
requestLayout();
}
}
从上面来看我们知道CompuundButton 的实现相对繁琐了些,主要是考虑状态是否已经选中等情况的消息通知,Android
开发网提醒大家而ToggleButton 相对CompuundButton 增加的给用户而言主要是开关的文字显示。
public class ToggleButton extends CompoundButton {
private CharSequence mTextOn;
private CharSequence mTextOff;
private Drawable mIndicatorDrawable;
private static final int NO_ALPHA = 0xFF;
private float mDisabledAlpha;
public ToggleButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a =context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ToggleButton, defStyle, 0);
mTextOn = a.getText(com.android.internal.R. styleable.ToggleButton_textOn);
mTextOff = a.getText(com.android.internal.R.styleable.ToggleButton_textOff);
mDisabledAlpha =a.getFloat(com.android.internal.R.styleable.ToggleButton_disabledAlpha, 0.5f) ;
syncTextState();
a.recycle();
}
public ToggleButton(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyleToggle);
}
public ToggleButton(Context context) {
this(context, null);
}
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
syncTextState();
}
private void syncTextState() {
boolean checked = isChecked();
if (checked && mTextOn != null) {
setText(mTextOn);
} else if (!checked && mTextOff != null) {
setText(mTextOff);
}
}
public CharSequence getTextOn() {
return mTextOn;
}
public void setTextOn(CharSequence textOn) {
mTextOn = textOn;
}
public CharSequence getTextOff() {
return mTextOff;
}
protected void onFinishInflate() {
super.onFinishInflate();
updateReferenceToIndicatorDrawable(getBackground());
}
@Override
public void setBackgroundDrawable(Drawable d) {
super.setBackgroundDrawable(d);
updateReferenceToIndicatorDrawable(d);
}
private void updateReferenceToIndicatorDrawable(Drawable backgroundDrawable) {
if (backgroundDrawable instanceof LayerDrawable) {
LayerDrawable layerDrawable = (LayerDrawable) backgroundDrawable;
mIndicatorDrawable =layerDrawable.findDrawableByLayerId(com.android.internal.R.id.toggle);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mIndicatorDrawable != null) {
mIndicatorDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA *mDisabledAlpha));
}
}
}
49. AsyncTask 实例代码演示Android异步任务
上次我们讲到了 Android 提供了一个较线程更简单的处理多任务的方法AsyncTask 异步任务类,相对于线程来说
AsyncTask 
对于简单的任务处理更安全,其内部的实现方法使用了Android  Handler 机制,对于常见的文件下载可以使用
AsyncTask 
类来处理,在 Browser 浏览器中就是用了该类下载 Web 服务器 URL  Favicon 图标。首先Android123 以简单
的下载例子演示该类的大致结构,如下
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls);
publishProgress((int) ((i / (float) count)100));
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
//
最终我们执行
DownloadFilesTask().execute(url1, url2, url3); //
即可。
//
 Android 浏览器中下载 Favicon 图标的实现如下:
class DownloadTouchIcon extends AsyncTask<String, Void, Bitmap> {
private final ContentResolver mContentResolver;
private final Cursor mCursor;
private final String mOriginalUrl;
private final String mUrl;
private final String mUserAgent;
/* package */
BrowserActivity mActivity;
public DownloadTouchIcon(BrowserActivity activity, ContentResolver cr, Cursorc, WebView view) { //
构造
方法
mActivity = activity;
mContentResolver = cr;
mCursor = c;
mOriginalUrl = view.getOriginalUrl();
mUrl = view.getUrl();
mUserAgent = view.getSettings().getUserAgentString();
}
public DownloadTouchIcon(ContentResolver cr, Cursor c, String url) { //
实现本类的构造
mActivity = null;
mContentResolver = cr;
mCursor = c;
mOriginalUrl = null;
mUrl = url;
mUserAgent = null;
}
@Override
public Bitmap doInBackground(String... values) { //
返回 Bitmap 类型
String url = values[0];
AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
HttpGet request = new HttpGet(url);
HttpClientParams.setRedirecting(client.getParams() , true); //
处理 302 等重定向问题
try {
HttpResponse response = client.execute(request);
if (response.getStatusLine().getStatusCode() == 200) { //
如果 OK
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream content = entity.getContent(); //
将图标保存到 InputStream 中,因为是二进制内

if (content != null) {
Bitmap icon = BitmapFactory.decodeStream( //
从流中取出 Bitmap,这里使用了BitmapFactory
类的静态方法decodeStream
content, null, null);
return icon;
}
}
}
} catch (IllegalArgumentException ex) {
request.abort();
} catch (IOException ex) {
request.abort();
} finally {
client.close();
}
return null;
} @Override
protected void onCancelled() {
if (mCursor != null) {
mCursor.close();
}
} @Override
public void onPostExecute(Bitmap icon) {
if (mActivity != null) {
mActivity.mTouchIconLoader = null;
} if (icon == null || mCursor == null || isCancelled()) {
return;
}
//
最终图标要保存到浏览器的内部数据库中,系统程序均保存为SQLite 格式,Browser 也不例外,因为图片是二进制的
所以使用字节数组存储数据库的BLOB 类型
final ByteArrayOutputStream os = new ByteArrayOutputStream();
icon.compress(Bitmap.CompressFormat.PNG, 100, os); //
 Bitmap 压缩成 PNG 编码,质量为 100%存储
ContentValues values = new ContentValues(); //
构造 SQLite Content 对象,这里也可以使用 raw sql 代替
values.put(Browser.BookmarkColumns.TOUCH_ICON,os.toByteArray()); //
写入数据库的
//Browser.BookmarkColumns.TOUCH_ICON 
字段
if (mCursor.moveToFirst()) {
do {
mContentResolver.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
mCursor.getInt(0)),values, null, null);
} while (mCursor.moveToNext());
}
mCursor.close();
}
}
本次 Android 开发网通过两个 AsyncTask 类演示了多种类型的任务构造,这里大家注意返回类型,本节演示了Android
平台上 ContentProviderAsyncTaskBitmapHTTP 以及 Stream 的相关操作,大家如何想很快提高开发水平其实只要理
 Google 如何去实现 Android 系统常规构架就可以轻松入门谷歌移动平台。
50. Android 自定义View实例实例AnalogClock源码源码
针对 Android 底层 View 的直接构造很多网友没有实战经验,本次Android 开发网结合目前平台开源代码一起通过
AnalogClock 
类来理解 View 的直接继承。AnalogClock 就是 Home Screen上的那个带有两根指针的表盘类。它的实现我们直
接从开源代码可以了解到:
public class AnalogClock extends View {
private Time mCalendar;
private Drawable mHourHand; //
时针
private Drawable mMinuteHand; //
分针
private Drawable mDial; //
表盘背景
private int mDialWidth; //
表盘宽度
private int mDialHeight; //
表盘高度
private boolean mAttached; //
附着状态
private final Handler mHandler = new Handler(); //
定一个 Handler 类实现更新时间
private float mMinutes;
private float mHour;
private boolean mChanged; //
时间是否改变
public AnalogClock(Context context) {
this(context, null);
}
public AnalogClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AnalogClock(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Resources r = mContext.getResources();
TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.AnalogClock, defStyle, 0);
mDial = a.getDrawable(com.android.internal.R.styleable.AnalogClock_dial); //
加载表盘资源
if (mDial == null) {
mDial = r.getDrawable(com.android.internal.R.drawable.clock_dial);
}
mHourHand =a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_hour); //
加载时针图片资

if (mHourHand == null) {
mHourHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_hour);
}
mMinuteHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_minute); //
加载分针图

if (mMinuteHand == null) {
mMinuteHand = r.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
} mCalendar = new Time(); //
获取当前系统时间
mDialWidth = mDial.getIntrinsicWidth(); //
获取表盘图片的宽度
mDialHeight = mDial.getIntrinsicHeight(); //
高度,同上
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mAttached) {
mAttached = true;
IntentFilter filter = new IntentFilter();//
注册一个消息过滤器,获取时间改变时区,改变 action
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED) ;
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);
}
mCalendar = new Time();
onTimeChanged();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAttached) {
getContext().unregisterReceiver(mIntentReceiver); //
反注册消息过滤器
mAttached = false;
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
float hScale = 1.0f;
float vScale = 1.0f;
if (widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth){
hScale = (float) widthSize / (float) mDialWidth;
}
if (heightMode != MeasureSpec.UNSPECIFIED && heightSize <mDialHeight) {
vScale = (float )heightSize / (float) mDialHeight;
}
float scale = Math.min(hScale, vScale);
setMeasuredDimension(resolveSize((int) (mDialWidth * scale), widthMeasureSpec),
resolveSize((int) (mDialHeight * scale), heightMeasureSpec));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mChanged = true;
} //
主要的绘图重写 View  onDraw 方法,我们可以看到通过 canvas 实例直接屏幕
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
boolean changed = mChanged;
if (changed) {
mChanged = false;
}
int availableWidth = mRight - mLeft;
int availableHeight = mBottom - mTop; 
int x = availableWidth / 2;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值