温故而知新——Android基本知识(2016-08-22)


一、 Linux 常用指令

mkdir 创建文件夹
rmdir 删除文件夹
rm 删除文件
mv 移动文件
cp 拷贝文件
cat 查看文件
tail 查看文件尾部
more 分页查看文件
cd 切换当前目录
ls 列出文件清单
reboot 重启
date 显示日期
cal 显示日历
ps 查看系统进程相当于 windows 的任务管理器
ifconfig 配置网络


学习网址:初窥Linux 之 我最常用的20条命令


2、书写出 android 工程的目录结构

src 源文件
gen 生成的文件 R 文件就在此
android. jar 依赖的 android sdk
assets 资源文件
bin 生成的字节码 apk 在此
libs 依赖 jar 和 so
res 资源文件
drawable
drawable-hdpi
layout
menu
values
AndroidManifest.xml
project.properties


3、 什么是 ANR 如何避免它?

ANR分类?

ANR 产生原因? 主线程有哪些? 非主线程有哪些?

如何避免ANR ? 

参考:Android ANR产生原因和解决办法


5、 一条最长的短信息约占多少 byte?

中文 70 (包括标点),英文 160, 160 个字节。

众所周知,一条短信是可以输入70个中文字符的。一个字符是2个字节(Byte)。一个字节是8位(bit).所以通用的计量是 一条短信可以输入70*2*8=1120 bit
一个ASCII码是7bit(128个字符的标准ASCII码),如果你输入的是标准ASCII码的话你,就可以在一条短信中输入1120/7=160个ASCII字符。


发送短信的方法,如何发送长短信,群发短信。

参考:android发送短信的两种方式,发送长短信的两种方式,群发短信


7、 如何判断是否有 SD 卡?

通过如下方法:
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
如果返回 true 就是有 sdcard,如果返回 false 则没有。

注意:

getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir() 区别和使用

参考:getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir() 解析


启动应用后,改变系统语言,应用的语言会改变么?


这个一般是不会改变的,一般需要重启应用才能改变应用语言。

如果应用做了国际化处理,则改变系统语言,应用语言会改变。但是如果没有做国际化处理,系统语言再怎么变,应用语言也不会变。


ddms 和 traceview 的区别
简单的说 ddms 是一个程序执行查看器,在里面可以看见线程和堆栈等信息,traceView 是程序性能分析器。


 Activity 生命周期


Activity 从创建到销毁有多种状态,从一种状态到另一种状态时会激发相应的回调方法,这些回调方法包括:
onCreate   onStart  onResume  onPause  onStop  onDestroy

其实这些方法都是两两对应的,onCreate 创建与 onDestroy 销毁;
onStart 可见与 onStop 不可见;onResume 可编辑(即焦点)与 onPause;

这 6 个方法是相对应的,那么就只剩下一个 onRestart 方法了,这个方法在什么时候调用呢?
答案就是:在 Activity 被 onStop 后,但是没有被 onDestroy,在再次启动此 Activity 时就调用 onRestart(而不再调用 onCreate)方法;
如果被 onDestroy 了,则是调用 onCreate 方法。


Activity异常情况下的生命周期分析 

情况1:资源相关的系统配置发生改变导致activity被杀死并重新创建

比如旋转屏幕

此时生命周期如下图所示:




其 OnPasuse 、onStop 、onDestroy 方法均被调用。onSaveInstanceState 方法会在onStop之前执行,它和onPause没有特定顺序,可前可后。

Activity 调用onSaveInstanceState 保存数据,然后Activity委托window去保存,接着window再委托顶级容器保存数据。DecorView再去通知子View保存数据。

基本的保存activity状态代码示例:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (null != savedInstanceState) { //注意要判空
			String test = savedInstanceState.getString("extra_test");
			Log.d(TAG, "[onCreate]restore extra_test:" + test);
		}
    }
    @Override
    protected void onSaveInstanceState(Bundle outState) {
    	super.onSaveInstanceState(outState);
    	Log.d(TAG, "onSaveInstanceState" );
    	outState.putString("extra_test", "test");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
    	super.onRestoreInstanceState(savedInstanceState);
    	String test = savedInstanceState.getString("extra_test");//不需要判空,如果走这个方法,则savedInstanceState一定不为空。建议用这个方法恢复数据。
		Log.d(TAG, "[onRestoreInstanceState]restore extra_test:" + test);
    	
    }


