Android开发网上的一些重要知识点

系统 3614 0

1. android单实例运行方法

我们都知道Android平台没有任务管理器,而内部App维护 者一个Activity history 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

这里可以再补充一下dip,sip的知识

3. Android中动态改变ImageView大小

很 多网友可能发现在layout.xml文件中定义了ImageView的绝对大小后,无法动态修改以后的大小显示,其实Android平台在设计UI控件 时考虑到这个问题,为了适应不同的Drawable可以通过在xml的相关ImageView中加入android:scaleType="fitXY" 这行即可,但因为使用了缩放可能会造成当前UI有所变形。使用的前提是限制ImageView所在的层,可以使用一个内嵌的方法限制显示。

4. 如何判断Android手机当前是否联网?

如 果拟开发一个网络应用的程序,首先考虑是否接入网络,在Android手机中判断是否联网可以通过 ConnectivityManager 类的isAvailable()方法判断,首先获取网络通讯类的实例 ConnectivityManager cwjManager=(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); ,使用cwjManager.getActiveNetworkInfo().isAvailable(); 来返回是否有效,如果为True则表示当前Android手机已经联网,可能是WiFi或GPRS、HSDPA等等,具体的可以通过 ConnectivityManager 类的getActiveNetworkInfo() 方法判断详细的接入方式,需要注意的是有关调用需要加入<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission> 这个权限,android开发网提醒大家在真机上Market和Browser程序都使用了这个方法,来判断是否继续,同时在一些网络超时的时候也可以检 查下网络连接是否存在,以免浪费手机上的电力资源。

5. Drawable、Bitmap、Canvas和Paint的关系

