android蓝牙开发实例有没有什么工具能找到是哪个实例在增长

TraceView 是 Android 平台配备一个很好的性能分析的工具。它可以通过图形化的方式让我们了解我们要跟踪的程序的性能,并且能具体到 method。详细内容参考:
TraceView 简介
TraceView 是 Android 平台特有的数据采集和分析工具,它主要用于分析 Android 中应用程序的 hotspot。TraceView 本身只是一个数据分析工具,而数据的采集则需要使用 Android SDK 中的 Debug 类或者利用 DDMS 工具。二者的用法如下:
开发者在一些关键代码段开始前调用 Android SDK 中 Debug 类的 startMethodTracing 函数,并在关键代码段结束前调用 stopMethodTracing 函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是 Java 线程)的函数执行情况,并将采集数据保存到 /mnt/sdcard/ 下的一个文件中。开发者然后需要利用 SDK 中的 TraceView 工具来分析这些数据。
借助 Android SDK 中的 DDMS 工具。DDMS 可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。
DDMS 中 TraceView 使用示意图如下,调试人员可以通过选择 Devices 中的应用后点击
按钮 Start Method Profiling(开启方法分析)和点击& Stop Method Profiling(停止方法分析)
开启方法分析后对应用的目标页面进行测试操作,测试完毕后停止方法分析,界面会跳转到 DDMS 的 trace 分析界面,如下图所示:
TraceView 界面比较复杂,其 UI 划分为上下两个面板,即 Timeline Panel(时间线面板)和 Profile Panel(分析面板)。上图中的上半部分为 Timeline Panel(时间线面板),Timeline Panel 又可细分为左右两个 Pane:
左边 Pane 显示的是测试数据中所采集的线程信息。由图可知,本次测试数据采集了 main 线程,传感器线程和其它系统辅助线程的信息。
右边 Pane 所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图可知,Thread-1412 线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
另外,开发者可以在时间线 Pane 中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。
上图中的下半部分为 Profile Panel(分析面板),Profile Panel 是 TraceView 的核心界面,其内涵非常丰富。它主要展示了某个线程(先在 Timeline Panel 中选择线程)中各个函数调用的情况,包括 CPU 使用时间、调用次数等信息。而这些信息正是查找 hotspot 的关键依据。所以,对开发者而言,一定要了解 Profile Panel 中各列的含义。下表列出了 Profile Panel 中比较重要的列名及其描述。
TraceView 实战
了解完 TraceView 的 UI 后,现在介绍如何利用 TraceView 来查找 hotspot。一般而言,hotspot 包括两种类型的函数:
一类是调用次数不多,但每次调用却需要花费很长时间的函数。
一类是那些自身占用时间不长,但调用却非常频繁的函数。
测试背景:APP 在测试机运行一段时间后出现手机发烫、卡顿、高 CPU 占有率的现象。将应用切入后台进行 CPU 数据的监测,结果显示,即使应用不进行任何操作,应用的 CPU 占有率都会持续的增长。
按照 TraceView 简介中的方法进行测试,TraceView 结果 UI 显示后进行数据分析,在 Profile Panel 中,选择按 Cpu Time/Call 进行降序排序(从上之下排列,每项的耗费时间由高到低)得到如图所示结果:
图中 ImageLoaderTools$2.run() 是应用程序中的函数,它耗时为 。然后点击 ImageLoaderTools$2.run() 项,得到更为详尽的调用关系图:
上图中 Parents 为&ImageLoaderTools$2.run() 方法的调用者:Parents (the methods calling this method);Children 为&ImageLoaderTools$2.run() 调用的子函数或方法:Children (the methods called by this method)。本例中 ImageLoaderTools$2.run() 方法的调用者为 Framework 部分,而& ImageLoaderTools$2.run() 方法调用的自方法中我们却发现有三个方法的 Incl Cpu Time % 占用均达到了 14% 以上,更离谱的是 Calls+RecurCalls/Total 显示这三个方法均被调用了 35000 次以上,从包名可以识别出这些方法为测试者自身所实现,由此可以判断 ImageLoaderTools$2.run() 极有可能是手机发烫、卡顿、高 CPU 占用率的原因所在。
大致可以判断是 ImageLoaderTools$2.run() 方法出现了问题,下面找到这个方法进行代码上的验证:
1 package com.sunzn.app.
3 import java.io.F
4 import java.io.IOE
5 import java.io.InputS
6 import java.lang.ref.SoftR
7 import java.util.ArrayL
8 import java.util.HashM
10 import android.content.C
11 import android.graphics.B
12 import android.os.E
13 import android.os.H
14 import android.os.M
16 public class ImageLoaderTools {
private HttpT
private Context mC
private boolean isLoop = true;
private HashMap&String, SoftReference&Bitmap&& mHashMap_
private ArrayList&ImageLoadTask& maArrayList_taskQ
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
ImageLoadTask loadTask = (ImageLoadTask) msg.
loadTask.callback.imageloaded(loadTask.path, loadTask.bitmap);
private Thread mThread = new Thread() {
public void run() {
while (isLoop) {
while (maArrayList_taskQueue.size() & 0) {
ImageLoadTask task = maArrayList_taskQueue.remove(0);
if (Constant.LOADPICTYPE == 1) {
byte[] bytes = httptool.getByte(task.path, null, HttpTools.METHOD_GET);
task.bitmap = BitMapTools.getBitmap(bytes, 40, 40);
} else if (Constant.LOADPICTYPE == 2) {
InputStream in = httptool.getStream(task.path, null, HttpTools.METHOD_GET);
task.bitmap = BitMapTools.getBitmap(in, 1);
if (task.bitmap != null) {
mHashMap_caches.put(task.path, new SoftReference&Bitmap&(task.bitmap));
File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (!dir.exists()) {
dir.mkdirs();
String[] path = task.path.split("/");
String filename = path[path.length - 1];
File file = new File(dir, filename);
BitMapTools.saveBitmap(file.getAbsolutePath(), task.bitmap);
Message msg = Message.obtain();
mHandler.sendMessage(msg);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
synchronized (this) {
} catch (InterruptedException e) {
e.printStackTrace();
public ImageLoaderTools(Context context) {
this.mContext =
httptool = new HttpTools(context);
mHashMap_caches = new HashMap&String, SoftReference&Bitmap&&();
maArrayList_taskQueue = new ArrayList&ImageLoaderTools.ImageLoadTask&();
mThread.start();
private class ImageLoadTask {
public interface Callback {
void imageloaded(String path, Bitmap bitmap);
public void quit() {
isLoop = false;
public Bitmap imageLoad(String path, Callback callback) {
Bitmap bitmap = null;
String[] path1 = path.split("/");
String filename = path1[path1.length - 1];
if (mHashMap_caches.containsKey(path)) {
bitmap = mHashMap_caches.get(path).get();
if (bitmap == null) {
mHashMap_caches.remove(path);
File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File file = new File(dir, filename);
bitmap = BitMapTools.getBitMap(file.getAbsolutePath());
if (bitmap != null) {
ImageLoadTask task = new ImageLoadTask();
task.path =
task.callback =
maArrayList_taskQueue.add(task);
synchronized (mThread) {
mThread.notify();
return null;
以上代码即是 ImageLoaderTools 图片工具类的全部代码,先不着急去研究这个类的代码实现过程,先来看看这个类是怎么被调用的:
1 ImageLoaderTools imageLoaderTools = imageLoaderTools = new ImageLoaderTools(this);
3 Bitmap bitmap = imageLoaderTools.imageLoad(picpath, new Callback() {
public void imageloaded(String picPath, Bitmap bitmap) {
if (bitmap == null) {
imageView.setImageResource(R.drawable.default);
imageView.setImageBitmap(bitmap);
15 if (bitmap == null) {
imageView.setImageResource(R.drawable.fengmianmoren);
17 } else {
imageView.setImageBitmap(bitmap);
ImageLoaderTools 被调用的过程非常简单:1.ImageLoaderTools 实例化;2.执行 imageLoad() 方法加载图片。
在 ImageLoaderTools 类的构造函数(90行-96行)进行实例化过程中完成了网络工具&HttpTools 初始化、新建一个图片缓存 Map、新建一个下载队列、开启下载线程的操作。这时候请注意开启线程的操作,开启线程后执行 run() 方法(35行-88行),这时 isLoop 的值是默认的 true,maArrayList_taskQueue.size() 是为 0 的,在任务队列 maArrayList_taskQueue 中还没有加入下载任务之前这个循环会一直循环下去。在执行 imageLoad() 方法加载图片时会首先去缓存 mHashMap_caches 中查找该图片是否已经被下载过,如果已经下载过则直接返回与之对应的 bitmap 资源,如果没有查找到则会往 maArrayList_taskQueue 中添加下载任务并唤醒对应的下载线程,之前开启的线程在发现 maArrayList_taskQueue.size() & 0 后就进入下载逻辑,下载完任务完成后将对应的图片资源加入缓存 mHashMap_caches 并更新 UI,下载线程执行 wait() 方法被挂起。一个图片下载的业务逻辑这样理解起来很顺畅,似乎没有什么问题。开始我也这样认为,但后来在仔细的分析代码的过程中发现如果同样一张图片资源重新被加载就会出现死循环。还记得缓存 mHashMap_caches 么?如果一张图片之前被下载过,那么缓存中就会有这张图片的引用存在。重新去加载这张图片的时候如果重复的去初始化 ImageLoaderTools,线程会被开启,而使用 imageLoad() 方法加载图片时发现缓存中存在这个图片资源,则会将其直接返回,注意这里使用的是
那就意味着&imageLoad() 方法里添加下载任务到下载队列的代码不会被执行到,这时候 run() 方法中的 isLoop = true 并且 maArrayList_taskQueue.size() = 0,这样内层 while 里的逻辑也就是挂起线程的关键代码 wait() 永远不会被执行到,而外层 while 的判断条件一直为 true,就这样程序出现了死循环。死循环才是手机发烫、卡顿、高 CPU 占用率的真正原因所在。
准确的定位到代码问题所在后,提出解决方案就很简单了,这里提供的解决方案是将 wait() 方法从内层 while 循环提到外层 while 循环中,这样重复加载同一张图片时,死循环一出现线程就被挂起,这样就可以避免死循环的出现。代码如下:
1 private Thread mThread = new Thread() {
public void run() {
while (isLoop) {
while (maArrayList_taskQueue.size() & 0) {
ImageLoadTask task = maArrayList_taskQueue.remove(0);
if (Constant.LOADPICTYPE == 1) {
byte[] bytes = httptool.getByte(task.path, null, HttpTools.METHOD_GET);
task.bitmap = BitMapTools.getBitmap(bytes, 40, 40);
} else if (Constant.LOADPICTYPE == 2) {
InputStream in = httptool.getStream(task.path, null, HttpTools.METHOD_GET);
task.bitmap = BitMapTools.getBitmap(in, 1);
if (task.bitmap != null) {
mHashMap_caches.put(task.path, new SoftReference&Bitmap&(task.bitmap));
File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (!dir.exists()) {
dir.mkdirs();
String[] path = task.path.split("/");
String filename = path[path.length - 1];
File file = new File(dir, filename);
BitMapTools.saveBitmap(file.getAbsolutePath(), task.bitmap);
Message msg = Message.obtain();
mHandler.sendMessage(msg);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
synchronized (this) {
} catch (InterruptedException e) {
e.printStackTrace();
最后再附上代码修改后代码运行的性能图,和之前的多次被重复执行,效率有了质的提升,手机发烫、卡顿、高 CPU 占用率的现象也消失了。
阅读(...) 评论()  在Android2.3中增加了对音频混响的支持,这些API包含在android.media.audiofx包中。 
  一、概述
  AudioEffect是android audio framework(android 音频框架)提供的音频效果控制的基类。开发者不能直接使用此类,应该使用它的派生类。 下面列出它的派生类。 
    Equalizer    Virtualizer    BassBoost    PresetReverb    EnvironmentalReverb
  当创建AudioEffect时,如果音频效果应用到一个具体的AudioTrack和MediaPlayer的实例,应用程序必须指定该实例的音频session ID,如果要应用Global音频输出混响的效果必须制定Session 0。
  要创建音频输出混响(音频 Session 0)要求要有 MODIFY_AUDIO_SETTINGS权限。
  如果要创建的效果在audio framework不存在,那么直接创建该效果,如果已经存在那么直接使用此效果。如果优先级高的对象要在低级别的对象使用该效果时,那么控制将转移到优先级高的对象上,否则继续停留在此对象上。在这种情况下,新的申请将被监听器通知。
  二、嵌套类
  1.AudioEffect.Descriptor:效果描述符包含在音频框架内实现某种特定的效果的信息。  2.AudioEffect.OnControlStatusChangeListener:此接口定义了当应用程序的音频效果的控制状态改变时由AudioEffect调用的方法。
  3.AudioEffect.OnEnableStatusChangeListener:此接口定义了当应用程序的音频效果的启用状态改变时由AudioEffect调用的方法。
  三、常量
ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION
关闭音频效果
ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL
启动一个音频效果控制面板UI。
ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION
打开音频效果。
ALREADY_EXISTS
内部操作状态。
CONTENT_TYPE_GAME
当播放内容的类型是游戏音频时EXTRA_CONTENT_TYPE的值。
CONTENT_TYPE_MOVIE
当播放内容的类型是电影时EXTRA_CONTENT_TYPE的值。
CONTENT_TYPE_MUSIC
当播放内容的类型是音乐时EXTRA_CONTENT_TYPE的值。
CONTENT_TYPE_VOICE
当播放内容的类型是话音时EXTRA_CONTENT_TYPE的值。&&&&&&&&&&&&&&&&&&
EFFECT_AUXILIARY
Effect connection mode&是auxiliary
EFFECT_INSERT
Effect connection mode 是insert.
指示操作错误。
ERROR_BAD_VALUE
指示由于错误的参数导致的操作失败。
ERROR_DEAD_OBJECT
指示由于已关闭的远程对象导致的操作失败。
ERROR_INVALID_OPERATION
指示由于错误的请求状态导致的操作失败。
ERROR_NO_INIT
指示由于错误的对象初始化导致的操作失败。
ERROR_NO_MEMORY
指示由于内存不足导致的操作失败。
EXTRA_AUDIO_SESSION
包含使用效果的音频会话ID。
EXTRA_CONTENT_TYPE
指示应用程序播放内容的类型。
EXTRA_PACKAGE_NAME
包含调用应用程序的包名。
操作成功。
  四、公有方法
AudioEffect.Descriptor
getDescriptor()
获取效果描述符。
getEnabled()
返回效果的启用状态。
返回效果的标识符
hasControl()
检查该AudioEffect 对象是否拥有效果引擎的控制。如果有,则返回true。
static&Descriptor[]
queryEffects()
查询平台上的所有有效的音频效果。
释放本地AudioEffect资源。
setControlStatusListener(AudioEffect.OnControlStatusChangeListener&listener)
注册音频效果的控制状态监听器.当控制状态改变时AudioEffect发出通知。
setEnableStatusListener(AudioEffect.OnEnableStatusChangeListener&listener)
设置音频效果的启用状态监听器。当启用状态改变时AudioEffect发出通知。
setEnabled(boolean enabled)
Enable or disable the effect.
  五、应用(此应用来自于SDK包)
  1.新建项目
  你或许已经发现在2.3的项目中比2.2多一个配置文件proguard.cfg,包含混淆所需的proguard脚本。
  2.打开AndroidManifest.xml文件
  添加权限&android.permission.RECORD_AUDIO&。
  3.可视化工具类VisualizerView.java
&&        效果图  
  4.Main.java
package com.wjq.import android.app.Aimport android.media.AudioMimport android.media.MediaPimport android.media.audiofx.Eimport android.media.audiofx.Vimport android.os.Bimport android.util.Limport android.view.Gimport android.view.ViewGimport android.widget.LinearLimport android.widget.SeekBimport android.widget.TextVpublic class Main extends Activity {
private static final String TAG = "AudioFxDemo";
private static final float VISUALIZER_HEIGHT_DIP = 50f;
private MediaPlayer mMediaP
private Visualizer mV
private Equalizer mE
private LinearLayout mLinearL
private VisualizerView mVisualizerV
private TextView mStatusTextV
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
mStatusTextView = new TextView(this);
mLinearLayout = new LinearLayout(this);
mLinearLayout.setOrientation(LinearLayout.VERTICAL);
mLinearLayout.addView(mStatusTextView);
setContentView(mLinearLayout);
// Create the MediaPlayer
mMediaPlayer = MediaPlayer.create(this, R.raw.test_cbr);
Log.d(TAG, "MediaPlayer audio session ID: " + mMediaPlayer.getAudioSessionId());
setupVisualizerFxAndUI();
setupEqualizerFxAndUI();
// Make sure the visualizer is enabled only when you actually want to receive data, and
// when it makes sense to receive data.
mVisualizer.setEnabled(true);
// When the stream ends, we don't need to collect any more data. We don't do this in
// setupVisualizerFxAndUI because we likely want to have more, non-Visualizer related code
// in this callback.
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mediaPlayer) {
mVisualizer.setEnabled(false);
mMediaPlayer.start();
mStatusTextView.setText("Playing audio...");}private void setupEqualizerFxAndUI() {
// Create the Equalizer object (an AudioEffect subclass) and attach it to our media player,
// with a default priority (0).
mEqualizer = new Equalizer(0, mMediaPlayer.getAudioSessionId());
mEqualizer.setEnabled(true);
TextView eqTextView = new TextView(this);
eqTextView.setText("Equalizer:");
mLinearLayout.addView(eqTextView);
short bands = mEqualizer.getNumberOfBands();
final short minEQLevel = mEqualizer.getBandLevelRange()[0];
final short maxEQLevel = mEqualizer.getBandLevelRange()[1];
for (short i = 0; i & i++) {
final short band =
TextView freqTextView = new TextView(this);
freqTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
freqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
freqTextView.setText((mEqualizer.getCenterFreq(band) / 1000) + " Hz");
mLinearLayout.addView(freqTextView);
LinearLayout row = new LinearLayout(this);
row.setOrientation(LinearLayout.HORIZONTAL);
TextView minDbTextView = new TextView(this);
minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
minDbTextView.setText((minEQLevel / 100) + " dB");
TextView maxDbTextView = new TextView(this);
maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
maxDbTextView.setText((maxEQLevel / 100) + " dB");
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.weight = 1;
SeekBar bar = new SeekBar(this);
bar.setLayoutParams(layoutParams);
bar.setMax(maxEQLevel - minEQLevel);
bar.setProgress(mEqualizer.getBandLevel(band));
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
mEqualizer.setBandLevel(band, (short) (progress + minEQLevel));
public void onStartTrackingTouch(SeekBar seekBar) {}
public void onStopTrackingTouch(SeekBar seekBar) {}
row.addView(minDbTextView);
row.addView(bar);
row.addView(maxDbTextView);
mLinearLayout.addView(row);
}}private void setupVisualizerFxAndUI() {
// Create a VisualizerView (defined below), which will render the simplified audio
// wave form to a Canvas.
mVisualizerView = new VisualizerView(this);
mVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
(int)(VISUALIZER_HEIGHT_DIP * getResources().getDisplayMetrics().density)));
mLinearLayout.addView(mVisualizerView);
// Create the Visualizer object and attach it to our media player.
mVisualizer = new Visualizer(mMediaPlayer.getAudioSessionId());
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes,
int samplingRate) {
mVisualizerView.updateVisualizer(bytes);
public void onFftDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {}
}, Visualizer.getMaxCaptureRate() / 2, true, false);}@Overrideprotected void onPause() {
super.onPause();
if (isFinishing() && mMediaPlayer != null) {
mVisualizer.release();
mEqualizer.release();
mMediaPlayer.release();
mMediaPlayer = null;
VisualizerView
1 package com.wjq. 2
3 import android.content.C 4 import android.graphics.C 5 import android.graphics.C 6 import android.graphics.P 7 import android.graphics.R 8 import android.view.V 9 10 public class VisualizerView extends View {11 12
private byte[] mB13
private float[] mP14
private Rect mRect = new Rect();15 16
private Paint mForePaint = new Paint();17 18
public VisualizerView(Context context) {19
super(context);20
private void init() {24
mBytes = null;25 26
mForePaint.setStrokeWidth(1f);27
mForePaint.setAntiAlias(true);28
mForePaint.setColor(Color.rgb(0, 128, 255));29
public void updateVisualizer(byte[] bytes) {32
mBytes =33
invalidate();34
@Override37
protected void onDraw(Canvas canvas) {38
super.onDraw(canvas);39 40
if (mBytes == null) {41
if (mPoints == null || mPoints.length & mBytes.length * 4) {45
mPoints = new float[mBytes.length * 4];46
mRect.set(0, 0, getWidth(), getHeight());49 50
for (int i = 0; i & mBytes.length - 1; i++) {51
mPoints[i * 4] = mRect.width() * i / (mBytes.length - 1);52
mPoints[i * 4 + 1] = mRect.height() / 253
+ ((byte) (mBytes[i] + 128)) * (mRect.height() / 2) / 128;54
mPoints[i * 4 + 2] = mRect.width() * (i + 1) / (mBytes.length - 1);55
mPoints[i * 4 + 3] = mRect.height() / 256
+ ((byte) (mBytes[i + 1] + 128)) * (mRect.height() / 2) / 128;57
canvas.drawLines(mPoints, mForePaint);60
阅读(...) 评论()安卓软件有没有可能已经安装的软件,移植到另一个手机上,但一摸一样,例如手机令牌
安卓软件有没有可能已经安装的软件,移植到另一个手机上,但一摸一样,例如手机令牌 25
不区分大小写匿名
qq同步助手。
现在一台手机上备份 已经安装的应用。 然后在另一台上QQ同步助手。同步
就可以实现 软件同步。
用钛备份试试!
相关知识等待您来回答
手机领域专家
当前分类官方群讨论、解答、交流电脑数码相关的疑难问题

我要回帖

更多关于 android fragment实例 的文章

 

随机推荐