0%

本课讲的是如何实现一个Runnable,在一个独立线程上运行Runnable.run()方法.Runnable对象执行特别操作有时叫作任务。 Thread和Runnable都是基础的类,靠他们自己,能力有限。作为替代,Android有强大的基础类,像HandlerThread,AsyncTask,IntentService。Thread和Runnable也是ThreadPoolExecutor的基础类。这个类可以自动管理线程和任务队列,甚至可以并行执行多线程。

定义一个实现Runnable接口的类

public class PhotoDecodeRunnable implements Runnable {
    ...
    @Override
    public void run() {
        /*
         * Code you want to run on the thread goes here
         */
        ...
    }
    ...
}

实现run()方法

Runnable.run()方法包含了要执行的代码。通常,Runnable里可以放任何东西。记住,Runnable不会在UI运行,所以不能直接修改UI对象属性。与UI通讯,参考Communicate with the UI Thread 在run()方法的开始,调用 android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);设置线程的权重,android.os.Process.THREAD_PRIORITY_BACKGROUND比默认的权重要低,所以资源会优先分配给其他线程(UI线程) 你应该保存线程对象的引用,通过调用 Thread.currentThread()

class PhotoDecodeRunnable implements Runnable {
...
    /*
     * Defines the code to run for this task.
     */
    @Override
    public void run() {
        // Moves the current Thread into the background
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
        ...
        /*
         * Stores the current Thread in the PhotoTask instance,
         * so that the instance
         * can interrupt the Thread.
         */
        mPhotoTask.setImageDecodeThread(Thread.currentThread());
        ...
    }
...
}

翻自:http://developer.android.com/training/multiple-threads/index.html 对于那些长时间运行,数据密集操作的功能,拆分成更小的操作,放到多线程里,速度和效率会有些提升。在有多核CPU的设备上,系统可以多线程同时运行,而不是等一直在等cpu时间片。比如decode多个图片文件,在屏幕上显示缩略图,使用多线程会更快。

课程:

指定代码运行在一个线程里

学会如何让代码运行在独立的线程里,通过一个实现Runnable接口的类。

创建多线程管理器

学会用ThreadPoolExecutor管理线程。

让代码在线程池里的线程里运行

学会在线程池里的线程里运行Runnable

与UI线程通讯

学会让线程池里的线程与UI线程通讯

原文:http://developer.android.com/training/monitoring-device-state/manifest-receivers.html 如果是在代码里注册的广播接收器,unregister一下就可以了。但如果是在manifest里注册的,这样不行。使用PackageManager可以切换manifest里定义的任何组件启用状态,包括广播接收器。

ComponentName receiver = new ComponentName(context, myReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP)

当你失去网络连接时,可以通过这项技术,关闭除网络连接改变接收器以外的其他接收器。

原文:http://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html 在无网或网速差的状态下,没必要去连接服务器。 你可以使用 ConnectivityManager 来判断是否连到网络,以及网络类型。

判断是否有网络连接

下面的代码用ConnectivityManager查询是活动网络连接判断是否有Internet连接。