1、 不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生命周期 默认首先销毁当前 activity,然后重新加载。


2、 设置 Activity 的 android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged 方法。


情况2:资源内存不足导致低优先级的activity被杀死



两个 Activity 之间跳转时必然会执行的是哪几个方法?

一般情况下比如说有两个 activity,分别叫 A,B,当在 A 里面激活 B 组件的时候, A 会调用 onPause()方法,然后 B 调用 onCreate() ,onStart(), onResume()。
这个时候 B 覆盖了窗体, A 会调用 onStop()方法. 如果 B 是个透明的,或者是对话框的样式, 就不会调用 A 的onStop()方法。


如何退出 Activity?如何安全退出已调用多个 Activity 的 Application?


1、通常情况用户退出一个 Activity 只需按返回键,我们写代码想退出 activity 直接调用 finish()方法就行。


2、记录打开的 Activity:(自定义Activity任务栈)
每打开一个 Activity,就记录下来。在需要退出时,关闭每一个 Activity 即可。


//伪代码
List<Activity> lists ;// 在 application 全局的变量里面
lists = new ArrayList<Activity>();
lists.add(this);
for(Activity activity: lists)
{
activity.finish();
}
lists.remove(this);


3、发送特定广播:
在需要结束应用时,发送一个特定的广播,每个 Activity 收到广播后,关闭即可。
//给某个 activity 注册接受接受广播的意图
registerReceiver(receiver, filter)
//如果过接受到的是 关闭 activity 的广播 就调用 finish()方法 把当前的 activity finish()掉


4、递归退出
在打开新的 Activity 时使用 startActivityForResult,然后自己加标志,在 onActivityResult 中处理,递归关闭


5、其实 也可以通过 intent 的 flag 来实现intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一个新的 activity。此时如果该任务栈中已经有该 Activity,那么系统会把这个 Activity 上面的所有 Activity 干掉。其实相当于给 Activity 配置的启动模式为 SingleTop。

	Intent intent = new Intent(Intent.ACTION_MAIN);
				intent.addCategory(Intent.CATEGORY_HOME);
				intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
				startActivity(intent);
				android.os.Process.killProcess(Process.myPid());

Service 是否在 main thread 中执行, service 里面是否能执行耗时的操作?

默认情况,如果没有显示的指 servic 所运行的进程, Service 和 activity 是运行在当前 app 所在进程的 main  thread(UI 主线程)里面。


service 里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )


特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让 service 在另外的进程中执行
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>


IntentService


IntentService 是一种特殊的Service ,它继承了Service,并且有一个抽象类。

IntentService 封装了HandlerThread 和 Handler ,从源码可以看出

@Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
 @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }



Service 的缺点:

Service 不会专门启动一条单独的进程,Service 与它所在应用位于同一个进程中即UI线程;
Service 也不是专门一条新线程,因此不应该在 Service 中直接处理耗时的任务;


IntentService有以下特点:

(1)  它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents。

(2)  创建了一个工作队列,来逐个发送intent给onHandleIntent()。

(3)  不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。

(4)  默认实现的onBind()返回null

(5)  默认实现的onStartCommand()的目的是将intent插入到工作队列中


使用方法及详解 参考:Android IntentService完全解析 当Service遇到Handler


Service 里面可以弹Toast吗?

可以的。弹吐司有个条件就是得有一个 Context 上下文,而 Service 本身就是 Context 的子类,因此在 Service里面弹吐司是完全可以的。比如我们在 Service 中完成下载任务后可以弹一个吐司通知用户。


BroadCastReceiver

广播分两种:有序广播和无序广播。
内部通信实现机制:通过 Android 系统的 Binder 机制实现通信。

无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者,并无法终止广播 intent 的传播。

有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者 A,B,C,优先级是 A >B > C。那这个消息先传给 A,再传给 B,最后传给 C。每个接收者有权终止广播,比如 B 终止广播,C 就无法接收到。此外 A 接收到广播后可以对结果对象进行操作,当广播传给 B 时,B 可以从结果对象中取得 A 存入的数据。

有序广播的接收者们将按照事先生命的优先级依次接收,数越大优先级越高(取值范围:-1000~10000),优先级可以声明在<intent-filter android:priority="n".../>,也可以调用IntentFilter对象的setPriority设置。并且接收者可以终止传播(调用abortBroadcast()方法即可终止),一旦终止后面接收者就无法接受广播。另外,接受者可以将处理结果存入数据(可通过setResultExtras(Bundle)方法将数据存入Broadcast),当做Broadcast再传递给下一级接收者(可通过代码Bundle bundle = getResultExtras(true)获取上一级传递过来的数据)。

