Android学习一些小技巧及踩过的坑:
1、jsoup包没有,显示红色错误:
在网上下载jsoup包,放到工程目录libs下面即可
2、打开网址提示 net::ERR_CACHE_MISS 错误(没有网络访问权限):
在AndroidManifest.xml中添加 解决
3、xml文件注释:
可以ctrl+shift+/ 快速生成
4、activity设置背景颜色:
在AndroidManifest.xml中添加属性
android:name=".MainActivity"
android:theme="@android:style/Theme.Black" //添加该属性
android:label="@string/app_name" >
5、设置apk图标:
替换res中的每个分辨率的文件夹下的icon文件,再修改AndroidManifest.xml文件中的
android:icon="@drawable/ic_launcher"
6、获取时间差:
long start = System.currentTimeMillis();
Document doc = Jsoup.connect(url).get();
long end = System.currentTimeMillis();
time = end - start;
7、输出log:
import android.util.Log;
protected static final String TAG="MyAndroid";//定义TAG
Log.v(TAG,"logcat message")
8、android sdk源码:
在csdn下载20M左右压缩源码,加压放到sdk目录的source下,
在eclipse中添加路径(按住ctrl键,会提示选择;手动右击工程下的android.jar,点击属性,添加Java
Source Attachment)
亲测可用。
9、有时显示appcompat_v7中报错,编译不了,可以忽略错误,继续编译:(蛮有用的)
Window--Preferences--Android--Lint Error Checking--Ingore All 忽略所有错误。
10、创建子线程:
// Thread是一个类,必须继承
public class myThread extends Thread {
@Override
public void run() {
super.run();
// 写子线程中的操作
}
}
11、添加apk的系统权限,在AndroidManifest.xml中加:
android:sharedUserId="android.uid.system"
再编译出的apk中加签名(右击工程--Export...---Export Android Application ...选择签名文件)
12、获取屏幕点击坐标位置:
getevent
查看 0035 0036(x y)
/dev/input/event4: 0003 0035 000001fd
/dev/input/event4: 0003 0036 0000015b
(509,347)
13、模拟长按事件:
Instrumentation mInst = new Instrumentation();
mInst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 140, 410, 0));//140 410是坐标 x y 按下
Thread.currentThread().sleep(1000);//等待1s
mInst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(), //松开
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 140, 410, 0));
14、android中的onCreateOptionsMenu()用来创建菜单:
15、android图标:
图标:把所有图片都放在drawable-hdpi 文件夹就可以
图标就是在AndroidManifest.xml 中通过android:icon="@drawable/ic_launcher"来指定的
不同的分辨率的图标需要放在不同的文件夹下(x,xx,xxx),设备会自动选择适合自己的分辨率的图标
16、android logcat打印:
android.util.Log
Log.v //verbose 级别最低的
17、id使用:
如果你需要在XML 中引用一个id,就使用@id/id_name 这种语法,而如果你需要在XML 中定义一个id,则要使用@+id/id_name 这种语法
18、activity的名称:
在AndroidManifest.xml给主活动指定的label 不仅会成为标题栏中的内容,还会成为启动器(Launcher)中应用程序显示的名称。
android:label="@string/app_name"
19、toast显示:
import android.widget.Toast;
Toast.makeText(context, text, duration) //使用方法原型 LENGTH_SHORT LENGTH_LONG
Toast.makeText(MainActivity.this, "this is a toast!", 3).show();//show 3s
//toast显示在屏幕中间
Toast t = Toast.makeText(mContext, "再按一次退出", Toast.LENGTH_LONG);//3.5s ,短的2s
t.setGravity(Gravity.CENTER, 0, 0);
注意:在button.setOnClickListener中使用必须 MainActivity.this ;在onCreate中可以直接使用this
因为活动本身就是一个Context对象,如果在Listener中使用的话,传入的参数就是“new View.OnClickListener(){}”,会出现错误。
toast只能显示3s或5s,其他的需要自己定义
20、销毁一个activity:
使用finish()
21、Intent使用:
Intent 的用法大致可以分为两种,显式Intent 和隐式Intent
显式Intent:
显示Intent启动activity
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);//创建一个Intent
startActivity(intent);//接受一个Intent的参数
隐式Intent:
相比于显式Intent,隐式Intent 则含蓄了许多,它并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action 和category 等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动.
添加:
隐式Intent启动(action和category同时匹配才可以启动activity)
Intent intent = new Intent("com.example.activitytest.ACTION_START");//将action传入,因为category设置的DEFAULT,所以不用传也可以
startActivity(intent);//接受一个Intent的参数
每个Intent 中只能指定一个action,但却能指定多个category
使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动。(启动浏览器打开网页)
//Intent.ACTION_VIEW 定义的是“android.intent.action.VIEW” action,VIEW,这是一个Android 系统内置的动作
Intent intent = new Intent("android.intent.action.VIEW");
//调用Intent 的setData()方法将这个Uri 对象传递进去
intent.setData(Uri.parse("http://www.baidu.com"));
22、Intent向下一个活动传递数据:
String data = " Hello, SecondActivity"
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);//显示Intent
intent.putExtra("extra_data", data);
startActivity(intent);
//取值
Intent intent = getIntent();//通过getIntent() 方法获取到用于启动SecondActivity 的Intent
String data = intent.getStringExtra("extra_data");
--------------------
使用intent Bundle传递一个字符串
Intent intent = new Intent(Moving_Ping_Activity.this, Distribute_Test_HistoryRecord.class);
Bundle bundle = new Bundle();
bundle.putStringArrayList("data", "abc");
intent.putExtras(bundle);
startActivity(intent);
取字符串
Intent intent = this.getIntent();
testResultList = intent.getStringArrayListExtra("data");
-------------------
使用intent Bundle传递一个对象,对象需要实现Serializable,可序列化
Intent intentCheckResult = new Intent(Moving_Ping_Activity.this, Distribute_Test_Check_Result.class);
Bundle bundleCheckResult = new Bundle();
bundleCheckResult.putSerializable("data", roamTestTotal_Toolbox_Current);
intentCheckResult.putExtras(bundleCheckResult);
startActivity(intentCheckResult);
取对象
Intent intent = this.getIntent();
roamTestTotal_Toolbox = (RoamTestTotal_Toolbox) intent.getSerializableExtra("data");
23、活动的启动模式一共有四种:
分别是standard、singleTop、singleTask 和singleInstance
修改启动模式:在AndroidManifest.xml的activity标签中添加
android:launchMode="singleTop"
standard 模式的活动:系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop 模式的活动:每当想要再启动一个FirstActivity 时都会直接使用栈顶的活动;不过当FirstActivity并未处于栈顶位置时,这时再启动FirstActivity,还是会创建新的实例的
singleTask 模式的活动:每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
24、遇到将一个后台的activity调到前台的问题:
在当前的activity中使用调用action,可以调到前台但是会提示打开方式
Intent intent1 = new Intent(“android.intent.action.MAIN”);
startActivity(intent1);
解决的方案:在应用中创建一个finish的activity,finish的activy功能就是回到主页
调到finish activity:
Intent intent1 = new Intent(MainActivity.this,finish.class);// finished 显示调用activity
startActivity(intent1);// back to home
在finish的activity中回到主页:
Intent intent = new Intent();
ComponentName comp = new ComponentName("com.example.runcmd","com.example.runcmd.MainActivity");//显示调用
intent.setComponent(comp);
startActivity(intent);
25、UI控件
TextView:文本控件
控件的高度和宽度,Android 中所有的控件都具有这两个属性,可选值有三种match_parent、fill_parent 和wrap_content,
android:match_parent 表示让当前控件的大小和父布局的大小一样。
android:wrap_content 表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小。
android:gravity="center" //指定文字对齐方式:,可选值有top、bottom、left、right、center等, 可以用“ | ” 来同时指定多个值
android:textSize="24sp" //文字大小
android:textColor="#00ff00" //文字的颜色 RGB
Button:按钮
和TextView类似,主要设置事件监听(setOnClickListener)
EditText:可编辑文本框
android:hint="Type something here" //编辑框中的提示语
不过随着输入的内容不断增多,EditText 会被不断地拉长。这时由于EditText 的高度指定的是wrap_content,因此它总能包含住里面的内容,但是当输入的内容过多时,界面就会变得非常难看。
可以使用android:maxLines 属性来解决这个问题。
android:maxLines="2" //指定了EditText 的最大行数为两行,超过2行就会自动向上滚动
android:inputType="phone" //值允许输入数字
默认是数字键盘,但是也可以输入字母:
et_dialog_confirm_input_address.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
String digists = "0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.:/";
et_dialog_confirm_input_address.setKeyListener(DigitsKeyListener.getInstance(digists));
有个问题:界面有个edittext控件的时候,默认进入就会弹出软键盘。
解决的方法是:在界面上加一个view,强制获取焦点就可以了
例如:
ImageView:图片控件
imageView.setImageResource(R.drawable.jelly_bean); //通过set方法可以设置不同图片控件
ProgressBar
所有的Android 控件都具有这个属性,可以通过 进行指定,可选值有三种,visible、invisible 和gone
我们还可以通过代码来设置控件的可见性,使用的是setVisibility()方法,传入View.VISIBLE、View.INVISIBLE 和View.GONE
常用的属性:
线性布局
android:layout_alignParentLeft="true" //在父类布局的左边
android:layout_alignParentRight="true" //在父类布局的右边
android:layout_centerInParent="true" //在父类布局的中间
android:layout_alignParentBottom="true" //在父类布局的下边
android:layout_alignParentTop="true" //在父类布局的上边
android:layout_above="@id/button3" //在button3控件的上方
android:layout_toLeftOf="@id/button3" //左边缘和button3对齐
FrameLayout:比较简单,这种布局没有任何的定位方式,所有的控件都会摆放在布局的左上角
TableLayout
26、获取WiFi 连接IP地址和网关地址
WifiManager wm = (WifiManager)getSystemService(WIFI_SERVICE);
DhcpInfo di = wm.getDhcpInfo();
String gateway_ip = Formatter.formatIpAddress(di.gateway);
String devices_ip= Formatter.formatIpAddress(di.ipAddress);
27、获取WiFi名称和信号强度
需要添加权限
WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
Log.d("Hello:wifiInfo", wifiInfo.toString());
Log.d("Hello:SSID",wifiInfo.getSSID());
28、android 多线程
线程的基本使用方法,3种写法:
a、
class MyThread extends Thread {
@Override
public void run() {
// 处理具体的逻辑
}
}
new MyThread().start();//启动线程
b、使用继承的方式耦合性有点高,更多的时候我们都会选择使用实现Runnable 接口的方式来定义一个线程
class MyThread implements Runnable {
@Override
public void run() {
// 处理具体的逻辑
}
}
MyThread myThread = new MyThread();
new Thread(myThread).start(); //启动线程,传入一个实现Runnable的对象参数
c、如果你不想专门再定义一个类去实现Runnable 接口,也可以使用匿名类的方式,这种写法更为常见
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑 ,直接更新UI会出现程序crash的问题
}
}).start();
Android 的UI 也是线程不安全的。也就是说,如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。
Android 提供了一套异步消息处理机制,完美地解决了在子线程中进行UI 操作的问题。
下面是段实例代码,测试可以正常运行:(运行的效果是点击按钮先弹3s toast,第5s在更新UI中的text文本)
public class MainActivity extends Activity{
protected static final String TAG = "Hello:"; //定义一个更新处理的标志
public static final int UPDATE_TEXT = 1;
private TextView text; //UI的元素
private Button button;
private Handler handler = new Handler() { //定义一个handler
public void handleMessage(Message msg) {
switch (msg.what) { //判断消息的类型(1,2,3,4)
case UPDATE_TEXT:
Bundle bundle1 = msg.getData(); //定义一个bundle变量接收message中的bundle数据
String arr1[] = bundle1.getStringArray("arr_return"); //接收bundle中的数组
// 在这里可以进行UI操作
text.setText("onClicked");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text= (TextView) findViewById(R.id.textView1);
button= (Button) findViewById(R.id.button1);
button.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "this is a toast!", 3).show();//show 3s
//新建一个子线程
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
Message message = new Message();
Bundle bundle = new Bundle(); //创建个bundle
bundle.putStringArray("arr_return", arr_return); //将一个arr_return数组放到bundle中
message.what = UPDATE_TEXT;
message.setData(bundle); //将bundle放到message中发送
handler.sendMessage(message); // 将Message对象发送出去
} catch (Exception e) {
Log.v(TAG, e.toString());
}
}
}).start();
//结束子线程
}
});//end listener
}
29、计算wifi流量
DecimalFormat fnum = new DecimalFormat("##0.0");//设置格式
long lastTimeStamp = System.currentTimeMillis(); //获取系统时间
long last_total_rx=TrafficStats.getTotalRxBytes();//获取总的接收数据
Thread.currentThread().sleep(3000);//设置3s刷新一次
long curTimeStamp = System.currentTimeMillis();
long cur_total_rx=TrafficStats.getTotalRxBytes();
long delta_total_rx=cur_total_rx-last_total_rx;
float speed = ((float)delta_total_rx * 1000 /(float) (curTimeStamp - lastTimeStamp));// 毫秒转换
//换算
if (speed > (1024 * 1024)) {
speedStr = fnum.format((speed / ((float) (1024 * 1024))))
+ "M/s";
} else if (speed > 1024) {
speedStr = fnum.format((float) speed / ((float) 1024)) + "K/s";
} else {
speedStr = String.valueOf((int)speed) + "B/s";
}
30、自定义toast
a、先定义一个myToast 的class,在单独的文件中
public class toast {
private Toast mToast;
private toast(Context context, CharSequence text, int duration) {
View v = LayoutInflater.from(context).inflate(R.layout.toast, null);
TextView textView = (TextView) v.findViewById(R.id.textView1);
textView.setText(text);
mToast = new Toast(context);
mToast.setDuration(duration);
mToast.setView(v);
}
public static toast makeText(Context context, CharSequence text, int duration) { //设置静态方法,直接通过类调用
return new toast(context, text, duration);//duration 设置0 1 (短2s 长3.5s)只能设置2个值
}
public void show() {
if (mToast != null) {
mToast.show();
}
}
public void setGravity(int gravity, int xOffset, int yOffset) {
if (mToast != null) {
mToast.setGravity(gravity, xOffset, yOffset);
}
}
}
b、在Activity中调用
toast.makeText(MainActivity.this,"text",1).show();
31、设置选项卡代码:
tabHost=(TabHost)findViewById(android.R.id.tabhost);//获取tabHost对象
tabHost.setup();//初始化TabHost组件
LayoutInflater inflater=LayoutInflater.from(this);//声明并实例化一个LayoutInflater对象
//关于LayoutInflater详细,请看我的另外一篇转载的总结
inflater.inflate(R.layout.tab1, tabHost.getTabContentView());
inflater.inflate(R.layout.tab2, tabHost.getTabContentView());
inflater.inflate(R.layout.tab3, tabHost.getTabContentView());
tabHost.addTab(tabHost.newTabSpec("tab01")
.setIndicator("标签页一")
.setContent(R.id.linearLayout1));//添加第一个标签页
tabHost.addTab(tabHost.newTabSpec("tab02")
.setIndicator("标签页二")
.setContent(R.id.linearLayout2));//添加第二个标签页
tabHost.addTab(tabHost.newTabSpec("tab03")
.setIndicator("标签页三")
.setContent(R.id.linearLayout3));//添加第三个标签页
View view1 = tabHost.getTabWidget().getChildAt(0);
if (tabHost.getCurrentTab() == 0) {
view1.setBackgroundColor(Color.GREEN);
}
32、单位和尺寸
在布局文件中指定宽高的固定大小有以下常用单位可供选择:px、pt、dp 和sp
px pt是固定的像素大小
dp sp是相对的大小,可以适配不同的分辨率的手机
33、使用ListView控件,界面显示类似表格的list(没有网格)
a、首先需要定义一个主页的xml
b、定义一个item的xml(每条记录的格式)
c、主函数的代码
onCreate函数里面的代码
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) this.findViewById(R.id.listView);//获取在主xml文件中定义的listView控件
List> data = new ArrayList>(); //定义一个list,保存需要显示的数据
HashMap item = new HashMap(); //定义一个HashMap,保存每条记录item的数据
item.put("id", 1); //将数据添加到item记录中
item.put("wifi_name", "ACP1");
item.put("wifi_mac", "8c:34:00:8c:34:00");
item.put("wifi_level", "-30");
item.put("wifi_freq", "2414");
item.put("wifi_channel", "1");
data.add(item); //将记录item添加到data中,可以用for添加,这里演示只添加一条记录
SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.item,
new String[]{"wifi_name", "wifi_mac", "wifi_level","wifi_freq","wifi_channel"},
new int[]{R.id.wifi_name, R.id.wifi_mac, R.id.wifi_level,R.id.wifi_freq, R.id.wifi_channel}); //创建一个适配器,将数据绑定到适配器中
listView.setAdapter(adapter); //将listView控件和适配器关联
listView.setOnItemClickListener(new ItemClickListener()); //设置点击事件
} //end oncreate
//获取点击事件
private final class ItemClickListener implements OnItemClickListener{
public void onItemClick(AdapterView> parent, View view, int position, long id) {
ListView listView = (ListView) parent;
HashMap data = (HashMap) listView.getItemAtPosition(position);
String personid = data.get("wifi_name").toString();
Toast.makeText(getApplicationContext(), personid, 1).show();
} //重写onItemClick方法,点击后的处理动作
34、android广播:动态注册和静态注册:
动态注册:在代码中注册广播
a、首先新建一个类,让它继承自BroadcastReceiver,并重写父类的onReceive()方法,方法中写接收到广播后的处理。
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "network changes",Toast.LENGTH_SHORT).show();//只显示一条toast
}
}
b、主函数中的代码:
public class MainActivity extends Activity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); //创建了一个IntentFilter 的实例,并给它添加了一个值为android.net.conn.CONNECTIVITY_CHANGE 的action.
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter); //registerReceiver()方法进行注册,将NetworkChangeReceiver 的实例和IntentFilter 的实例都传了进去.
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeReceiver); //动态注册的广播接收器一定都要取消注册才行
}
...(第一步的class添加在此)
}
c、这里有非常重要的一点需要说明,Android 系统为了保证应用程序的安全性做了规定,如果程序需要访问一些系统的关键性信息,必须在配置文件中声明权限才可以,否则程序将会直接崩溃
在AndroidManifest.xml添加:
35、使用SharedPreferences进行数据存错
写入
SharedPreferences sp=this.getSharedPreferences("settings", MODE_PRIVATE); //this代表当前的activity,settings是存储的文件名称。
Editor et= sp.edit();//获取SharedPreferences.Editor 对象
et.putString("name", "test");//name是字段名称,test存储的数据
et.commit();//提交数据
数据存在 /data/data/包名/shared_prefs /目录下,有个settings.xml文件
读出
String s = sp.getString("name", "default");//读取name字段数据,没有就赋值为default
36、ContentProvider
读取手机联系人
ContentResolver cr = this.getContentResolver();
Uri uri = Uri.parse("content://com.example.app.provider/table1")
cr.insert(url, values);
例如:
Cursor cursor = null;
try {
// 查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null, null, null, null);
while (cursor.moveToNext()) {
// 获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
// 获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
创建自己的内容提供器
1. *:表示匹配任意长度的任意字符
2. #:表示匹配任意长度的数字
所以,一个能够匹配任意表的内容URI 格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配table1 表中任意一行数据的内容URI 格式就可以写成:
content://com.example.app.provider/table1/#
37、使用ScrollView包裹TextView实现滑动
38、保留2位小数点
DecimalFormat fnum = new DecimalFormat("##0.00");
delay_avg=fnum.format(10.23546);
39、android获取某个应用的流量数据
a、使用TrafficStats.getUidRxBytes 方法,底层也是调用 解析文件
cat proc/uid_stat/(uid#)/tcp_rcv (uid可以通过进程查询,先ps查看进程号pid,再进入/proc/pid/,cat里面的status状态就可以查到uid)
cat proc/uid_stat/(uid#)/tcp_snd
b、有的手机没有proc/uid_stat这个文件夹
/proc/net/xt_qtaguid/stats,查询这个文件夹
查到的数据类似下面:(10117就是uid,第6是rx数据,第8列是tx数据)
38 wlan0 0x0 10117 0 252397 347 24379 395 252397 347 0 0 0 0 24379 395 0 0 0 0
39 wlan0 0x0 10117 1 18728554 18911 2774867 16508 18728554 18911 0 0 0 0 2468507 16138 0 0 306360 370
经过分析可以知道这2列的tx+rx的数据就是应用的接收和发送的Bytes数据(手动相加和进入设置流量管理查看 结果一模一样,可见android系统设置也是解析这个文件的)
40、显示和隐藏一个view
ViewGroup vg = (ViewGroup) findViewById(R.id.viewGroup);//找到父容器
Button bt1 = new Button(this);//new一个button
bt1.setWidth(iv.getWidth());//设置宽高
bt1.setHeight(iv.getHeight());
//bt1.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
bt1.setText("显示");
vg.addView(bt1);//添加
bt1.setOnClickListener(new OnClickListener() {//设置监听
@Override
public void onClick(View v) {
v.setVisibility(View.GONE);
iv.setVisibility(View.VISIBLE);
}
});
设置一个图片大小和margin
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(iv_toolbox_network_delay_result_game.getLayoutParams());
DisplayMetrics dm = getResources().getDisplayMetrics();
layoutParams.width = (int)(dm.density*28);//28dp
layoutParams.height = (int)(dm.density*28);
layoutParams.setMargins((int)(dm.density*3), 0, (int)(dm.density*3), 0);
iv_toolbox_network_delay_result_game.setLayoutParams(layoutParams);
创建webView
ViewGroup vg = (ViewGroup) webView.getParent();
int width = webView.getLayoutParams().width;
int height = webView.getLayoutParams().height;
webView = new WebView(cxt);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);//设置宽高
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);//设置对齐方式
webView.setLayoutParams(params);
vg.addView(webView);
41、全局content
42、可编辑的图片显示,如果图片需要覆盖需要使用Ralaytive的布局
iv = (ImageView) findViewById(R.id.iv);
Bitmap bt = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/test.png");
//iv.setImageBitmap(bt);
bt_copy = Bitmap.createBitmap(bt.getWidth(), bt.getHeight(), bt.getConfig());
Paint paint = new Paint();
Canvas canvas = new Canvas(bt_copy);
Matrix mt = new Matrix();
//mt.setRotate(30);
canvas.drawBitmap(bt, mt, paint);
iv.setImageBitmap(bt_copy);
43、mpandroidchart使用:
直接在网上下载lib库文件,(mpchartlib.jar 、 nineoldandroids-2.4.0.jar)
绘制简单的图表
//定义图表变量
private LineDataSet lineDataSet;//测试结果曲线
private LineDataSet lineDataSet_avg;//标记线
private ArrayList dataSets;//将dataSet_moving_ping dataSet_moving_ping_avg添加进来
private LineData lineData;
private LineChart lineChart;// 绘制折线图
private ArrayList xVals = new ArrayList();// 横坐标数据
private ArrayList entries = new ArrayList();// y坐标数据,显示测试值
private ArrayList entries_avg = new ArrayList();//第二条线 ,y坐标数据,显示平均时延
public void showLineChart() {
lineChart.setVisibility(0);//设置图表显示
//entries = new ArrayList();// 显示条目
//xVals = new ArrayList();// 横坐标标
// 添加x轴的数据
// xVals.add("1");
// xVals.add("2");
// xVals.add("3");
// xVals.add("4");
// xVals.add("5");
// xVals.add("6");
// 添加y轴的数据
// entries.add(new Entry((float) 16.9,0));
// entries.add(new Entry((float) 8.19,1));
// entries.add(new Entry((float) 9.19,2));
// entries.add(new Entry((float) 74.6,3));
// entries.add(new Entry((float) 51.6,4));
// entries.add(new Entry((float) 21.0,5));
//添加第二条线的数据
// entries_avg.add(new Entry((float) 20.0,0));
// entries_avg.add(new Entry((float) 20.0,1));
// entries_avg.add(new Entry((float) 20.0,2));
// entries_avg.add(new Entry((float) 20.0,3));
// entries_avg.add(new Entry((float) 20.0,4));
// entries_avg.add(new Entry((float) 20.0,5));
//设置线条格式(测试结果的)
lineDataSet = new LineDataSet(entries, "ping时延动态图");
lineDataSet.setDrawValues(false);//顶部不显示数值
lineDataSet.setDrawCubic(true);//设置是否圆滑
lineDataSet.setDrawFilled(true);//填充数值
lineDataSet.setColor(Color.RED);//color
lineDataSet.setLineWidth((float) 0.5);
//dataSet_moving_ping.setColors(ColorTemplate.COLORFUL_COLORS);
//dataSet_moving_ping.setFillColor(0x00ff00);//设置填充颜色
//设置第二条线条的格式
//System.out.println(lineDataSet.getFillColor());
//setting avg mark line
lineDataSet_avg= new LineDataSet(entries_avg, "时延平均值");
lineDataSet_avg.setDrawValues(false);//no value
lineDataSet_avg.setLineWidth((float) 0.8);
lineDataSet_avg.setDrawCircles(false);//no circles
lineDataSet_avg.setColor(Color.GREEN);//color
//将2条折线数据添加到dataSets集合中
dataSets = new ArrayList();
dataSets.add(lineDataSet);
dataSets.add(lineDataSet_avg);
//将2个折线的数据,加上x坐标生成2条曲线,赋值给lineData
lineData = new LineData(xVals, dataSets);
//将2条曲线显示在图表上
lineChart.setData(lineData);
// 设置Y方向上动画animateY(int time);
lineChart.animateY(2000);//设置动画时间
// 描述
lineChart.setDescription("时延");//图表的描述,默认在右下角显示
YAxis leftAxis = lineChart.getAxisRight();//隐藏右边的y轴坐标
leftAxis.setEnabled(false);
// chart_moving_ping.getXAxis().setPosition(XAxisPosition.BOTTOM);
}
动态更新图表的数据
case SHOW_TEXT_PING:
Bundle bundle = msg.getData();
String arr[] = bundle.getStringArray("str");
//arr[0]:显示console,在小窗口显示的文本
//arr[1]:测试一次的传回来的x轴的值
//arr[2]:测试一次的传回来的y轴的值
text_moving_ping.setText(arr[0]);
// 更新图表数据
if (arr[1] != null && arr[2]!="") {//如果==null 测试结束,不用更新图表
//packages_revieve++;// 增加接收的包数
if (xVals.size() >= 30) {
xVals.remove(0);
entries.remove(0);
entries_avg.remove(0);
// 添加数据
lineData.addXValue(arr[1]);
if(arr[2].equals("time out")){//如果超时,设置y值为 超时时间*1000
arr[2]=TIME_OUT*1000+"";
}else{
recieve_packages_count++;
recieve_packages_total+=Float.valueOf(arr[2]);
DecimalFormat fnum = new DecimalFormat("##0.00");
//接收包时延总值/接收到的包的个数
delay_avg=fnum.format(recieve_packages_total/recieve_packages_count);
}
lineData.addEntry(new Entry(Float.valueOf(arr[2]), Integer.valueOf(arr[1])), 0);//0代表第一条线
lineData.addEntry(new Entry(Float.valueOf(delay_avg), Integer.valueOf(arr[1])), 1);//1代表第二条线
//因为第二条线是一条直线,所以需要把里面的数据都设置当前的数据
for (int i =0 ; i < entries_avg.size();i++){
entries_avg.get(i).setXIndex(i);//设置第i个数据的x轴的值(0,1,2,3,...)
entries_avg.get(i).setVal(Float.valueOf(delay_avg));//都设置为delay_avg
}
} else {
//这里处理当数据量不足30个时,就不删除数据,直接追加显示
try {
if(arr[2].equals("time out")){//如果超时,设置y值为 超时时间*1000
arr[2]=TIME_OUT*1000+"";
}else{
recieve_packages_count++;
recieve_packages_total+=Float.valueOf(arr[2]);
DecimalFormat fnum = new DecimalFormat("##0.00");
delay_avg=fnum.format(recieve_packages_total/recieve_packages_count);
}
lineData.addXValue(arr[1]);
lineData.addEntry(new Entry(Float.valueOf(arr[2]), Integer.valueOf(arr[1])), 0);
lineData.addEntry(new Entry(Float.valueOf(delay_avg), Integer.valueOf(arr[1])), 1);
//因为第二条线是一条直线,所以需要把里面的数据都设置当前的数据
for (int i =0 ; i < entries_avg.size();i++){
entries_avg.get(i).setXIndex(i);
entries_avg.get(i).setVal(Float.valueOf(delay_avg));
}
} catch (Exception e) {
e.printStackTrace();
LogUtils.d(TAG, "add data error!");
}
}
// 重新设置数据的图表索引(0,1,2,3,...)
float ymax = entries.get(0).getVal();// 获取y最大值
int i = 0;
for (Entry en : entries) {
en.setXIndex(i++);
// 获取最大值
try {
if (ymax < en.getVal()) {
ymax = en.getVal();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取y轴对象
YAxis leftAxis = lineChart.getAxisLeft();
// leftAxis.resetAxisMaxValue();//重新设置y轴
leftAxis.setAxisMaxValue(ymax + ymax / 15);
if (ymax>100){
//lineDataSet.setFillColor(0xF2DDDC);
lineDataSet.setFillColor(0xE6B9B8);
}else{
lineDataSet.setFillColor(-7542017);
}
// 刷新图表
try {
lineChart.notifyDataSetChanged();
lineChart.invalidate(); // refresh
} catch (Exception e) {
e.printStackTrace();
LogUtils.e(TAG, "flush chart error!");
}
//有数据就更新
if (packages_send>0){
// 更新界面数据
text_ping_send_count.setText(packages_send + "");
text_ping_receive_count.setText((packages_send-packages_lost) + "");
DecimalFormat fnum = new DecimalFormat("##0.00");
String lostRate = fnum.format(packages_lost * 100.0/ packages_send) + "%";
text_ping_lost_rate.setText(lostRate);
if (lostRate.equals("0.00%")) {
text_ping_lost_rate.setTextColor(Color.GREEN);
} else {
text_ping_lost_rate.setTextColor(Color.RED);
}
text_ping_delay_avg.setText(delay_avg+"ms");
}
}
break;
44、启动app时报了个 “java.lang.SecurityException: Permission Denial: starting Intent”错误
查阅资料发现在xml中调用另外一个activity需要定义一个exported的属性
定义之后解决
当时出现一个问题,用eclipse启动应用直接就进入了主页,没有过开机向导,单独在手机上启动没问题。是因为在run as里面还是用之前的配置,
把启动的launcher action替换为你需要启动的activity,问题解决
45、在合入开机向导的时候出现了一个问题
01-16 10:05:08.927: E/AndroidRuntime(29010): java.lang.IllegalStateException: The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 0, found: 3 Pager id: com.example.runcmd:id/viewpager Pager class: class android.support.v4.view.ViewPager Problematic adapter: class com.example.runcmd.ViewPagerAdapter
经过检查发现在设置 vpAdapter = new ViewPagerAdapter(views); 之前
并没有给views赋上值
46、mpandroidchart 设置坐标轴没有小数点
private final ValueFormatter vf_Int = new ValueFormatter() {
@Override
public String getFormattedValue(float arg0) {
return ""+(int)arg0;
}
};
dataSet_wifi_level.setValueFormatter(vf_Int);
47、android json数据解析
代码演示:
public void click (View v) throws IOException, JSONException{
//System.out.println("/assets/test.json");
String mac = "8c:34:fd";
InputStream is = getAssets().open("test.json");
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
String str = new String();
StringBuilder stringBuilder = new StringBuilder();
try {
while ((str = buf.readLine()) != null) {
//System.out.println(str);
stringBuilder.append(str);
}
} catch (IOException e) {
e.printStackTrace();
}
is.close();
//System.out.println(stringBuilder.toString());
JSONObject jsonObject = new JSONObject(stringBuilder.toString());
JSONArray infArray_Manufacturers = jsonObject.getJSONArray("Manufacturers");
JSONArray infArray_MACs = jsonObject.getJSONArray("MACs");
//获取id
String id = "";
for (int i = 0; i < infArray_MACs.length(); i++) {
//System.out.println(infArray_MACs.getJSONObject(i).getString("Oui"));
if (mac.equals(infArray_MACs.getJSONObject(i).getString("Oui"))){
id = infArray_MACs.getJSONObject(i).getString("ManufactureId");
break;
}
}
//获取路由器型号
String routerName = "";
for (int i =0 ; i < infArray_Manufacturers.length(); i++) {
JSONObject inf_Array = infArray_Manufacturers.getJSONObject(i);
if (id.equals(infArray_Manufacturers.getJSONObject(i).getString("Id"))){
routerName = infArray_Manufacturers.getJSONObject(i).getString("Name");
}
}
System.out.println(routerName);
}
json的数据结构:
{
"Manufacturers": [
{
"Id": "1001",
"Name": "Apple",
"Img": "apple.png"
},
{
"Id": "1002",
"Name": "Xiaomi",
"Img": "xiaomi.png"
},
],
"MACs": [
{
"Oui": "00:00:dd",
"ManufactureId": "1028"
},
{
"Oui": "a0:a8:cd",
"ManufactureId": "1031"
}
]
}
48、checkbox 设置
cb.setClickable(ti.getStatus()); //设置是否可选
cb.setChecked(ti.getStatus()); //设置选中状态
android:layout_alignBaseline="@id/tv_name"//不能设置居中对齐,可以设置layout_alignBaseline, 效果也是居中
49、自定义标题
标题的xml
在main的xml添加
//添加的
50、listview中加载不同的布局:
适配器中的关键代码
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
ti = testItem.get(position);
if(ti.getItemName().equals("基本测试") || ti.getItemName().equals("漫游测试") ||
ti.getItemName().equals("高级测试(需要Root权限)")){
return TYPE_TITLE;
}else{
return TYPE_ITEM;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ti = testItem.get(position);
int type = getItemViewType(position);
//System.out.println(type);
ViewHolder1 myHolder1 = null;
ViewHolder2 myHolder2 = null;
if (convertView == null){
switch (type) {
case TYPE_TITLE:
convertView = inflater.inflate(R.layout.item_layout_title, parent, false);
myHolder1 = new ViewHolder1();
myHolder1.tv_title = (TextView) convertView.findViewById(R.id.tv_name);
myHolder1.tv_title.setText(ti.getItemName());
convertView.setTag(myHolder1);
break;
case TYPE_ITEM:
convertView = inflater.inflate(R.layout.item_layout, parent, false);
myHolder2 = new ViewHolder2();
myHolder2.tv_item = (TextView) convertView.findViewById(R.id.tv_name);
myHolder2.tv_item.setText(ti.getItemName());
CheckBox cb = (CheckBox) convertView.findViewById(R.id.cb);
cb.setChecked(ti.getStatus());
cb.setClickable(ti.getEnable());
convertView.setTag(myHolder2);
break;
}
}else{
switch (type) {
case TYPE_TITLE:
myHolder1 = (ViewHolder1) convertView.getTag();
myHolder1.tv_title.setText(ti.getItemName());
break;
case TYPE_ITEM:
myHolder2 = (ViewHolder2) convertView.getTag();
myHolder2.tv_item.setText(ti.getItemName());
CheckBox cb = (CheckBox) convertView.findViewById(R.id.cb);
cb.setClickable(ti.getStatus());
cb.setChecked(ti.getStatus());
break;
default :
break;
}
}
return convertView;
}
51、沉浸式状态栏设置(需要android4.4以上才支持),非常简单
xml定义
//这2行
代码设置:
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//透明状态栏
}
52、主界面实现滑动切换效果
使用的是tabHost+ViewPager
xml文件:
53、android字体设置
Roboto thin:
android:fontFamily="sans-serif-thin"
Roboto:
android:fontFamily="sans-serif"
android:dashWidth 表示'-'这样一个横线的宽度
android:dashGap 表示之间隔开的距离
54、xml里面设置虚线矩形
//边框虚线的间隔
55、给布局设置侦听
例如给LinearLayout 整个布局设置一个侦听,布局上面还有其他的控件,此时点击其他的控件就不会响应侦听的事件
因为控件消耗了事件,所以没有反应
例如布局中有个按钮button , 我们只有把按钮的 属性设置下 android:clickable="false" 就行了。
56、关于在自定义listView中(有button checkbox 等)不响应setOnItemClickListener 侦听的办法
在自定义的layout的xml文件最外层添加 android:descendantFocusability="blocksDescendants"
例如:
57、ImageView添加切图,layout根据切图缩放
android:scaleType="centerInside"
要注意一点,Drawable文件夹里面的图片命名是不能大写的
58、解决自定义的checkbox,背景图片太大的问题
可以设置checkbox的固定宽高
android:button="@null"
android:background="@drawable/test_option_checkbox"
主要是设置android:button="@null"
59、数据库一定不要使用下划线做数据库名称,不然会报错
60、static变量设置的重要性
每次在子线程中启动,设置一个漫游切换体验的平均时延就会有问题,主线程和子线程打印的数据不一致,定义为static解决问题。
61、dialog中获取屏幕的宽高
DisplayMetrics dm = context.getResources().getDisplayMetrics();
int width = dm.widthPixels;
int height = dm.heightPixels;
dialog.getWindow().setLayout(width - width/20, height/3);//设置dialog的宽高
dialog.getWindow().setDimAmount(0f);//背景不透明
// 正常获取 Android获得屏幕的宽和高
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
int screenWidth = display.getWidth();
int screenHeight = display.getHeight();
//设置dialog的位置
Window dialogWindow = dialog.getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
dialogWindow.setGravity(Gravity.LEFT | Gravity.TOP);
lp.x = location[0];
lp.y = location[1];
dialogWindow.setAttributes(lp);
62、layout显示为虚线,界面不显示虚线的问题
注意一定要加 android:layerType="software" 这个属性
63、android弹框动画效果
在style.xml中添加
在res文件夹下创建一个anim文件夹,里面放2个动画的xml
animations_dialog_enter.xml
nimations_dialog_enter.xml
animations_dialog_exit.xml
代码:
Window dialogWindow = dialog.getWindow();
dialogWindow.setGravity(Gravity.BOTTOM);
dialogWindow.setWindowAnimations(R.style.animations_dialog_style);
64、textView中的行间距
android:lineSpacingExtra="6dp"
65、获取控件在屏幕中的位置
int[] location = new int[2];
button_test_start.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
获取控件的宽高
ll_toolbox_distribute_avg_network_speed.getHeight();
ll_toolbox_distribute_avg_network_speed.getWidth();
计算系统状态栏高度
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
int top = rect.top;
66、获取用户的pid和uid
//pid
int pid = android.os.Process.myPid();
int uid = android.os.Process.myUid();
//uid
private int getUid(){
PackageManager packageManager=getApplicationContext().getPackageManager();
List appList=packageManager.getInstalledApplications(0);
int uid = 0;
for (ApplicationInfo app : appList) {
if (app.packageName.equals(getApplicationContext().getPackageName())){
uid = app.uid;
}
}
System.out.println("uid:"+uid);
return uid;
}
67、去调用一个未启动的activity里面的一个public static变量,导致里面的handle接收到消息界面不刷新
背景:想通过一个activity的子线程获取排名,获取到后发送消息给即将跳转的activity显示
之前的做法是在目标activity里面定义一个public static的标志位,当前的activity循环判断,如果页面启动就发送消息,发现目标activity接收到消息一直不刷新界面
hanlde失效了,原因可能是当前去调用目标activity的时候就已经把那个类初始化了。
解决的方法:
在本类中定义一个标识位,目标activity启动后就改变当前activity的标识位就行,当前activit的子线程就一直读取本类的标识位就行。
68、使用onActivityResult接收到的Intent永远是null ,返回码是0的问题
是因为在onDestory中调用
应该重写onBackPressed方法,
@Override
public void onBackPressed() {
setResultAndFinish();
super.onBackPressed();//注意这个必须在后面调用,否则还是会出现返回值为null的问题
}
private void setResultAndFinish() {
Intent mIntent = new Intent();
mIntent.putExtra("data", testRecordTotal);
setResult(resultCode, mIntent);
finish();
}
69、动画匀速转动的代码:
//播放动画
RotateAnimation animation;
animation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(1000);
animation.setRepeatCount(Integer.MAX_VALUE);
animation.setRepeatMode(Animation.RESTART);
animation.setFillAfter(true);
LinearInterpolator lip = new LinearInterpolator();
animation.setInterpolator(lip);//关键设置这个属性
iv_dialog_count_down_second.startAnimation(animation);
70、http请求代码:
new Thread() {
public void run() {
DefaultHttpClient client = new DefaultHttpClient();
try {
String uriAPI;// 声明网址字符串
if(CONTANT.VERSION.equals("USER")){
uriAPI = CONTANT.USER.INSERT_TEST_RECORD;
}else{
uriAPI = CONTANT.DEBUG.INSERT_TEST_RECORD;
}
HttpPost httpRequest = new HttpPost(uriAPI); // 建立HTTP
// POST联机
List params = new ArrayList(); // Post运作传送变量必须用NameValuePair[]数组储存
params.add(new BasicNameValuePair("testRecord",JsonUtils.toJson(changeToServerFormat())));
System.out.println("testRecord" + JsonUtils.toJson(changeToServerFormat()));
httpRequest.setEntity(new UrlEncodedFormEntity(params,HTTP.UTF_8)); // 发出http请求
HttpResponse httpResponse = client.execute(httpRequest); // 取得http响应
if (httpResponse.getStatusLine().getStatusCode() == 200) {
String strResult = EntityUtils.toString(httpResponse.getEntity()); // 获取字符串
System.out.println("strResult" + strResult);
//上传成功修改本地数据库的标记位
ContentValues cv = new ContentValues();
cv.put("upload_mark", "1");
String testTime = testRecordTotal.getUserinfo().getTestTime();
DatabaseOperator dbo = new DatabaseOperator(new MyOpenHelperTestRecord(Check.this));
dbo.update(CONTANT.sheet_test_record, cv, "retain_field_1=?", new String[]{testTime});
}
} catch (Exception e) {
System.out.println("数据上传失败");
System.out.println(e.toString());
e.printStackTrace();
}finally{
client.getConnectionManager().shutdown();
}
};
}.start();
71、代码动态添加View的方法:
private void addAddressView(String [] strAddress){
DisplayMetrics dm = getResources().getDisplayMetrics();
NetworkDelayAddress networkDelayAddress = new NetworkDelayAddress();
networkDelayAddress.setName(strAddress[0]);
networkDelayAddress.setAddress(strAddress[1]);
LinearLayout linearLayout= null;
if(linearLayoutList.size()!=0){
linearLayout = linearLayoutList.get(linearLayoutList.size()-1);
}else{
linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
linearLayout.setLayoutParams(params);
ll_toolbox_network_delay_address.addView(linearLayout);
linearLayoutList.add(linearLayout);
}
if(addressList.size()%4 == 0){
linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(0, (int)(dm.density*12), 0, 0);
linearLayout.setLayoutParams(params);
ll_toolbox_network_delay_address.addView(linearLayout);
linearLayoutList.add(linearLayout);
}
TextView tv = new TextView(Network_Delay_Test_Activity.this);
tv.setText(strAddress[0]);
tv.setTextSize(12);
tv.setTextColor(0xff000000);
tv.setAlpha((float) 0.5);
tv.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams((int)(dm.density*71),(int)(dm.density*26));
layoutParams.setMargins((int)(dm.density*16), 0, 0, 0);
if(addressList.size()%4 == 0){
layoutParams.setMargins((int)(dm.density*16), 0, 0, 0);
}else{
layoutParams.setMargins((int)(dm.density*30), 0, 0, 0);
}
tv.setLayoutParams(layoutParams);
tv.setBackgroundResource(drawable.toolbox_network_delay_address_button_selecter);
linearLayout.addView(tv);
textViewList.add(tv);//添加view
addressList.add(networkDelayAddress);//添加地址
networkDelayAddressList.setNetworkDelayAddressList(addressList);//重置地址数据,保存到文件
System.out.println(JsonUtils.toJson(networkDelayAddressList));
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Message message = new Message();
Bundle bundle = new Bundle();
bundle.putString("str", addressList.get(addressList.size()-1).getName()+" "+addressList.get(addressList.size()-1).getAddress());
message.setData(bundle);
message.what = FLUS_EDIT_ADDRESS;
handler.sendMessage(message);
}
});
}
72、图片旋转处理
Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(),R.drawable.bg_toolbox_event_tip);
int width = bitmapOrg.getWidth();
int height = bitmapOrg.getHeight();
Matrix matrix = new Matrix();
matrix.postRotate(180);
matrix.postScale(1, -1);//左右翻转
Bitmap dealBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0,width, height, matrix, true);
BitmapDrawable bmd = new BitmapDrawable(dealBitmap);//Drawable
73、出现FAILED BINDER TRANSACTION 错误,读取历史记录,由于历史记录太多,使用bundler传输会有问题
04-22 16:40:48.501 3452 4800 E JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 682020)
04-22 16:40:48.502 3452 4800 W ActivityManager: Exception in new application when starting activity com.example.main/com.example.basetest.WiFi_Scan_HistoryRecord
74、给ArrayList里面的对象排序:
Collections.sort(sameChannelDisturbList, new SortByLevel());
class SortByLevel implements Comparator {//DisturbDetailWifi是list里面的对象
@Override
public int compare(DisturbDetailWifi o1, DisturbDetailWifi o2) {
DisturbDetailWifi ddw1 = (DisturbDetailWifi) o1;
DisturbDetailWifi ddw2 = (DisturbDetailWifi) o2;
return ddw1.getLevel().compareTo(ddw2.getLevel());
}
}
75、listView外面包裹ScrollView,实现整个屏幕的滚动。
有个问题:listView会占据屏幕所有,底下的组件就显示不出来
private void setSameChannelAdapter(){
sameChannelDisturbList = new ArrayList();
ArrayList wifiDisturbList = enviAnalysisTotal_Toolbox.getEnviAnalysis().getSameChannelDisturbList();
String freq = enviAnalysisTotal_Toolbox.getUserinfo().getCurrentFrequency();
if(Integer.parseInt(freq)<5000){
freq = "2.4GHz";
}else{
freq = "5GHz";
}
for (WiFiDistrub wifiDistrub : wifiDisturbList) {
LogUtils.v(TAG, "同频干扰WiFi的名称:" + wifiDistrub.getNoiseName());
DisturbDetailWifi disturbDetailWifi = new DisturbDetailWifi();
disturbDetailWifi.setName(wifiDistrub.getNoiseName());
disturbDetailWifi.setFreq(freq);
disturbDetailWifi.setChannel(wifiDistrub.getNoiseChannel());
disturbDetailWifi.setLevel(wifiDistrub.getNoiseRssi());
if(Integer.valueOf(wifiDistrub.getNoiseRssi())>=-60){
disturbDetailWifi.setComment("强");
sameChannelStrongDisturb++;
}else{
disturbDetailWifi.setComment("");
}
sameChannelDisturbList.add(disturbDetailWifi);
}
Collections.sort(sameChannelDisturbList, new SortByLevel());
DisturbAnalysisDetailWifiAdapter adapter = new DisturbAnalysisDetailWifiAdapter(this, sameChannelDisturbList);
lv_toolbox_disturb_analysis_detail_same_channel.setAdapter(adapter);//先设置adapter
setListViewHeight(adapter,lv_toolbox_disturb_analysis_detail_same_channel);//再根据listView的数据,固定listview的高度
}
//设置listview的高度
private void setListViewHeight(DisturbAnalysisDetailWifiAdapter adapter,ListView lv){
int totalHeight = 0;
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, lv);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = lv.getLayoutParams();
params.height = totalHeight + (lv.getDividerHeight() * (adapter.getCount() - 1));
lv.setLayoutParams(params);
}
76、替换单位,保留数字
sameChannel = sameChannel.replaceAll("[a-zA-Z\u4e00-\u9fa5]", "");//去掉字母,中文
sameChannel = sameChannel.replaceAll("[a-zA-Z\u4e00-\u9fa5%]", "");//去掉字母,中文 ,%
//输入框只能输入 数字,字母 . // :
et_dialog_confirm_input_address.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
String digists = "0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.:/";
et_dialog_confirm_input_address.setKeyListener(DigitsKeyListener.getInstance(digists));
77、handler定义为static,id不定义static调用方法:
private final MyHandler handler_moving = new MyHandler(this);
private static class MyHandler extends Handler{
private final WeakReference mActivity;
public MyHandler(Moving_Ping_Activity activity) {
mActivity = new WeakReference(activity);
}
public void handleMessage(Message msg) {
Moving_Ping_Activity activity = mActivity.get();
if (null != activity) {
switch (msg.what) {
case SHOW_TEXT_PING:
activity.tv_toolbox_distribute_lost_rate.setText(lostRate);
break;
default:
break;
}
}
}
}
78、今天遇到一个插入数据库一直失败的问题:
在程序退出的时候,写一次数据库,一直提示失败,返回码为-1,错误信息是已存在表
(明明就把手机上的apk卸载掉了)
1、在ondestory中使用了System.exit(0),把这个注释掉,还是会出错。
2、把插入数据的代码放到其他的地方执行,也一样会有问题。
3、检查MyOpenHelperTestRecord里面的代码,发现onCreate的方法里面没有创建BI数据表的代码
只是把这个代码放到了onUpgrade里面。这个就解释了为什么从以前版本升上来正常,而重新安装就不正常
4、把oncreate方法里面也加上创建的代码,可是还是出错,提示表已存在。
后来分析,是因为eclipse编译的问题,把bin目录下的apk删除,重新安装就OK了
79、延时处理:
有时候需要延迟处理,定义Handler太麻烦,就可以直接New 一个Handler,延时1秒执行代码
new Handler().postDelayed(new Runnable(){
public void run() {
viewPager.setCurrentItem(0);
}
}, 1000);
例如想让ScrollView启动activity的时候就滚动到最底部,就需要使用延时处理
sv_toolbox_network_delay.fullScroll(ScrollView.FOCUS_DOWN);
实现ScrollView滚动条的隐藏,有两种方法,
a.是在XML的ScrollView布局中加入属性android:scrollbars="none"
b.是在代码中获取ScrollView后进行scroll.setVerticalScrollBarEnabled(false);
80、在ViewPager(4个页面)里面,第二个页面有ListView组件,出现listView点击item不生效问题;
规律:只有点击第四个页面,再点击第二个页面,就必然出现
尝试的步骤:
1.把ListView里面的checkbox设置为不可用,不行
2.把关于(第四个页面)里面的组件全部去掉,不行
3.把自定义的ListView换成系统自带的的ListView显示,不行
4.在ListView上设置onTouch事件,查看有事件响应
5.在自定义adaper里面getView设置view的侦听,有事件响应
6.所以最后解决方法:在adapter里面设置侦听,写一个接口,在activity里面实现接口就可以了
81、assert里面的路由器mac地址表出现乱码:
保存的时候一定要保存为utf-8的格式:
方法:可以用记事本打开,另存为... utf-8
82、遇到一个反复点击开始测试工具箱,出现应用crash,挂在图表刷新上:
现象是反复点击工具箱会出现,而在网络时延中就不会。提示数组越界,java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
1.对比2个代码差异,工具箱handler使用的是static,把这个换回来一样会出现
2.把这三行代码注释掉,反复点击就不会出现
lineData.addXValue(arr[1]);
lineData.addEntry(new Entry(Float.valueOf(arr[2]), Integer.parseInt(arr[1])), 0);
lineData.addEntry(new Entry(Float.valueOf(delay_avg), Integer.parseInt(arr[1])), 1);
3.对比网络时延,这三变量没有定义为static,而工具箱里面是定义static的
private ArrayList xVals = new ArrayList();// 横坐标数据
private ArrayList entries = new ArrayList();// y坐标数据,显示测试值
private ArrayList entries_avg = new ArrayList();// 第二条线
4.把static去掉,就OK了
第二天发现问题仍然存在,继续检视代码,发现在点击的时候把按钮的值设置了“开始测试”,而由于点击的速度太快,第二次进入的时候,
读取到了值为“开始测试”,就进入了开启线程测试的分支,其实现在用不到这个了。由于2个线程不一样,导致修改数据异常(这也是个线程安全的问题)
把这个代码注释掉就OK了
之后又出现了个点击多次就停止测试了,log打印出来测试标记位被置为false了,将TEST_START不定义为static测试问题消失。每次启动保存一个标记位,不用同一个,有可能这次开始测试
,上一次结束还没来得及设置false,延迟了执行,导致标记位为false了,这次测试也就停止了,界面就不刷新了,感觉就像是卡死一样。
总结:不要轻易的将变量定义为static,因为这个变量在内存中只保存一份,其他地方修改了,可能导致不可预知的问题出现。注意线程安全的问题。
83、沉浸式状态栏的实现:
方法不止一种,结合网上的方法,给出一种解决方案:亲测可用
a.定义一个工具类,写一个static方法,直接调用
public class TranslucentStatusBar {
@SuppressLint("InlinedApi")
public static void setTranslucentStatusBar(Activity activity,int color){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//sdk大于19才能使用
Window window = activity.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
ViewGroup viewGroup = (ViewGroup) window.getDecorView();
View statusBarView = new View(window.getContext());
int statusBarHeight = getStatusBarHeight(window.getContext());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, statusBarHeight);
params.gravity = Gravity.TOP;
statusBarView.setLayoutParams(params);
statusBarView.setBackgroundColor(0xff1294F6);
viewGroup.addView(statusBarView);//动态添加一个view,如果不添加则显示的就是一个白色透明的状态栏
//这个代码可以代替在xml中去设置 android:fitsSystemWindows=true 这个属性,因为如果在每个xml里面去写这个属性太麻烦
//如果把这个属性写在style.xml中,在其他的dialog会显示异常,所以就写在代码里面了。
//如果去掉了这个属性代码的设置,那么页面就会覆盖状态栏(也会有沉浸式的效果,只是你的页面高度需要重新写下)
ViewGroup mContentView = (ViewGroup) window.findViewById(Window.ID_ANDROID_CONTENT);
View mChildView = mContentView.getChildAt(0);
if (mChildView != null) {
mChildView.setFitsSystemWindows(true);
}
}
}
//获取系统状态栏的高度
public static int getStatusBarHeight(Context context) {
int statusBarHeight = 0;
Resources res = context.getResources();
int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = res.getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
}
b.在onCreate方法中直接调用:
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_distribute_layout_home);
TranslucentStatusBar.setTranslucentStatusBar(this,0xff1294F6);//在setContentView之后调用
注意:setColor(0x1294F6)默认前面是0x00,全透明的。所以需要写成setColor(0xff1294F6),不设置透明度。
后面遇到一个问题,在有的手机上显示的是半透明的(android 5.0以上)。
解决的方案:调用一个API-21(对应的android5.0版本),window.setStatusBarColor(Color.TRANSPARENT);
在函数后面加上一段
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.L) {
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
测试OK
android SDK-21和21的源码下载地址:http://www.androiddevtools.cn/
84、有的android6.0以上的系统获取手机的mac地址出现 02:00:00:00:00:00 的问题
解决方案:通过多种的方式去获取:
//第一种方法
setWf(getWm().getConnectionInfo());
String phoneMac = getWf().getMacAddress();
LogUtils.e("gaoqiang", phoneMac);
第二种方法,有的系统没有读取/sys的权限
if(phoneMac.equals("02:00:00:00:00:00")){
String command = "cat /sys/class/net/wlan0/address";
BufferedReader buf = null;
try {
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
buf = new BufferedReader(new InputStreamReader(process.getInputStream(),"utf-8"));
String str = "";
StringBuffer sb = new StringBuffer();
while ((str = buf.readLine()) != null) {
sb.append(str).append("\n");
}
LogUtils.e("gaoqiang", sb.toString());
if(!phoneMac.equals("") && phoneMac.length()==17){//如果不为空,地址的位数正确,就重新赋值
phoneMac = sb.toString();
}
LogUtils.e("gaoqiang", phoneMac);
} catch(RuntimeException e){
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(null != buf){
buf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//第二种方法获取
phoneMac = "02:00:00:00:00:00";
if(phoneMac.equals("02:00:00:00:00:00")){
try {
List all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (nif.getName().equalsIgnoreCase("wlan0")) {
byte[] macBytes = nif.getHardwareAddress();
if (macBytes == null) {
break;
}
StringBuilder res1 = new StringBuilder();
for (byte b : macBytes) {
res1.append(String.format("%02X:",b));
}
if (res1.length() > 0) {
res1.deleteCharAt(res1.length() - 1);
}
LogUtils.e("gaoqiang", "res1:" + res1.toString());
phoneMac = res1.toString().toLowerCase();//注意默认获取的mac地址是大写的,这里转化了一下
break;
}
}
} catch (Exception e) {
LogUtils.e("gaoqiang", "Error lecture propriete Adresse MAC ");
}
}
85、遇到蓝牙扫描的时候,已连接的蓝牙设备扫描不出来。
问题的现象:打开扫描的工具,手机已连接蓝牙设备,但是扫描出来却没有。
问题的原因:因为已配对的蓝牙设备,会在“已配对的设备”这个列表中,不在“可用设备”列表中。
最终解决的方法:因为我们只需要查看设备是不是在线,所以我们去尝试连接下,发现如果绑定的设备在线,会有一个 BluetoothDevice.ACTION_ACL_CONNECTED 这个广播产生。
(最终验证可行,只是使用了多台手机,没有使用耳机设备验证;有个问题就是不能获取到rssi的值,一直都是0,这个需要另外研究)
解决的具体步骤:
a.先注册两个广播(连接完成和配对广播)
//配对请求广播
IntentFilter mFilterRequest = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
MyApplication.getContext().registerReceiver(mReceiver, mFilterRequest);
//连接成功广播
IntentFilter mFilterConnected = new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
MyApplication.getContext().registerReceiver(mReceiver, mFilterConnected);
b.我们在广播接收里面做下逻辑处理,如果收到 BluetoothDevice.ACTION_ACL_CONNECTED 广播,说明设备在线,我们就添加设备。
部分代码
...
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 实际测试中,如果连接设备的时候收到了这个广播,说明这个设备肯定在线
if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
LogUtils.i(TAG, "配对完成");
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
LogUtils.i(TAG, device.getName());
short rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
LogUtils.i(TAG, String.valueOf(rssi));
String[] arr = { device.getName(), device.getAddress(), String.valueOf(rssi) };//rssi会一直是0
discovery_devices.add(arr);
}
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
LogUtils.d(TAG, "请求配对");
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
try {
LogUtils.d(TAG, "Success to add the PIN.");
try {
abortBroadcast();
//这个地方注释掉,是因为我们不用去配对,其实查看设备在不在线可以不用这个广播,因为测试的时候只配对一次也没有收到广播(尝试多次配对会收到)
//ClsUtils.setPin(device.getClass(), device, "0000");
//ClsUtils.createBond(device.getClass(), device);
//ClsUtils.cancelPairingUserInput(device.getClass(), device);
LogUtils.d(TAG, "Success to setPairingConfirmation.");
} catch (Exception e) {
LogUtils.e(TAG, e.getMessage());
e.printStackTrace();
}
} catch (Exception e) {
LogUtils.e(TAG, e.getMessage());
e.printStackTrace();
}
}
...
c.我们在扫描的时候尝试去连下已绑定的设备,不然是不会收到广播的。
定义一个类
/**
* @author gaoqiang 检测已配对过的蓝牙设备是不是连接状态
*/
class CheckConnectedDevices extends Thread {
@Override
public void run() {
List devices_bonded = new ArrayList();
Set devices = mBluetoothAdapter.getBondedDevices();
if (devices.size() > 0) {
for (BluetoothDevice bluetoothDevice : devices) {
String[] arr = { bluetoothDevice.getName(), bluetoothDevice.getAddress() };
devices_bonded.add(arr);
LogUtils.d(TAG, "已配对过的蓝牙设备:" + bluetoothDevice.getName());
// 连接设备
ParcelUuid[] pus = bluetoothDevice.getUuids();
int count = 0;
for (ParcelUuid parcelUuid : pus) {
count++;
if (RUNNING && count <= 1) {//因为我们只是判断设备在不在线,所以设置1次(实际测试中会有10个左右的uuid,一般尝试连接5次可以成功)
UUID uuid = parcelUuid.getUuid();
LogUtils.d(TAG, "bluetoothDevice的uuid:" + bluetoothDevice.getName() + uuid);
try {
BluetoothSocket btSocket = bluetoothDevice.createRfcommSocketToServiceRecord(uuid);
btSocket.connect();
LogUtils.d(TAG, bluetoothDevice.getName() + "连接成功");
break;
} catch (IOException e) {
LogUtils.d(TAG, bluetoothDevice.getName() + "连接失败");
}
} else {
break;
}
}
}
}
}
}
d.扫描的时候启动尝试连接已绑定的设备:
new CheckConnectedDevices().start();//扫描已绑定在线设备 gaoqiang 2017-06-02
注意事项:
这个我们测试的时候是OK的
我们在收到 BluetoothDevice.ACTION_PAIRING_REQUEST 配对广播的时候,使用abortBroadcast(); ,目的是为了不弹出配对框,只会有个toast。
具体的一些蓝牙配对问题还需要深入的研究。(配对码)
86、AsyncTask (异步任务)的使用:
AsyncTask是一个轻量级的,适用简单的异步处理。不需要借助Handler和线程即可实现。
AsyncTask是一个抽象类,通常被继承,继承时需要指定3个泛型参数
AsyncTask //输入的参数,进度值的参数(通常是Integer),任务执行完返回结果的参数。
需要实现的一些方法:
onPreExecute:执行操作前调用,一般用于初始化一些操作
doInBackground:真正执行的任务
在doInBackground中执行publishProgress(i); 可以调用onProgressUpdate方法更新界面
onProgressUpdate:在执行任务的时候可以调用,用来更新进度,在这个方法中可以更新UI
onPostExecute:执行完成,系统自动调用,可以刷新UI
onCancelled:这个方法可以手动调用,如果在doInBackground中,我不想在做耗时操作了,手动结束
直接在doInBackground中调用cancel(true);
注意:
a.必须在UI线程中创建AsyncTask实例。
b.必须在UI线程中调用AsyncTask的execute()方法开始。
c.每个AsyncTask只能被执行一次,多次调用会引发异常。
06-03 11:04:54.413: E/AndroidRuntime(11668): Caused by: java.lang.IllegalStateException: Cannot execute task: the task is already running.
一个简单的示例:在button上计时0-9
public class MainActivity extends Activity {
Button bt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt = (Button) findViewById(R.id.bt);
}
public void download(View v) throws MalformedURLException {
DownloadTask task = new DownloadTask(this);
task.execute(0);//UI线程开始异步任务
}
class DownloadTask extends AsyncTask {
private Context context;
public DownloadTask(Context context) {
this.context = context;
}
@Override
protected String doInBackground(Integer... params) {
System.out.println("开始执行耗时操作");
System.out.println("接收的参数是:" + params[0]);
for (int i = 0; i < 10; i++) {
publishProgress(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cancel(true);
return "返回结果";
}
@Override
protected void onPreExecute() {
System.out.println("任务前的准备");
}
@Override
protected void onPostExecute(String result) {
System.out.println("返回的结果是:"+result);
bt.setText("OK");
System.out.println("任务完成");
}
@Override
protected void onProgressUpdate(Integer... values) {
System.out.println("进度更新 "+values[0]);
bt.setText(values[0] + "");
}
@Override
protected void onCancelled() {
System.out.println("onCancelled");
System.out.println("任务取消");
}
}
}
87、在服务器端增加字段步骤:
a.打开本地web服务器,databases配置到服务器。
b.为了快速的上传数据,将Check中的canUpload设置为true,不需要全部测完。
c.在服务器端“UploadServerResult”类中增加一个字段。
private String userinfo_phone_connected_freq;//手机连接的wifi频率(2.4GHz 5GHz)
生成get set 方法
在“getInsertMap”方法中判断是否为null或“NA”,决定是否插入数据库
例如:
if(null != uploadServerResult.getUserinfo_phone_connected_freq() && !("NA").equals(uploadServerResult.getUserinfo_phone_connected_freq())){
insertMap.put("userinfo_phone_connected_freq", uploadServerResult.getUserinfo_phone_connected_freq());
}
d.此时服务器端加了字段,客户端没有加,插入数据的时候不会出现问题,只是服务器新增的字段值为null。
例如:
"userinfo_phone_connected_freq":null
注意:如果客户端有的字段,在服务器端没有会出问题。
e.在ExampleTestMapper.xml 中添加字段,同时需要在数据库增加字段,否则会出错。
f.在客户端增加 “userinfo_phone_connected_freq” 字段。
例如:
//手机当前连接的频率
String userinfo_phone_connected_freq_local = testRecordTotal.getUserinfo().getCurrentFrequency();
if(!userinfo_phone_connected_freq_local.equals("NA")){
userinfo_phone_connected_freq_local = userinfo_phone_connected_freq_local.replaceAll("[a-zA-Z\u4e00-\u9fa5]", "");
int userinfo_phone_connected_freq = Integer.parseInt(userinfo_phone_connected_freq_local);
if(userinfo_phone_connected_freq >0 && userinfo_phone_connected_freq <5000){
uploadServerResult.setUserinfo_phone_connected_freq("2.4GHz");
}else{
uploadServerResult.setUserinfo_phone_connected_freq("5GHz");
}
}
g.成功上报,调试OK
恢复数据
服务器端上传的文件
“UploadServerResult.java”
"ExampleTestMapper.xml"
数据库记得加字段
88、可折叠列表框:
elv_toolbox_router.setGroupIndicator(null);//设置为null,不显示系统的箭头
elv_toolbox_router.expandGroup(0);//默认展开第一项
在xml文件中设置(代码中设置测试没有生效)
android:childDivider="#00000000"//设置子item不显示分割线,透明的
android:divider="#00000000"//设置group组之间的分割线不显示,透明的
在设置分割线的时候遇到一个问题:
就是最后一个item分割线不会显示出来,后面分析,可能是分割线在底部,被下一个item遮住了(之前还以为是设置0.5dp无效)。
解决的方法:就是将分割线margin底部0.5dp
DisplayMetrics dm = MyApplication.getContext().getResources().getDisplayMetrics();
RelativeLayout.LayoutParams paramsChildLineItem = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,(int) (dm.density * 0.5));
paramsChildLineItem.setMargins((int) (dm.density * 72), -1, (int) (dm.density * 16), (int) (dm.density * 0.5));
paramsChildLineItem.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
viewHolderChild.line.setLayoutParams(paramsChildLineItem);
89、代码从资源文件中获取数据:
arrays.xml:
- 100M
- 50M
- 20M
- 10M
- 4M
代码中获取的方式:
String [] arr = getResources().getStringArray(R.array.spinner_router_evaluate_operation);//一个String数组
ArrayList data = new ArrayList();//list方式
for (int i = 0; i < arr.length; i++) {
data.add(arr[i]);
}
90、禁止页面横屏
在xml配置:
在代码中设置:
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
91、android自定义View的属性定义
我们需要一个color(interger的),和一个check(boolean的)
a.
注意:加上自己的命名空间 xmlns:mynamespace="http://schemas.android.com/apk/res/com.example.mydefineview"
mynamespace:paint_color="@integer/paint_color"//我们可以定义一个integer类型变量
mynamespace:paint_check="false"
调用
b.
在values文件夹下创建(名称可以随便取,最好能看懂) attrs.xml
//这个名称也没有要求
//定义整型的
c.
我们在string.xml中可以
MyDefineVeiw
Settings
Hello world!
//可以直接定义integer
0xffff0000
也可以在自己创建一个integer.xml的文件,在里面定义
d.
在代码中使用
private static final String NAMESPACE = "http://schemas.android.com/apk/res/com.example.mydefineview";
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
//这个整型我们是通过context拿到
color = context.getResources().getInteger(R.integer.paint_color);
//这个直接从attrs中拿到属性
boolean check = attrs.getAttributeBooleanValue(NAMESPACE,"paint_check", false);
System.out.println("tag + " + check);
paint = new Paint();
paint.setColor(color);
paint.setStrokeWidth((float) 3.0);
paint.setStyle(Style.STROKE);
}
92、强制横屏:
在xml的activity配置
android:screenOrientation="landscape"
代码中不要设置
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
不然会导致加载卡顿
93、获取布局的margin参数的方式:
MarginLayoutParams mlp = (MarginLayoutParams) tv_my_wifi_city.getLayoutParams();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(tv_my_wifi_city.getLayoutParams());
设置2个参数
params.setMargins(mlp.leftMargin, mlp.topMargin, mlp.rightMargin, mlp.bottomMargin);
tv_my_wifi_city.setLayoutParams(params);
94、intent传递bitmap数据:
Intent intent = new Intent(Test_My_WiFi_Select.this,Test_My_WiFi_Execute.class);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
myWiFiHouseType.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, baos);
byte [] bitmapByte =baos.toByteArray();
intent.putExtra("bitmap", bitmapByte);
intent.getStringExtra(myWiFiHouseType.getHouseDiscript());
intent.getStringExtra(myWiFiHouseType.getHousePosition());
startActivity(intent);
接收数据:
byte [] bis=intent.getByteArrayExtra("bitmap");
Bitmap bitmap=BitmapFactory.decodeByteArray(bis, 0, bis.length);
String houseDiscript = intent.getStringExtra("houseDiscript");
String housePosition = intent.getStringExtra("housePosition");
95、获取listview中的当前item的位置(在列表中的位置)
给listview设置滚动侦听 import android.widget.AbsListView.OnScrollListener;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
System.out.println("onScrollStateChanged: " + scrollState);
int count = view.getChildCount();//当前屏幕可见的view
for (int i = 0; i < count; i++) {
TextView tv = (TextView) view.getChildAt(i).findViewById(R.id.iv_house_left_discript);
System.out.println(tv.getText().toString() + " 位置:" + tv.getTag());
}
}
}
在adpater里面给一个view设置tag,tag的就设置position的值
96、遇到一个横竖屏快速切换的时候出现之前页面重新调用oncreate加载:
问题背景:首先一个竖屏页面,跳转到横屏页面,快速返回的时候之前的那个竖屏页面重新加载了。
尝试的解决方法:
1、在AndroidManifest.xml文件中配置activity
发现问题还在。
2、在网上搜索,在竖屏界面要重写一个方法:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
} else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
}
}
我们快速切换的时候,就概率调用到这个方法,而这个方法里面没有做任何处理,也就不会重启activity
问题解决。
97、隐藏软键盘
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(et_my_wifi_building_name.getWindowToken(), 0);
98、ImageView的setImageBitmap 和setbackgroudResource区别:
setImage是设置iamge的内容
setbackgroud是设置图片的背景
如果设置了2个就会导致2个图片重叠的情况
解决方法:在设置一个的时候,另一个设置无效
iv_my_wifi_house.setImageBitmap(bitmap);
iv_my_wifi_house.setBackgroundResource(0);
iv_my_wifi_house.setImageBitmap(null);//内容无效
iv_my_wifi_house.setBackgroundResource(R.drawable.home);//背景有效
99、android6.0 权限请求控制问题:
查看某个权限用户是否授权
注意在AndroidManifest.xml文件中 android:targetSdkVersion="23" 这个最少要设置23以上,因为android6.0才加了权限的动态控制
例如:
checkSelfPermission : 查看是否授权,需要使用sdk23 ,23以下的api中没有这个方法。
requestPermissions : 请求授权
private static final int REQUEST_PEMISSION_LOCATION_ENVIRONMENT_CODE = 0;
private static final String REQUEST_PEMISSION_LOCATION_ENVIRONMENT_NAME = "android.permission.ACCESS_FINE_LOCATION";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(REQUEST_PEMISSION_LOCATION_ENVIRONMENT_NAME)!= PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {REQUEST_PEMISSION_LOCATION_ENVIRONMENT_NAME},REQUEST_PEMISSION_LOCATION_ENVIRONMENT_CODE);
}else{
runEnvironmentAnalysis();//正常的逻辑操作
}
}
我们点击授权弹框后,回调的方法,我们可以在这个回调中做一些逻辑操作
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_PEMISSION_LOCATION_ENVIRONMENT_CODE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
runEnvironmentAnalysis();
} else {
if (!shouldShowRequestPermissionRationale(REQUEST_PEMISSION_LOCATION_ENVIRONMENT_NAME)) {
Uri data = Uri.fromParts("package", getPackageName(), null);
showErrorDialogPosition(MainActivity.this,"分析仪需要位置权限,快去设置再来吧!",Settings.ACTION_APPLICATION_DETAILS_SETTINGS,data);
}
}
break;
default:
break;
}
}
注意,如果用户点击了不在提示后,requestPermissions是不会再弹框提示了。
shouldShowRequestPermissionRationale : 这个方法可以判断用户时候点击了不再提示。
如果点击了,我们就弹出自定义的弹框提示用户,需要授权
这几个方法都是属于activity中的方法。
100、未root手机,导出已安装应用的apk方法:
连接adb
应用一般安装完成后都在 /data/app/...apk
我们直接访问是没有权限的
先确定apk的包名(pm list packages 和dumpsys window 都行)
adb pull /data/app/com.smartapp.welcome-1.apk (1代表是第几次安装,如果重装过就可能是2 3 等,一个个试)
手机安装包名称是
adb pull /data/app/com.smartapp.welcom-1/base.apk (名称变成了base.apk了)
101、android6.0存储数据到sd卡上
String path= Environment.getExternalStorageDirectory().getAbsolutePath()+"/person.txt";
//path = /storage/emulated/0/person.txt
给了 还提示没有权限
存储在自己应用的目录下是不用任何权限的
this.getCacheDir()+"/person.txt";
// "/data/user/0/test.example.com.hello/cache/person.txt";
this.getFilesDir()+"/person.txt";
// "/data/user/0/test.example.com.hello/files/person.txt";
当时系统在内存不足的情况下会清除应用cache中的数据
102、隐藏/显示密码输入框的文本
显示:
et_password.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
et_password.setSelection(et_password.getText().toString().length());
隐藏:
et_password.setTransformationMethod(PasswordTransformationMethod.getInstance());
et_password.setSelection(et_password.getText().toString().length());//光标移动到最后
103、android6.0 代码忘记已连接的网络错误
网上查找的原因是:android6.0以上,当前的app不允许移除其他app建立的wifi连接,6.0以下没有这个问题。
自己的应用连接的ssid是可以忘记的。
104、android判断当前应用是否处于后台(不需要额外的权限)
public static boolean isBackground(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List appProcesses = activityManager.getRunningAppProcesses();
for (RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(context.getPackageName())) {
if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
LogUtils.d("ApplicationBI", "后台");
return true;
}else{
LogUtils.d("ApplicationBI", "前台");
return false;
}
}
}
return false;
}
105、使用手机测试wifi接口,获取频率值的时候有时候会不准
手动设置路由器的信道,读取接口的数据有时候会是之前的信道(对比wifi分析仪和wifi魔盒是正常的)
有问题的方式:
WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiInfo.toString().contains("Frequency:")) {
//取的会有问题
}
解决方法:(扫描所有ssid,通过mac比对获取)
WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
wifiManager.startScan();
List list = wifiManager.getScanResults();
String connectedMac = wifiManager.getConnectionInfo().getBSSID();
for (ScanResult scanResult : list) {
if (connectedMac.equals(scanResult.BSSID)) {
wifi_freq = scanResult.frequency + "";
}
}
测试正常通过
106、遇到一个奇怪的问题,wifi扫描使用WifiInfo wifiInfo = wifiManager.getConnectionInfo(); 有时候会卡在方法里面几十秒
解决方法:使用wifiManager.getScanResults(); ,通过mac地址判断来规避
107、设置当前activity不熄屏
108、查看应用的内存
a.查看meminfo文件
cat /proc/meminfo
cat /proc/meminfo |grep Mem
b.更详细的信息可以使用dumpsys命令
dumpsys meminfo 查看总的内存使用
dumpsys meminfo com.example.main 查看单个应用的内存使用情况
dumpsys meminfo 27606 通过pid查看单个应用的内存使用情况
109、简单内存泄露分析
DDMS + MAT(Memory Analysis Tool)
内存的知识:
JAVA是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。
栈(stack):栈最显著的特征是:LIFO(Last In, First Out, 后进先出),栈中只存放基本类型和对象的引用(不是对象)。
堆(heap):堆内存用于存放由new创建的对象和数组。
在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。
方法区(method):又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
栈(stack)可以自行清除不用的内存空间。但是如果我们不停的创建新对象,堆(heap)的内存空间就会被消耗尽。
所以JAVA引入了垃圾回收(garbage collection,简称GC)去处理堆内存的回收。
从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
强引用(Strong reference):
常见形式如:A a = new A();等
软引用(Soft Reference):
A a = new A();
SoftReference srA = new SoftReference(a);
软引用所指示的对象进行垃圾回收需要满足如下两个条件:
1.当其指示的对象没有任何强引用对象指向它;
2.当虚拟机内存不足时。
弱引用(Weak Reference):
A a = new A();
WeakReference wrA = new WeakReference(a);
WeakReference不改变原有强引用对象的垃圾回收时机,一旦其指示对象没有任何强引用对象时,此对象即进入正常的垃圾回收流程。
虚引用(Phantom Reference):
其实在Android中会造成内存泄露的情景无外乎两种:
a.全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。
b.活在Activity生命周期之外的线程。没有清空对Activity的强引用
DDMS是ADT自带的调试工具:
MAT的下载网址:http://www.eclipse.org/mat/downloads.php
我自己在线安装一直都不成功,各种错误。后面直接在下载(Stand-alone Eclipse RCP Applications)版本。
开始的时候下载了64位的版本,发现运行exe文件的时候老是报 “failed to load the jni shared jvm.dll” 这个错误。
后面又下载32位了发现可以运行。查看本地的java版本,发现是32位的(我电脑是64位的),这就可以解释通。
D:\android-sdk-windows-4.3\platform-tools>java -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) Client VM (build 24.51-b03, mixed mode, sharing)
分析的基本步骤:
a.先获取hprof文件,在eclipse的ddms视图中,选择你要分析的进程,点击绿色的小圆柱(Update Heap),再点击Gause GC。
手动触发垃圾回收保存一份开始运行的hprof文件(1.hprof)
b.构造一个有内存泄露的代码,反复操作,触发内存泄露。
public class Second extends Activity {
private List list = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
// 模拟Activity一些其他的对象
for (int i = 0; i < 10000; i++) {
list.add("Memory Leak!");
}
// 开启线程
new MyThread().start();
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
// 模拟耗时操作
try {
Thread.sleep(10 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
c.再次点击Gause GC,触发java垃圾回收,如果有内存泄露,java就不能正常的回收,此时再dump一份hprof文件(2.hprof)。
d.使用MAT分析hprof文件的时候需要先使用工具转化一下,不然会报“Unknown HPROF Version (JAVA PROFILE 1.0.3) ”错误。
在android sdk的目录下有工具,我的目录是 D:\android-sdk-windows-4.3\platform-tools\
cmd到当前的目录下,把你需要转化的hprof文件也可以拷贝到当前的目录
hprof-conv 2.hprof after.hprof
瞬间完成,完成后就可以把之前的文件删除了。
e.打开MAT工具,加载(Open Heap Dump ...)转换后的hprof文件就可以进行内存泄露分析了。
f.打开Histogram(直方图)他列举了每个对象的统计。它可以列出任意一个类的实例数。
比如你需要查看MainActivity,可以使用正则表达式 .*Main.* (注意大小写)
可以列出与MainActivity相关的类
g.选中com.example.test.MainActivity,右击,选择“Merge Shortest Paths to GC Roots”,
再选择选择“exclude all phantom/weak/soft etc.references”
(排查虚引用/弱引用/软引用等)因为被虚引用/弱引用/软引用的对象可以直接被GC给回收.
在JAVA中是通过可达性(Reachability Analysis)来判断对象是否存活,这个算法的基本思想是通过一系列的称谓"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走得路径称为引用链
h.如果存在GC Roots链,即存在内存泄露问题。
除了使用Merge Shortest Paths to GC Roots 我们还可以使用
List object - With outgoing References 显示选中对象持有那些对象
List object - With incoming References 显示选中对象被那些外部对象所持有
Show object by class - With outgoing References 显示选中对象持有哪些对象, 这些对象按类合并在一起排序
Show object by class - With incoming References 显示选中对象被哪些外部对象持有, 这些对象按类合并在一起排序
通过 Dorminator Tree(支配树),可以直观地反映一个对象的retained heap
shallow heap:指的是某一个对象所占内存大小。
retained heap:指的是一个对象的retained set所包含对象所占内存的总大小。
它主要可以用于诊断一个对象所占内存为什么会不断膨胀,一个对象膨胀,就说明它对应到支配树中的子树就越来越庞大。
根据retained heap进行排序
注意:MAT工具上显示的size的大小单位是: Bytes 。例如 21414072 = 20.4M
在MAT中可以查看到有类似如下的显示
com.example.main.MainActivity$1
com.example.main.MainActivity$2
这个 $1 表示第一个匿名类的大小。
$2、$3这样排下去。
使用的技巧:
1.我在Second这个activity中把list的在加大,出现内存泄露,通过直方图看都只有一个应用,但是实际GC后应用的内存占到68M,正常只有15M左右。
这个时候通过dominator_tree来看就非常的直观。
排名第一的就是
com.example.testmemoryleak.Second 216 68,025,088 76.26%
shallow heap: 216Bytes
retained heap: 68M
光这一个activity就占了76.26%,所以肯定是有什么东西没有释放。(这里我们知道是list 100万个string)
发现是这个类持有了Second的引用,导致activity的资源得不到释放。
android.view.inputmethod.InputMethodManager
2.直方图里面也可以跳转到dominator_tree查看单一对象所持有的对象
右击对象--Java Basics -- Open In Dominator Tree
很实用
3.操作前后两个hprof的对比:
dump出前后2个hprof文件
使用mat工具打开,在Navigation History这个视图中,右击histogram--Add to Compare Basket
Window -- Compare Basket,里面会有2个histogram。点击对比就可以了。
上面这个对比结果不利于查找差异,还可以调整对比选项
Difference from base Table
4.我们使用对比发现了操作后的内存中多了很多 char[] (我们构造的20万个String未释放)。
a.可以右击 -- Immediate dominators(查看引用者) -- 可以发现Second这个activity持有20万个,基本可以判断Second这个页面有内存泄露。
我们再查看那个变量持有了这么多。
b.右击这个Second -- 选择 Dominated Objects(注意一定是这个不要选择Objects里面的) -- Merge Shortest Paths to GC Roots --
exclude all phantom/weak/soft etc.references
c.一步步看谁持有了这个Second不释放
发现是
android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper
.
.
list
可以确定是输入法持有了activity的引用,导致Second activity里面list这个变量未正常释放。
(输入法是单例的,只会持有一个Second的,所以不管你打开多少次这个页面,泄露的大小是固定的。网上查阅这个是系统的一个bug,
如果页面占用内存不是太多,主要是全局变量,那么泄露的就可以接受)
根据一个内存地址查对象
110、避免重复弹Toast,限定3秒之内不许弹,防止反复点击Toast一直不消失
/**
* 显示toast,3s内不在弹,避免多次点击界面toast一直不消失
* @author gaoqiang
*/
private static boolean canShowToast = true;
public static void showToast(String content){
if(canShowToast){
Toast.makeText(MyApplication.getContext(), content, Toast.LENGTH_SHORT).show();
canShowToast = false;
new Handler().postDelayed(new Runnable(){
public void run() {
canShowToast = true;
}
}, 3000);
}
}
111、验证ip的合法性:
public static boolean ipCheck(String text) {
if (text != null && !text.isEmpty()) {
// 定义正则表达式
String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\." + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
// 判断ip地址是否与正则表达式匹配
if (text.matches(regex)) {
// 返回判断信息
return true;
} else {
// 返回判断信息
return false;
}
}
return false;
}
112、使用”Pass”.toLowerCase(); 会提示一个警告错误:
Implicitly using the default locale is a common source of bugs: Use toLowerCase(Locale) instead
解决方式:
"Pass".toLowerCase(Locale.CHINESE);
这样就不会提示错误
113、遇到一个页面底部有输入框,点击输入框输入的时候,发现软键盘挡住了输入区域:
解决的方法:
在AndroidManifest.xml中配置activity的属性,配置adjustPan这个属性
网上说这两个属性都行,实际测试,只有adjustPan才能达到效果
adjustPan是把整个界面向上平移,使输入框露出,不会改变界面的布局;
adjustResize则是重新计算弹出软键盘之后的界面大小,相当于是用更少的界面区域去显示内容,输入框一般自然也就在内了。
页面有WebView的情况需要另外处理,网上有处理方法,加一个AndroidBug5497Workaround这个类就OK(暂未测试验证)。
114、遇到一个使用第三方jar导致的内存泄露的问题:
使用GifView.jar正常使用可以达到想要的效果,今天检查内存泄露的时候发现这个会导致严重的内存泄露问题。
没测试退出一次内存+8M左右
后面分析发现是这个View里面的一个子线程没有停止,导致当前页面的其他资源得不到释放(图表list数据等)
因为是jar包,所以改不了里面的源码(这个动画也不是太重要)。
后面看到class里面有一些私有的属性,但是我们找不到共有的方法来设置,也就是activity退出了,没法停止这个gif,导致内存大量的泄露。
后面想到一个解决方法:使用反射机制来将里面的私有属性(isRun)设置为false,这样就可以停止了
private GifView gv_toolbox_distribute_gif;
gv_toolbox_distribute_gif = (GifView) findViewById(R.id.gv_toolbox_distribute_gif);
gv_toolbox_distribute_gif.setGifImage(R.drawable.ic_toolbox_distribute_gif);//这里启动gif动画,
DisplayMetrics dm = getResources().getDisplayMetrics();
gv_toolbox_distribute_gif.setShowDimension((int)(dm.density*8), (int)(dm.density*18));
在onDestory方法里面使用
//gaoqiang 2017-12-05 修复内存泄露问题(gif有内存泄露)
if(null != gv_toolbox_distribute_gif){
try {
Class extends GifView> clazz = gv_toolbox_distribute_gif.getClass();
Field f = clazz.getDeclaredField("isRun");//使用反射来设置私有属性,暴力停止
f.setAccessible(true);
f.set(gv_toolbox_distribute_gif, false);
} catch (Exception e) {
LogUtils.d(TAG, "停止gif失败:" + e.getMessage());
}
}
测试成功的释放了内存,gif可以停止。
115、查看手机app最大允许申请的heap堆内存大小:
cat /system/build.prop
dalvik.vm.heapstartsize=8m //它表示堆分配的初始大小
dalvik.vm.heapgrowthlimit=192m // 单个应用可用最大内存
dalvik.vm.heapsize=512m //heapsize参数表示单个进程可用的最大内存,但如果存在heapgrowthlimit参数,则以heapgrowthlimit为准.
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m
116、内存泄露的总结:
在实际的应用中遇到了以下一些的内存泄露问题:
a.将一些View定义为static,例如小窗口显示:我在其它的类里面直接调用静态的方法来设置静态的View上面的文本。
因为static的生命周期很长,导致这个View持有activity的引用不释放,导致activity销毁了,资源还得不到释放(如果有图片资源泄露的更多)。
解决的方法:
将View不要定义为static,Handler类定义为静态的,使用弱引用来持有activity的引用。
b.在工具类GetWiFiInformation类中,定义了一个静态的WiFiManager变量wm
在使用的时候,直接将context传到这个工具类中,wm使用到了这个context获取服务,activity销毁了,wm不会销毁,导致activity资源不释放,内存泄露。
解决的方法:
在工具类中不用传递进来的context,直接获取全局的Context
或者context.getApplicationContext();来获取
c.将context传进了测试类中,类里面又将context定义为static,导致activity销毁内存不释放。(和上面的情况类似)
解决方式:
测试完成,将静态的context设置为null,断掉GC Root链路
d.使用第三方的jar包,显示gif图像导致的内存泄露:
我们启动了gif,但是jar提供的停止的方法中不能停止它里面的子线程。
使用mat分析到时jar包里面的子线程引用了activity,这样子线程不结束,activity就释放不了
解决方法:
我们没有jar的源码,通过查看class显示的私有属性有个isRunning,但是没有提供方法设置。我通过反射去设置了这个变量的属性。问题解决,可以正常释放了。
if(null != gv_toolbox_distribute_gif){
try {
Class extends GifView> clazz = gv_toolbox_distribute_gif.getClass();
Field f = clazz.getDeclaredField("isRun");
f.setAccessible(true);
f.set(gv_toolbox_distribute_gif, false);
} catch (Exception e) {
LogUtils.d(TAG, "停止gif失败:" + e.getMessage());
}
}
e.自定义一个WaveCircle动画水波纹,里面使用了postDelay方法。导致内存泄露(网上有比较多的这个分析案例)
通过mat工具分析发现,有个ThreadLocal老是持有activity,继续往下看,在getRunQueue里面有2个Runnable未执行。
后面解决方法:
1.在调用stop动画的时候睡500ms,测试有效。
2.我使用发消息的方法stop动画,测试也有效(暂未研究源码,也许是消息执行需要等待一段时间)
f.有个等待的状态Dialog没有正常关闭
通过mat分析到时有个WindowManger持有了activity的引用不释放。后面查代码发现dialog根本就没有关闭。
解决方法:
在dialog显示完成结束的时候,需要手动dismiss掉这个dialog,实际测试OK
g.使用了帧动画,没有正常结束
通过mat分析到有很大的Bitmap未释放,把原图导出来看下,发现是帧动画的原图,检查代码发现,原来开启了动画,当时没有结束动画。
解决方法:
在动画显示完成的时候stop掉就OK了。
117、调用系统提示音:
public class RingUtils {
public static void startRing(Context context) {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
if (notification == null)
return;
Ringtone r = RingtoneManager.getRingtone(context.getApplicationContext(), notification);
r.play();
}
}