ConnectivityManager cm =
        (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
 
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
                      activeNetwork.isConnectedOrConnecting();

判断Internet连接类型

连接类型可能是移动数据,WiMax,WIFI,以太网。可以通过类似下面的方法查询网络类型:

boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;

移动数据比WIFI耗电,应该注意。下载大文件应该延迟到有wifi以后。

监听连接改变

ConnectivityManager会在连接改变时发送广播”android.net.conn.CONNECTIVITY_CHANGE”,所以,注册,监听一下这个广播就可以:

<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>

每次从移动数据和WIFI间改变时都会触发,这个改变可能会很频繁。最好是只有当你之前暂停下载或更新的时候,才去监听这个广播。通常在下载或更新前先检查一次网络连接。 下节讲切换开关在manifest里声明的接收器.

翻自:http://developer.android.com/training/monitoring-device-state/docking-monitoring.html 为了提高翻译效率,从这篇开始,个人认为没有必要的句子就不翻了,能看懂就行。 底座的状态跟充电状态类似,很多底座提供充电功能(座充). 底座状态同样使用sticky Intent广播。可以查询设备是否插入底座,哪种底座。

判断当前底座状态

广播的Action是ACTION_DOCK_EVENT,sticky Intent不需要注册真实的接收器

IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
Intent dockStatus = context.registerReceiver(null, ifilter);
//You can extract the current docking status from the EXTRA_DOCK_STATE extra:

//int dockState = battery.getIntExtra(EXTRA_DOCK_STATE, -1);
boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;

判断插入底座类型

插入底座有四种类型:

  • 车载
  • 桌面
  • Low-End (Analog) Desk(猜是显示模拟时钟)
  • High-End (Digital) Desk(猜是显示数字时钟)

后面两项是在API11以后引入的,所以,后面三者都判断是最好的。

boolean isCar = dockState == EXTRA_DOCK_STATE_CAR;
boolean isDesk = dockState == EXTRA_DOCK_STATE_DESK || 
                 dockState == EXTRA_DOCK_STATE_LE_DESK ||
                 dockState == EXTRA_DOCK_STATE_HE_DESK;

监控充电

ACTION_DOCK_EVENT会在插入、拨出底座时广播。所以接收这个广告就可以了

<action android:name="android.intent.action.ACTION_DOCK_EVENT"/>

可以用上一步相同的方法读取插入底座的类型和状态.

翻自:http://developer.android.com/training/improving-layouts/optimizing-layout.html 使用基本布局构建产生最高效的布局,是一个常见的错误想法。然而,每个你加到应用里的控件和布局都需要初始化,排列,绘画。比如,使用嵌套的LinearLayout会导致层次很深。此外,嵌套多个LinearLayout,使用layout_weight,因为每个child都需要测量两次而代价昂贵。这在layout被反复inflate时特别重要,像在ListView或GridView里使用时. 本节,你会学习到如何用Hierarchy Viewer和Layoutopt检验并优化布局。

检视你的布局

Android SDK工具里包含了一个叫Hierarchy Viewer的工具,它能在应用运行时分析布局。使用这个工具可以帮助你发现布局性能的瓶颈。 Hierarchy Viewer通过让你选择一个在连接的设备或模拟器上的进程来工作,然后显示布局树。每一个块上的红绿灯展示了测量,布局,绘画的性能,帮你鉴别潜在问题。 如,下图显示了一个ListView里的item的布局。这个布局在左边显示了一个小图片,右边两个堆放的text.这里特别重要,因为布局会被多次inflate,优化了一个,就会得到多倍的性能提升。
Layout listitem
图1
Hierarchy Viewer在/tools/下(可以从DDMS里启动,要求连接的设备已经root,否则看不到层次图),打开后,Hierarchy Viewer列出当前当前运行的可用设备。点击Load View Hierarchy可以查看选择组件的布局层次。如图2,展示了图1说的list item.
Hierarchy linearlayout
图2
Hierarchy layouttimes
图3
图2里,你可以看到三层布局会有些问题。在item上点击,会显示每个阶段处理时间(图3)。可以很清楚地看到这个item花了多少时间测量,布局,绘画,你该在哪里花时间去优化。 使用这个布局表现一个完整的list item,花费的时间: 测量: 0.977ms 布局: 0.167ms 绘画: 2.717ms

修正你的布局

因为上面的嵌套LinearLayout影响了性能,可以通过RelativeLayout减少层次来提高性能。
Hierarchy relativelayout
使用RelativeLayout修改后,只有两层。 修改后花费的时间: 测量: 0.598ms 布局: 0.110ms 绘画: 2.146ms 可以看到有一些小提升,但list里有多个item,所以优化减少的时候会是加N倍的。 由于使用LinearLayout的layout_weight,大多数时间是不一样的,这会降低测量的速度。这只是一个如何合理使用Layout的案例,必要的时候,你要小心考虑是否用layout weight

使用Lint

在布局文件上开启Lint工具扫描可以优化的地方是个很好的实践。Lint替换了Layoutopt工具,并且功能更强。一些Lint规则如下:

  • 使用compound drawables–包含ImageView和TextView的LinearLayout可以使用compound drawable实现,这样更高效。
  • 合并根Frame–如果FrameLayout是布局的根节点,并且不提供背景,padding等,可以用merge标签替换,略微高效
  • 无用的叶子–一个无背景,无下级元素的layout,通常可以删除(因为不可见)
  • 无用的父级–只有一个子元素的layout,如果不是ScrollView或根节点,并且没有背景,可以直接删除,把子元素移到外面一层
  • 深层布局–太多嵌套带来低性能,考虑使用更平级(层次少)的布局,像RelativeLayout或GridLayout来提高性能。最多不能超过10层

注:compound drawables指设置TextView的android:drawableLeft等 Lint的另一个好处是整合进了Eclipse的插件ADT里,当你导出apk,使用Layout Editor编辑保存xml文件时时,lint会自动运行。需要手动强制运行lint,可以按eclipse工具栏里的按钮:
Lint icon 在eclipse里使用时,lint有自动修复一些问题的能力,为其他问题提供建议,直接跳到问题代码处。如果你不用eclipse,lint也可以在命令行运行。更多关于lint的信息,访问 tools.android.com

翻自:http://developer.android.com/training/monitoring-device-state/battery-monitoring.html 当你在更改后台更新频率来减少这些更新对电池寿命的影响时,检查当前电量和充电状态是一个好的开始。 电池寿命通过剩余电量和充电状态来影响应用更新的执行。当用交流电充电时,执行更新操作对设备的影响是微不足道的,所以在大多数案例里,你可以把更新频率调到最快。如果设备不在充电,降低更新频率可以帮助延长电池寿命。 类似的,你可以检查电池剩余电量级别,在电量低时,应该降低更新频率甚至停止更新。 注:此处的更新,指的是类似发送心跳包的动作,或者定时更新内容。并非仅仅指更新应用版本。如果是用户动作,比如翻页刷新,不需要根据电量和充电状态处理。

判断当前充电状态

通过判断当前充电状态开始。BatteryManager会通过一个intent广播所有电池和充电详情,包含充电状态。 因为这是一个sticky intent,你不需要注册广播接收器。简单地通过调用 registerReceiver,像下面的代码段传入一个null的接收器,当前电池状态的intent就会返回。你也可以传入一个真实的接收器对象,但我们暂时不会操作更新,所以这是没必要的。

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
//你可以读到充电状态,如果在充电,可以读到是usb还是交流电

// 是否在充电
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                     status == BatteryManager.BATTERY_STATUS_FULL;

// 怎么充
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;

通常你应该在使用交流电充电时最大化后台更新频率,在使用usb充电时降低,不充电时更低。

监听充电状态的改变

充电状态很容易改变(插入/拔出充电器),所以监听充电状态,更改刷新频率很重要。 充电状态改变时,BatteryManager会发一个广播。接收这些事件很重要,甚至在应用没有运行的时候,因为可能你需要后台开启更新服务。所以,在Androidmanifest.xml里注册广播接收器,加上两个action:ACTION_POWER_CONNECTED 和ACTION_POWER_DISCONNECTED作为过滤。

<receiver android:name=".PowerConnectionReceiver">
  <intent-filter>
    <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
    <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
  </intent-filter>
</receiver>

在关联的广播接收器实现里,你可以读出当前充电状态,方法跟上一步说的相同:

public class PowerConnectionReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) { 
        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
        boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                            status == BatteryManager.BATTERY_STATUS_FULL;
    