在通过 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)时

我们可以指定 resultReceiver 广播接收者,这个接收者我们可以认为是最终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的 onReceive 会被执行两次,第一次是正常的按照优先级顺序执行,第二次是作为最终接收者接收。如果比他优先级高的接收者终止了广播,那么他依然能接收到广播。


注册方法:

静态注册

        <!-- Required SDK核心功能 -->
        <receiver
            android:name="cn.jpush.android.service.PushReceiver"
            android:enabled="true"
            android:exported="false" >
            <intent-filter android:priority="1000" >
                <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" />
 				<!-- Required  显示通知栏 -->
                <category android:name="com.business" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
            <!-- Optional -->
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />

                <data android:scheme="com.business" />
            </intent-filter>
        </receiver>

动态注册:

localBroadcastManager = LocalBroadcastManager.getInstance(mContext);
		IntentFilter intentFilter = new IntentFilter();
		intentFilter.addAction(REFRESH_DATA);
		intentFilter.addAction(OFF_LINE_ACTION);
		broadcastReceiver = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				switch (intent.getAction()) {
				case REFRESH_DATA:
					String module = intent.getStringExtra("module");
					if("OrderFragment".equals(module)){
						Fragment fragment = mFragments[1];
						
						if(fragment != null && fragment instanceof OrderFragment){
							((OrderFragment)fragment).reload();
						}
					}
					break;
				case OFF_LINE_ACTION:
					offLine();
					break;
				default:
					break;
				}
				
			}
		};
		localBroadcastManager.registerReceiver(broadcastReceiver, intentFilter);

LocalBroadCastManager 注册本地广播接收器。


Android 的数据存储方式

File 存储

SharedPreference 存储

ContentProvider 存储
SQLiteDataBase 存储
网络存储


为什么要用 ContentProvider?它和 sql 的实现上有什么差别?

ContentProvider 屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的 uri 就可以了,ContentProvider 可以实现不同 app 之间共享。(安全、共享)


Sql 也有增删改查的方法,但是 sql 只能查询本应用下的数据库。而 ContentProvider 还可以去增删改查本地文件. xml 文件的读取等。


ContentProvider、ContentResolver、ContentObserver 之间的关系

ContentProvider 内容提供者,用于对外提供数据

ContentResolver 内容解析者,用于获取内容提供者提供的数据

ContentResolver.notifyChange(uri)发出消息

ContentObserver 内容监听器,可以监听数据的改变状态
ContentResolver.registerContentObserver()监听消息。


ListView 


ListView 如何提高其效率?
① 复用 ConvertView
② 自定义静态类 ViewHolder
③ 使用分页加载
④ 使用 WeakRefrence 引用 ImageView 对象

5、不要在getView中做耗时操作

6、onScrollListenerChanged 方法中判断,不滚动时开启线程做耗时操作。


在滚动状态发生改变的方法中,有三种状态:
手指按下移动的状态: SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动
惯性滚动(滑翔(flgin)状态): SCROLL_STATE_FLING: // 滑翔
静止状态: SCROLL_STATE_IDLE: // 静止


ListView 可以显示多种类型的条目吗?

这个当然可以的,ListView 显示的每个条目都是通过 baseAdapter 的 getView(int position, View convertView, ViewGroup parent)来展示的,理论上我们完全可以让每个条目都是不同类型的 view,除此之外 adapter 还提供了getViewTypeCount()和 getItemViewType(int position)两个方法。在 getView 方法中我们可以根据不同的viewtype 加载不同的布局文件。


ListView 如何定位到指定位置
可以通过 ListView 提供的 lv.setSelection(48);方法


当在 ScrollView 中如何嵌入 ListView

方法一:重写ListView

package 

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

public class DisableScrollListView extends ListView {

	public DisableScrollListView(Context context, AttributeSet attrs,
			int defStyleAttr, int defStyleRes) {
		super(context, attrs, defStyleAttr, defStyleRes);
	}

	public DisableScrollListView(Context context, AttributeSet attrs,
			int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}

	public DisableScrollListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public DisableScrollListView(Context context) {
		super(context);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
		super.onMeasure(widthMeasureSpec, expandSpec);
	}

	
	
}