很 多网友刚刚开始学习Android平台,对于Drawable、Bitmap、Canvas和Paint它们之间的概念不是很清楚,其实它们除了 Drawable外早在Sun的J2ME中就已经出现了,但是在Android平台中,Bitmap、Canvas相关的都有所变化。

  首先让我们理解下Android平台中的显示类是View,但是还提供了底层图形类android.graphics,今天所说的这些均为graphics底层图形接口。

  Bitmap - 称作位图,一般位图的文件格式后缀为bmp,当然编码器也有很多如RGB565、RGB888。作为一种逐像素的显示对象执行效率高,但是缺点也很明显存储效率低。我们理解为一种存储对象比较好。

  Drawable - 作为Android平下通用的图形对象,它可以装载常用格式的图像,比如GIF、PNG、JPG,当然也支持BMP,当然还提供一些高级的可视化对象,比如渐变、图形等。

  Canvas - 名为画布,我们可以看作是一种处理过程,使用各种方法来管理Bitmap、GL或者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 (New I/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,ServerSocketChannel,Selector和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控件的样式为cwjListView,xml样式代码如下:

  <?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="cwjTheme" parent="android:Theme.Light">
   <item name="android:windowNoTitle">true</item>
   <item name="android:windowBackground">@android:color/transparent</item>
   <item name="android:listViewStyle">@style/cwjListView</item>
</style> 

有关ListView控件我们自定义的风格就是修改下系统listview这个控件的每行分隔符样式,这里我们在工程下res/drawable文件夹下放一个图片名为list_selector图片,这样我们的cwjListView的代码可以这样写

  <style name="cwjListView" parent="@android:style/Widget.ListView">
     <item name="android:listSelector">@drawable/list_selector</item>
   </style>
</resources>

  通过定义style可以设置更多,比如让cwjListView的字体颜色就加入textAppearance属性,比如 <item name="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);
            }

            Pattern pattern = Pattern.compile(SimpleWikiHelper.WORD_OF_DAY_REGEX); //正则表达式处理,有关定义见下面的SimpleWikiHelper类
            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的url,action是view即打开web浏览器
                PendingIntent pendingIntent = PendingIntent.getActivity(context,
                        0 /* no requestCode */, 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) throws ApiException {
        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定时器是一种更好的解决方法。 我们需要引入import java.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秒后每5秒执行一次定时器中的方法,比如本文为调用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

Menu
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

Tab
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就写到过 Android Button按钮控件美化方法 里面提到了xml的selector构造。当然除了使用drawable这样的图片外今天Android开发网谈下自定义图形shape的方法,对于Button控件Android上支持以下几种属性shape、gradient、stroke、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 implements OnChronometerTickListener {
    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客户端设计,基本上都没有采用很好的内存管理机制和缓存处理。

  如果细心的网友可能发现Android Market客户端载入时,每个列表项的图标是异步刷新显示的,但当我们快速的往下滚动到一定数量比如50个,再往回滚动时可能我们看到了部分App的图 标又重新开始加载,当然这一过程可能是从SQLite数据库中缓存的,但是在内存中已经通过类似SoftReference的方式管理内存。

  在Java中内存管理,引用分为四大类,强引用HardReference、弱引用WeakReference、软引用SoftReference和虚引 用PhantomReference。它们的区别也很明显,HardReference对象是即使虚拟机内存吃紧抛出OOM也不会导致这一引用的对象被回 收,而WeakReference等更适合于一些数量不多,但体积稍微庞大的对象,在这四个引用中,它是最容易被垃圾回收的,而我们对于显示类似 Android Market中每个应用的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 void setLayerInset (int index, int l, int t, int r, int b) 其中第一个参数为层的索引号,后面的四个参数分别为left、top、right和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 xxx File ? 的时候,我们可能需要在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 23 years old,是不是很方便啊.  当然了,下面看下String字符串时的情况.

  例二: 字符串型的

  String sName="cwj"

  String sCity="Shanghai"

   资源定义为   <string name="alert2">My name is %1$s , I am form %2$s</string> 

   则Java中只需要

  String sInfoFormat = 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 is cwj , 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命名空间和age、university以及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的尺寸类型,比如23dip,15px,18sp的长度单位,还有一种特殊的为reference,一般用于引用@+id/cwj @drawable/xxx这样的类型。

  当然什么时候用reference呢? 我们就以定义一个颜色为例子,

  <attr name="red" format="color|reference" />  这里我们用了逻辑或的运算符,定义的红色是颜色类型的,同时可以被引用

  当然,对于我们自定义的类中,我们需要使用一个名为obtainStyledAttributes的方法来获取我们的定义。在我们自定义View的构造方法(Context context, 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提示大家不要忘了回收资源

}

这样类的全局成员变量 mAge、mCity就获取了我们需要的内容,当然根据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">
        <item name="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资源,比如我们的背景是一层楼,那么全屏可以显示同 样的为5层效果,而图片仅是一层大小,对于资源利用相对更高。

  当然bitmap的属性tileMode的值为repeat外还有其他的值比如clamp、mirror,这些值并没有在SDK中并没有找到定义,通过上次Android开发网的 Android自定义View以及layout属性全攻略 一文,我们可以联想到bitmap属于android.graphics.Bitmap 包,由于是android框架,所以下载git的base包,找到该类,类的实例化时android123已经在 Android自定义View以及layout属性全攻略 说的很清楚,所以我们定位到res\values中找到attr.xml有关bitmap的定义即可,有关bitmap的更多属性如  antialias、filter和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 shell monkey来查看monkey工具中的参数说明,如图: 

  我们要测试的apk文件要在android设备中已经安装,当然模拟器中也可以测试的。执行adb shell monkey -p cn.com.android123.cwj -v 100 我们执行这句的中包含了p参数,这里代表已安装软件的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来处理,相关的绘制主要有drawRect、drawLine、drawPath等等。 view方法内部还重写了很多接口,其回调方法可以帮助我们判断出view的位置和大小,比如onMeasure(int, int) Called to determine the size requirements for this view and all of its children.  、onLayout(boolean, int, int, int, int) Called when this view should assign a size and position to all of its children 和onSizeChanged(int, int, int, int) Called when the size of this view has changed. 具体的作用,大家可以用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, int bottom)
   {
    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;
    

Android开发网上的一些重要知识点


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论