        int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
        boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
        boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
    }
}

判断当前剩余电量

在某些案例里,判断当前剩余电量同样很有用。如果电量在某些水平之下,你可能会选择降低后台更新频率。 你可以用下面的代码读到电量:

//当前剩余电量
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
//电量最大值
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
//电量百分比
float batteryPct = level / (float)scale;

注:暂时不知道为什么要这样算,在我自己的机器上运行,scale就是100的。

监听剩余电量显著改变

持续监听电池状态不容易,但你不必这么做。 一般来说,持续监听电池电量对电池的影响比app的正常行为还要大。所以,只监听剩余电量的指定级别的改变(进入或离开低电量状态)是一个很好的实践。 manifest里声明的接收器,会在进入或离开低电量状态时触发。

<receiver android:name=".BatteryLevelReceiver">
<intent-filter>
  <action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
  <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
  </intent-filter>
</receiver>

剩余电量严重不足时,最好禁用所有后台更新。在你可以使用手机之前就关机了,这种情况下,如果刷新数据并不重要。 在很多情况下,设备是是插入到底座里充电的(好吧,反正我没见几个人额外花钱买底座的,可能国外较多)。下节讲怎么判断当前底座状态和监听插入底座时改变。

翻自http://developer.android.com/training/monitoring-device-state/index.html 作为一个好公民,App应该考虑到对宿主设备电池寿命的影响。这节课之后,你将能构建可以基于设备不同的状态监控修改它们的功能和行为的应用。 通过几个步骤,像在断网时关闭后台服务更新,在电量较少时降低更新频率。你可以确保你的app对电池续航影响最小化,不牺牲用户体验。

课程

监听电量和充电状态