方法二:手动测量ListView的高度。

lv = (ListView) findViewById(R.id.lv);
adapter = new MyAdapter();
lv.setAdapter(adapter);
getTotalHeightofListView(lv);


public static void getTotalHeightofListView(ListView listView) {
    ListAdapter mAdapter = listView.getAdapter(); 
    if (mAdapter == null) {
       return;
    }
    int totalHeight = 0;
    for (int i = 0; i <</SPAN> mAdapter.getCount(); i++) {
        View mView = mAdapter.getView(i, null, listView);
        mView.measure(
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        //mView.measure(0, 0);
        totalHeight += mView.getMeasuredHeight();
        Log.w("HEIGHT" + i, String.valueOf(totalHeight));
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (mAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();    
}


使用这个代码来获取listview的高度,需要注意一下几个问题:

1、listview的item的根布局一定要是LinearLayout;

2、调用这个方法需要在适配器数据加载更新之后:


ListView 中如何优化图片


图片的优化策略比较多
1、处理图片的方式:
如果 ListView 中自定义的 Item 中有涉及到大量图片的,一定要对图片进行细心的处理,因为图片占的内存是ListView 项中最头疼的,处理图片的方法大致有以下几种
①、不要直接拿路径就去循环 BitmapFactory.decodeFile;使用 Options 保存图片大小、不要加载图片到内存去。
②、对图片一定要经过边界压缩尤其是比较大的图片,如果你的图片是后台服务器处理好的那就不需要了
③、在 ListView 中取图片时也不要直接拿个路径去取图片,而是以 WeakReference(使用 WeakReference 代替强引用。比如可以使用 WeakReference mContextRef)、SoftReference、WeakHashMap 等的来存储图片信息。
④、在 getView 中做图片转换时,产生的中间变量一定及时释放

2、异步加载图片基本思想:
1)、 先从内存缓存中获取图片显示(内存缓冲)
2)、获取不到的话从 SD 卡里获取(SD 卡缓冲)
3)、都获取不到的话从网络下载图片并保存到 SD 卡同时加入内存并显示(视情况看是否要显示)
原理:
优化一:先从内存中加载,没有则开启线程从 SD 卡或网络中获取,这里注意从 SD 卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅。
优化二:于此同时,在 adapter 里有个 busy 变量,表示 listview 是否处于滑动状态,如果是滑动状态则仅从内存中获取图片,没有的话无需再开启线程去外存或网络获取图片。
优化三:ImageLoader 里的线程使用了线程池,从而避免了过多线程频繁创建和销毁,如果每次总是 new 一个线程去执行这是非常不可取的,好一点的用的 AsyncTask 类,其实内部也是用到了线程池。在从网络获取图片时,先是将其保存到 sd 卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存。


Java 中引用类型都有哪些
Java 中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

强引用(StrongReference)
这个就不多说,我们写代码天天在用的就是强引用。如果一个对象被被人拥有强引用,那么垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
Java 的对象是位于 heap 中的,heap 中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下
代码:
String abc=new String("abc"); //1
SoftReference<String> softRef=new SoftReference<String>(abc); //2
WeakReference<String> weakRef = new WeakReference<String>(abc); //3
abc=null; //4
softRef.clear();//5

第一行在 heap 堆中创建内容为“abc”的对象,并建立 abc 到该对象的强引用,该对象是强可及的。
第二行和第三行分别建立对 heap 中对象的软引用和弱引用,此时 heap 中的 abc 对象已经有 3 个引用,显然此时 abc 对象仍是强可及的。第四行之后 heap 中对象不再是强可及的,变成软可及的。第五行执行之后变成弱可及的。

软引用(SoftReference)
如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。

软引用是主要用于内存敏感的高速缓存。在 jvm 报告内存不足之前会清除所有的软引用,这样以来 gc 就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于 gc 的算法和 gc 运行时可用内存的大小。当 gc 决定要收集软引用时执行以下过程,以上面的 softRef 为例:

1 首先将 softRef 的 referent(abc)设置为 null,不再引用 heap 中的 new String("abc")对象。
2 将 heap 中的 new String("abc")对象设置为可结束的(finalizable)。
3 当 heap 中的 new String("abc")对象的 finalize()方法被运行而且该对象占用的内存被释放, softRef
被添加到它的 ReferenceQueue(如果有的话)中。
注意:对 ReferenceQueue 软引用和弱引用可以有可无,但是虚引用必须有。

被 Soft Reference 指到的对象,即使没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存不足且没有 Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误(OutOfMemoryError)。

弱引用(WeakReference)
如果一个对象只具有弱引用, 那该类就是可有可无的对象,因为只要该对象被 gc 扫描到了随时都会把它干掉。
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用(PhantomReference)

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
建立虚引用之后通过 get 方法返回结果始终为 null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是 get 方法返回结果为 null。先看一下和 gc 交互的过程再说一下他的作用。
1 不把 referent 设置为 null, 直接把 heap 中的 new String("abc")对象设置为可结束的(finalizable)。
2 与软引用和弱引用不同, 先把 PhantomRefrence 对象添加到它的 ReferenceQueue 中.然后在释放虚可及的对象。


在 Android 中如何调用 C 语言

当我们的 Java 需要调用C 语言的时候可以通过 JNI 的方式,Java Native Interface。Android提供了对JNI 的支持,因此我们在 Android 中可以使用 JNI 调用 C 语言。

在 Android 开发目录的 libs 目录下添加 xxx.so 文件,不过xxx.so文件需要放在对应的 CPU 架构名目录下,比如 armeabi,x86 等。
在Java 代码需要通过 System.loadLibrary(libName);加载 so 文件。同时 C 语言中的方法在 java 中必须以 native 关键字来声明。普通 Java 方法调用这个 native 方法接口,虚拟机内部自动调用 so 文件中对应的方法。


请介绍一下 NDK
1.NDK 是一系列工具的集合
NDK 提供了一系列的工具,帮助开发者快速开发 C(或 C++)的动态库,并能自动将 so 和 java 应用一起打包成 apk。NDK 集成了交叉编译器,并提供了相应的 mk 文件隔离 CPU、平台、ABI 等差异,开发人员只需要简单修改 mk 文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出 so。
2.NDK 提供了一份稳定、功能有限的 API 头文件声明
Google 明确声明该 API 是稳定的,在后续所有版本中都稳定支持当前发布的 API。从该版本的 NDK 中看出,这些 API 支持的功能非常有限,包含有:C 标准库(libc)、标准数学库(libm)、压缩库(libz)、Log 库(liblog)


Android常用的两种连接网络对象

Android 提供了 org.apache.http.HttpClientConnection  和  java.net.HttpURLConnection 两个连接网络对象。(从android5.0以后,google逐渐去除Apache ,要使用HttpClientConnection ,需要自己下载lib)


生成 JSON 对象和数组

学习: Post 请求参数 数据装载. 生成JSON
1)生成 JSON:
方法 1、创建一个 map,通过构造方法将 map 转换成 json 对象
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "zhangsan");
map.put("age", 24);
JSONObject json = new JSONObject(map);


方法 2、创建一个 json 对象,通过 put 方法添加数据
JSONObject json=new JSONObject();
json.put("name", "zhangsan");
json.put("age", 24);


2)生成 JSON 数组:
创建一个 list,通过构造方法将 list 转换成 json 对象
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("name", "zhangsan");
map1.put("age", 24);
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("name", "lisi");
map2.put("age", 25);
List<Map<String, Object>> list=new ArrayList<Map<String,Object>>();
list.add(map1);
list.add(map2);
JSONArray array=new JSONArray(list);
System.out.println(array.toString());


如何从网络上加载一个图片显示到界面
可以通过 BitmapFactory.decodeStream(inputStream);方法将图片转换为 bitmap,然后通过imageView.setImageBitmap(bitmap);将该图片设置到 ImageView 中。这是原生的方法,还可以使用第三方开源的工具来实现,比如使用 SmartImageView 作为 ImageView 控件,然后直接设置一个 url 地址即可。也可以使用xUtils 中的 BitmapUtils 工具。


Serializable 和 Parcelable 的区别

参考:Android 对象序列化 Serializable实现与Parcelabel实现的区别


序列化和反序列化原理

java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输、或者持久化存储到数据库或文件系统中;然后在需要的时候,可以读取字节流中的信息来重构一个相同的对象。序列化机制在java中有着广泛的应用,EJB、RMI、hessian等技术都是以此为基础的。 


so,序列化一般用于以下场景: 
1:永久性保存对象,保存对象的字节序列到本地文件或者数据库中
2:通过序列化以字节流的形式使对象在网络中进行传递和接收
3:通过序列化在进程间传递对象。