学会如何根据判断,监听当前电池剩余电量和充电状态来改变应用更新频率。

判断和监听底座状态和类型

最佳的刷新频率可以基于设备如何被使用。学会如何判断、监听插入底座的状态和底座类型来影响你的app的行为。(貌似用底座的用户不多)

监听和判断连接状态

没有网络连接时,你不能在线更新app(app的内容,并非升级).学会如何检查网络状态来修改后台更新频率。同时也要学习在开始大数据传输操作前检查WIFI或移动数据连接。

根据需要手工操作广播接收器

在不需要的时候,AndroidManifest.xml里声明的广播接收器可以在app运行时切换来关闭。学会通过切换和状态改变接收器,延迟执行命令直到设备到了指定的状态。

翻自:http://developer.android.com/training/improving-layouts/loading-ondemand.html 有时,你的布局需要一些复杂,但很少用的视图。无论是item详情,进度指示器,撤销信息,你可以使用懒加载来减少内存占用并加速。

定义一个ViewStub

ViewStub是一个轻量级,没有尺寸并且不需要绘画也不会加到布局里。同样,inflate成本和加到视图布局的成本都很低。每个ViewStub仅仅需要使用android:layout指定inflate哪个布局。 下面的ViewStub是一个半透明的进度条浮层。它仅仅在会在新的item被导入到应用时显示。

<ViewStub
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"
    android:layout="@layout/progress_overlay"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />

载入ViewStub布局

当你想要载入ViewStub指定的布局时,可以调用setVisibility(View.VISIBLE)方法或inflate()方法

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

提示:一旦inflate()完成,会返回View,所以你不需要再调findViewById() 一旦visible/inflated,viewStub就不再是视图里的元素了。它会被指定的布局替换,这个布局的根节点id就是ViewStub的android:inflatedId属性指定的id。(android:id指定的ID只有在visible/inflated前有效) 提示:ViewStub的一个缺点是,不支持布局里的merge标签

翻自:http://developer.android.com/training/improving-layouts/reusing-layouts.html 尽管Android提供了各种组件来实现小而可复用的交互元素,你也可能因为布局需要复用一个大组件。为了高效复用完整布局,你可以使用标签嵌入另一个布局到当前布局。所以当你通过写一个自定义视图创建独立UI组件,你可以放到一个布局文件里,这样更容易复用。 复用布局因为其允许你创建可复用的复杂布局而显得非常强大。如,一个 是/否 按钮面板,或带描述文本的自定义进度条。这同样意味着,应用里多个布局里共同的元素可以被提取出来,独立管理,然后插入到每个布局里。

创建可复用布局

如果你已经知道哪个布局需要重用,就创建一个新的xml文件来定义布局。如,下面是一个来自G-Kenya代码库里定义标题栏的布局,它可以被插到每个Activity里:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=”match_parent”
    android:layout_height="wrap_content"
    android:background="@color/titlebar_bg">

    <ImageView android:layout_width="wrap_content"
               android:layout_height="wrap_content" 
               android:src="@drawable/gafricalogo" />
</FrameLayout>

根视图应该刚好和你在其他要插入这个视图的视图里相应位置一样。

使用标签

在你要添加可复用布局的布局里,添加标签。下面是添加标题栏:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width=”match_parent”
    android:layout_height=”match_parent”
    android:background="@color/app_bg"
    android:gravity="center_horizontal">

    <include layout="@layout/titlebar"/>

    <TextView android:layout_width=”match_parent”
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

你同样可以覆盖所有的布局参数(android:layout_*属性)

<include android:id=”@+id/news_title”
         android:layout_width=”match_parent”
         android:layout_height=”match_parent”
         layout=”@layout/title”/>

可是,如果你要用include标签覆盖布局属性,为了让其他属性生效,就必须覆盖android:layout_height和android:layout_width。

使用标签

标签帮助你排除把一个布局插入到另一个布局时产生的多余的View Group.如,你的被复用布局是一个垂直的线性布局,包含两个子视图,当它作为一个被复用的元素被插入到另一个垂直的线性布局时,结果就是一个垂直的LinearLayout里包含一个垂直的LinearLayout。这个嵌套的布局并没有实际意义,而且会让UI性能变差。 为了避免插入类似冗余的View Group,你可以使用标签标签作为可复用布局的根节点,如:

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/add"/>

    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/delete"/>

</merge>

现在,当你使用include标签插入这个布局到另一个布局时,系统会忽略merge标签,直接把两个Button替换到include标签的位置。