例子:

 Person  model

package com.tan.test.modle;

import java.io.Serializable;

public class Person implements Serializable{

	public String name;
	public String  sex;
	
	public Person(String name, String sex) {
		super();
		this.name = name;
		this.sex = sex;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
}

Test

package com.tan.test.main;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.tan.test.modle.Person;

public class Test {

	public static void main(String[] args) {
		// 创建一个对象
		Person people = new Person("张三", "男");
		try {
			// 实例化ObjectOutputStream对象
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\person.txt"));
			// 将对象写入文件
			oos.writeObject(people);
			oos.flush();
			oos.close();

			// 实例化ObjectInputStream对象
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\person.txt"));

			try {
				// 读取对象people,反序列化
				Person p = (Person) ois.readObject();
				System.out.println("姓名:" + p.getName());
				System.out.println("性别:" + p.getSex());
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


我们将对象序列化并输出。ObjectOutputStream能把Object输出成Byte流。

代码显示的18行-22行


我们将Byte流暂时存储到person.txt文件里

而在24行-34行,我们利用反序列化,根据字节流重建对象。

从上面代码中,我们不难看出,序列化和反序列化的俩个主要类:ObjectOutputStream、ObjectInputStream。


IntentFilter 匹配规则

参考:《Android 开发艺术探索》随手笔记——第一章Activity生命周期和启动模式


Fragment 跟 Activity 之间是如何传值的


当 Fragment 跟 Activity 绑定之后,在 Fragment 中可以直接通过 getActivity()方法获取到其绑定的 Activity对象,这样就可以调用 Activity 的方法了。

在 Activity 中可以通过如下方法获取到 Fragment 实例
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(tag);
Fragment fragment = fragmentManager.findFragmentById(id);

获取到 Fragment 之后就可以调用 Fragment 的方法。也就实现了通信功能。


Fragment 生命周期

参考: Activity 和 Fragment 生命周期 图形说明


如何对 Android 应用进行性能分析

DDMS 中 有traceviewheapallocation tracker 等工具都可以帮助我们分析应用的方法执行时间效率和内存使用情况

heap

heap 工具可以帮助我们检查代码中是否存在会造成内存泄漏的地方。
用heap 监测应用进程使用内存情况的步骤如下:

1. 启动 eclipse 后,切换到DDMS 透视图,并确认Devices 视图、Heap视图都是打开的;
2. 点击选中想要监测的进程,比如 system_process 进程;
3. 点击选中Devices 视图界面中最上方一排图标中的“Update Heap”图标;
4. 点击Heap 视图中的“CauseGC”按钮;
5. 此时在Heap 视图中就会看到当前选中的进程的内存使用量的详细情况。

说明:

a) 点击“Cause GC”按钮相当于向虚拟机请求了一次 gc 操作;
b) 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap 视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化;
c) 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。


如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值:Heap 视图中部有一个 Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在 data object 一行中有一列是“TotalSize”,其值就是当前进程中所有 Java 数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断:

a) 不断的操作当前应用,同时注意观察 data object 的Total Size 值;
b) 正常情况下Total Size 值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行 GC 的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;
c) 反之如果代码中存在没有释放对象引用的情况,则 data object 的 Total Size 值在每次 GC 后不会有明显的回落,随着操作次数的增多 Total Size 的值会越来越大,直到到达一个上限后导致进程被 kill 掉。
d) 此处以system_process 进程为例,在我的测试环境中 system_process 进程所占用的内存的 data object的 Total Size 正常情况下会稳定在 2.2~2.8 之间,而当其值超过 3.55 后进程就会被 kill。

总之,使用 DDMS 的Heap 视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。

 

allocationtracker

运行 DDMS,只需简单的选择应用进程并单击 Allocation tracker 标签,就会打开一个新的窗口,单击“Start Tracing”按钮;

然后,让应用运行你想分析的代码。运行完毕后,单击“Get Allocations”按钮,一个已分配对象的列表就会出现第一个表格中。

单击第一个表格中的任何一项,在表格二中就会出现导致该内存分配的栈跟踪信息。通过 allocation tracker,不仅知道分配了哪类对象,还可以知道在哪个线程、哪个类、哪个文件的哪一行。


TraceView  的使用分析方法 参考  《群英传》和《艺术开发探索》


第三方工具   MAT  的使用分析方法参考  《群英传》和《艺术开发探索》


Hierarchy Viewer (分析冗余布局)

位于 :sdk\tools\hierarchyviewer.bat


内存泄漏 OOM 等

1、资源释放问题
程序代码的问题,长期保持某些资源,如 ContextCursorIO流的引用,资源得不到释放造成内存泄露。

2、对象内存过大问题
保存了多个耗用内存过大的对象(如 BitmapXML文件),造成内存超出限制。

3static关键字的使用问题

static Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用 static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。
public class ClassName {
private static Context mContext;
//
省略
}
以上的代码是很危险的,如果将 Activity赋值到 mContext的话。那么即使该 Activity已经 onDestroy,但是由于仍有对象保存它的引用,因此该 Activity 依然不会被释放。

针对 static 的解决方案

应该尽量避免 static 成员变量引用资源耗费过多的实例,比如 Context
② Context
尽量使用 ApplicationContext ,因为 Application Context 的生命周期比较长,引用它不会出现内存泄露的问题。
使用 WeakReference 代替强引用。比如可以使用 WeakReference<Context> mContextRef;


4、线程导致内存溢出

线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码

public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyThread().start();
}
private class MyThread extends Thread{
@Override
public void run() {
super.run();
//do somthing
}
}

这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设 MyThread 的 run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一 般情况下当屏幕转换时会重新创建 Activity,按照我们的想法,老的 Activity 应该会被销毁才对,然而事实上并非如此。

由于我们的线程是 Activity 的内部类,所以MyThread 中保存了Activity 的一个引用,当MyThread 的 run函数没有结束时,MyThread 是不会被销毁的,因此它所引用的老的 Activity 也不会被销毁,因此就出现了内存泄露的问题。


有些人喜欢用 Android 提供的AsyncTask,但事实上AsyncTask 的问题更加严重,Thread 只有在 run 函数不结束时才出现这种内存泄露问题,然而 AsyncTask 内部的实现机制是运用了 ThreadPoolExcutor,该类产生的 Thread 对象的生命周期是不确定的,是应用程序无法控制的,因此如果 AsyncTask 作为 Activity 的内部类,就更容易出现内存泄露的问题。

针对这种线程导致的内存泄露问题的解决方案:

第一、将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。
第二、在线程内部采用弱引用保存 Context 引用。


3、如何避免 OOM 异常


1、图片过大导致 OOM

解决方法:
方法 1: 等比例缩小图片

方法 2:对图片采用软引用,及时地进行 recyle()操作

2、界面切换导致OOMOO

有时候我们会发现这样的问题,横竖屏切换 N 次后OOM 了

3 、查询数据库没有关闭游标

4、构造 Adapter时,没有使用缓存的 convertView

5、Bitmap对象不再使用时调用recycle()释放内


自定义组合控件

1. 声明一个 View 对象,继承相对布局,或者线性布局或者其他的 ViewGroup。
2. 在自定义的View 对象里面重写它的构造方法,在构造方法里面就把布局都初始化完毕。
3. 根据业务需求添加一些 api 方法,扩展自定义的组合控件;
4. 希望在布局文件里面可以自定义一些属性。
5. 声明自定义属性的命名空间。
xmlns:itheima=http://schemas.android.com/apk/res/com.itheima.mobilesafe

Itheima 是命名空间 类似android:text    res/ 后面跟包名
6. 在 res目录下的values 目录下创建attrs.xml 的文件声明我们写的属性。

7. 在布局文件中写自定义的属性。
8. 使用这些定义的属性。自定义 View 对象的构造方法里面有一个带两个参数的构造方法布局文件里面定义的属性都放在 AttributeSet attrs,获取那些定义的属性。


自定义VIew

参考: Android 自定义View (一)


描述一下View制流程

整个 View 树的绘图流程是在 ViewRoot.java 类(该类位于 Android 源码下面:

D:\AndroidSource_GB\AndroidSource_GB\frameworks\base\core\java\android\view)的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw),


AsyncTask 使用注意事项

1.异步任务的实例必须在 UI 线程中创建。
2.execute(Params... params)
方法必须在 UI线程中调用。
3.
onPreExecute() doInBackground(Params... params) onProgressUpdate(Progress.. values)onPostExecute(Result result)这几个方法。
4.
不能在 doInBackground(Params... params)中更改 UI组件的信息。
5.
一个任务实例只能执行一次,如果执行第二次将会抛出异常。
.





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值