首页 > 系统 > Android > 正文

Android 面试

2019-11-07 22:47:15
字体:
来源:转载
供稿:网友

转载出处:http://blog.csdn.net/u014745194/article/details/51344210#_Toc29600

1,如何优化listView中的数据?

第一,复用ConvertView,减少view的创建次数。

也是最普通的优化,就在MyAdapter类中的getView方法中,我们注意到,上面的写法每次需要一个View对象时,都是去重新inflate一个View出来返回去,没有实现View对象的复用,而实际上对于ListView而言,只需要保留能够显示的最大个数的view即可,其他新的view可以通过复用的方式使用消失的条目的view,而getView方法里也提供了一个参数:convertView,这个就代表着可以复用的view对象,当然这个对象也可能为空,当它为空的时候,表示该条目view第一次创建,所以我们需要inflate一个view出来

 

第二:创建一个ViewHolder类,减少findViewById的次数,优化内存。

基本思路就是在convertView为null的时候,我们不仅重新inflate出来一个view,并且还需要进行findviewbyId的查找工作,但是同时我们还需要获取一个ViewHolder类的对象,并将findviewById的结果赋值给ViewHolder中对应的成员变量。最后将holder对象与该view对象“绑”在一块。

当convertView不为null时,我们让view=converView,同时取出这个view对应的holder对象,就获得了这个view对象中的TextView组件,它就是holder中的成员变量,这样在复用的时候,我们就不需要再去findViewById了,只需要在最开始的时候进行数次查找工作就可以了。这里的关键在于如何将view与holder对象进行绑定,那么就需要用到两个方法:setTag和getTag方法

 

第三:进行分批加载。

比如说1000条新闻的List集合,我们一次加载20条,等到用户翻页到底部的时候,我们再添加下面的20条到List中,再使用Adapter刷新ListView,这样用户一次只需要等待20条数据的传输时间,不需要一次等待好几分钟把数据都加载完再在ListView上显示。其次这样也可以缓解很多条新闻一次加载进行产生OOM应用崩溃的情况。

 

第四:进行分页加载。

分批加载也不能完全解决问题,因为虽然我们在分批中一次只增加20条数据到List集合中,然后再刷新到ListView中去,假如有10万条数据,如果我们顺利读到最后这个List集合中还是会累积海量条数的数据,还是可能会造成OOM的情况,这时候我们就需要用到分页,比如说我们将这10万条数据分为1000页,每一页100条数据,每一页加载时都覆盖掉上一页中List集合中的内容,然后每一页内再使用分批加载,这样用户的体验就会相对好一些。

2,android中oom产生的原因,以及如何处理?

定义:OOM就是内存溢出,即Out Of Memory。也就是说内存占有量超过了所分配的最大,一般常见的有16M,24M,32M。

产生的两种原因:

第一种原因:应用中需要加载大对象,例如Bitmap,bitmap分辨率越高,所占用的内存就越大,这个是以2为指数级增长的。

第二种情况:长期保存某些大型资源的应用,资源得不到释放,导致Memory Leak . 也就是我们说的内存泄漏:

1 静态变量导致的Memory leak,慎用静态,因为它的生命周期太长。

2 不合理使用Context 导致的Memory leak。

3 非静态内部类导致的Memory leak

4 Drawable对象的回调隐含的Memory leak

5,cursor没有关闭或者file的流没有关

6使用9path代替大图

7,adapter中使用convertView

 

解决办法:

1,数据库的cursor没有关闭。 

2.构造adapter没有使用缓存contentView。 

3.调用registerReceiver()后未调用unregisterReceiver(). 

4.未关闭InputStream/OutputStream。 

5.Bitmap使用后未调用recycle()。 

6.Context泄漏。

7.static关键字 

3,如何避免oom的产生?

1,当我们需要显示大的bitmap对象或者较多的bitmap的时候,就需要进行压缩来防止OOM问题。我们可以通过设置BitmapFactory.Optiions的inJustDecodeBounds(边界压缩)属性为true,这样的话不会加载图片到内存中,但是会将图片的width和height属性读取出来,我们可以利用这个属性来对bitmap进行压缩。Options.inSampleSize 可以设置压缩比。

2,使用开源框架Universal-Image-Loader加载大图片,它中维护了一个LRUCache算法,它内部维护了一个LinkedHashMap和maxSize;每次添加(put)的时候会拿当前map中的size和maxSize进行比较; 如果超过maxSize,则移除最早添加的。先从内存中加载,如果没有,从文件中加载,如果都没有,可以开启一个子线程从网络中加载。一旦加载成功,取到bitmap,就直接把这个bitmap设置到对应的ImageView控件上。

4,listView加载大图片的时候,如何解决图片错位或闪退的问题?

原因:

listview 异步加载图片之所以错位的根本原因是重用了 convertView且有异步操作。

如果不重用 convertView 不会出现错位现象, 重用 convertView但没有异步操作也不会有问题。

原理:

在getView的时候,给ViewHolder类中的ImageView设置一个Tag,这个Tag中设置的是图片的url地址,然后在异步加载图片完成回调的方法中,使用findViewWithTag(String url)来找到ListView中position对应的ImagView,然后给该ImageView设置图片即可。

解决方案:

在getView的时候,给ViewHolder类中的ImageView设置一个Tag, 并预设一个图片。

5,Handler消息机制。

Message:消息;其中包含了消息ID,消息对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理

Handler:处理者;负责Message发送消息及处理。Handler通过与Looper进行沟通,从而使用Handler时,需要实现handlerMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等(主线程中才行)

MessageQueue:消息队列;用来存放Handler发送过来的消息,并按照FIFO(先入先出队列)规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等Looper的抽取。

Looper:消息泵,不断从MessageQueue中抽取Message执行。因此,一个线程中的MessageQueue需要一个Looper进行管理。Looper是当前线程创建的时候产生的(UI Thread即主线程是系统帮忙创建的Looper,而如果在子线程中,需要手动在创建线程后立即创建Looper[调用Looper.PRepare()方法])。也就是说,会在当前线程上绑定一个Looper对象。

Thread:线程;负责调度消息循环,即消息循环的执行场所。

 

具体流程:

0、准备数据和对象:

①、如果在主线程中处理message(即创建handler对象),那么如上所述,系统的Looper已经准备好了(当然,MessageQueue也初始化了),且其轮询方法loop已经开启。【系统的Handler准备好了,是用于处理系统的消息】。【Tips:如果是子线程中创建handler,就需要显式的调用Looper的方法prepare()和loop(),初始化Looper和开启轮询器】

②、通过Message.obtain()准备消息数据(实际是从消息池中取出的消息)

③、创建Handler对象,在其构造函数中,获取到Looper对象、MessageQueue对象(从Looper中获取的),并将handler作为message的标签设置到msg.target上

1、发送消息:sendMessage():通过Handler将消息发送给消息队列

2、给Message贴上handler的标签:在发送消息的时候,为handler发送的message贴上当前handler的标签

3、开启HandlerThread线程,执行run方法。

4、在HandlerThread类的run方法中开启轮询器进行轮询:调用Looper.loop()方法进行轮询消息队列的消息

【Tips:这两步需要再斟酌,个人认为这个类是自己手动创建的一个线程类,Looper的开启在上面已经详细说明了,这里是说自己手动创建线程(HandlerThread)的时候,才会在这个线程中进行Looper的轮询的】

5、在消息队列MessageQueue中enqueueMessage(Message msg, long when)方法里,对消息进行入列,即依据传入的时间进行消息入列(排队)

6、轮询消息:与此同时,Looper在不断的轮询消息队列

7、在Looper.loop()方法中,获取到MessageQueue对象后,从中取出消息(Message msg = queue.next())

8、分发消息:从消息队列中取出消息后,调用msg.target.dispatchMessage(msg);进行分发消息

9、将处理好的消息分发给指定的handler处理,即调用了handler的dispatchMessage(msg)方法进行分发消息。

10、在创建handler时,复写的handleMessage方法中进行消息的处理

11、回收消息:在消息使用完毕后,在Looper.loop()方法中调用msg.recycle(),将消息进行回收,即将消息的所有字段恢复为初始状态

6,android的四个组件以及他们的分别作用。

Activity:

Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持久化的事情,妥善管理生命周期以及一些跳转逻辑。

Activity之间通过Intent进行通信。在Intent的描述结构中,有两个最重要的部分:动作action和动作对应的数据data,还有一个类型category。

Activity的激活通过传递一个Intent 对象至Context.startActivity()或Activity.startActivityForResult()以载入(或指定新工作给)一个activity。相应的activity 可以通过调用getIntent() 方法来查看激活它的intent。如果它期望它所启动的那个activity 返回一个结果,它会以调用startActivityForResult()来取代startActivity()。比如说,如果它启动了另外一个Activity 以使用户挑选一张照片,它也许想知道哪张照片被选中了。结果将会被封装在一个Intent 对象中,并传递给发出调用的activity 的onActivityResult() 方法。

service:

后台服务于Activity,封装有一个完整的功能逻辑实现,接受上层指令,完成相关的事物,定义好需要接受的Intent提供同步和异步的接口。

服务不能自己运行,需要通过Context.startService()或Context.bindService()启动服务。通过startService()方法启动的服务与调用者没有关系,即使调用者关闭了,服务仍然运行想停止服务要调用Context.stopService(),此时系统会调用onDestory(),使用此方法启动时,服务首次启动系统先调用服务的onCreate()-->onStartCommond(),如果服务已经启动再次调用只会触发onStart()Commod()方法,销毁时调用onDestroy();方法

使用bindService()启动的服务与调用者绑定,只要调用者关闭服务就终止,使用此方法启动时,服务首次启动系统先调用服务的onCreate()-->onBind(),如果服务已经启动再次调用不会再触发这2个方法,调用者退出时系统会调用服务的onUnbind()-->onDestory(),想主动解除绑定可使用Contex.unbindService(),系统依次调用onUnbind()-->onDestory();

BroadCast Receiver:

接受一种或者多种Intent作触发事件,接受相关消息,做一些简单处理,转换成一条Notification,统一了Android的事件广播模型。例如:电量改变,开机,收发短信,屏幕解锁。

两种广播类型:

无序广播,通过Context.sendBroadcast(Intent myIntent)发送的。(新闻联播)

有序广播,通过Context.sendOrderedBroadcast(intent, receiverPermission)发送的,该方法第2个参数决定该广播的级别,级别数值是在-1000 到1000 之间, 值越大 , 发送的优先级越高;广播接收者接收广播时的级别(可通过intentfilter中的priority进行设置设为1000时优先级最高),同级别接收的先后是随机的, 再到级别低的收到广播,高级别的或同级别先接收到广播的可以通过abortBroadcast()方法截断广播使其他的接收者无法收到该广播。

注意:使用sendOrderedBroadcast(intent, null,new FinalReceiver(), null, 1, "习大大给每一个村民发了1000斤大米", null);中的第三个参数是一个最终/最后的广播接受者,并且它不用在清单文件中注册。好比钦差大臣。

监听广播步骤:

1, 写一个继承BroadCastReceiver的类,重写onReceive()方法,广播接收器仅在它执行这个方法时处于活跃状态。当onReceive()返回后,它即为失活状态。

2, 注册该广播接收者,注册有两种方法:代码动态注册和AndroidManifest文件中进行静态注册

静态注册:静态注册,注册的广播,下面的priority表示接收广播的级别"1000"为最高优先级

<receiverandroid:name=".SMSBroadcastReceiver" >  <intent-filterandroid:priority= "1000" >    <actionandroid:name="android.provider.Telephony.SMS_RECEIVED" /></intent-filter>

</receiver>

动态注册:动态注册,一般在Activity可交互时onResume()内注册BroadcastReceiver或者在activity创建时onCreate()方法中注册

IntentFilter intentFilter=new IntentFilter("android.provider.Telephony.SMS_RECEIVED");registerReceiver(mBatteryInfoReceiver ,intentFilter);//在onDestroy()方法中,反注册,否则会产生漏气的现象unregisterReceiver(receiver);

注意事项:

1.生命周期只有十秒左右,如果在 onReceive()内做超过十秒内的事情,就会报ANR(application No Response)程序无响应的错误信息,如果需要完成一项比较耗时的工作 ,应该通过发送 Intent给 Service,由Service来完成 . 这里不能使用子线程来解决 ,因为 BroadcastReceiver的生命周期很短 ,子线程可能还没有结束,BroadcastReceiver 就先结束了 .BroadcastReceiver 一旦结束, 此时 BroadcastReceiver 的所在进程很容易在系统需要内存时被优先杀死,因为它属于空进程( 没有任何活动组件的进程). 如果它的宿主进程被杀死, 那么正在工作的子线程也会被杀死. 所以采用子线程来解决是不可靠的

2. 动态注册广播接收器还有一个特点,就是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器就是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用

系统常见广播Intent,如开机启动、电池电量变化、时间改变等广播

Content Provider:

是Android提供的第三方应用数据的访问方案,可以派生Content Provider类,对外提供数据,可以像数据库一样进行选择排序,屏蔽内部数据的存储细节,向外提供统一的接口模型,大大简化上层应用,对数据的整合提供了更方便的途径。

1)ContentProvider:内容提供者

把一个应用程序的私有数据(如数据库)信息暴露给别的应用程序,让别的应用程序可以访问;在数据库中有对应的增删改查(crud)的方法,如果要让别的应用程序访问,需要有一个路径uri:

通过content:// 路径对外暴露,uri写法:content://主机名/表名

2)ContentResolver:内容解析者

根据内容提供者的路径uri,对数据进行操作(crud);

3)ContentObserver:内容观察者

可以理解成android系统包装好的回调,数据发生变化时,会执行回调中的方法;

ContentResolver发送通知,ContentObserver监听通知;

当A的数据发生变化的时候,A就会显示的通知一个内容观察者,然后通过消息把这个变化的uri,提供给内容解析者,进行对数据的操作。

 

四大组件一些总结:

内容提供者的激活:当接收到ContentResolver 发出的请求后,内容提供者被激活。而其它三种组件──activity、服务和广播接收器被一种叫做intent的异步消息所激活。所以,没有必要去显式的关闭这些组件。

Activity关闭:可以通过调用它的finish()方法来关闭一个activity,

关闭多个activity的解决方法:1,使用list保存activity实例,然后再一一干掉。简而言之,通过单例模式把每个Activity的引用添加到一个全局链表中,每次退出程序先调用链表中Activity的finish方法,再调用System.exit(0),确保一一干掉activity。代码如下:

public class SysApplicationextends Application {

private List<Activity> mList = new LinkedList<Activity>();

private static SysApplication instance;private SysApplication() {}public synchronized static SysApplication getInstance() {   

      if (null == instance) {         instance =new SysApplication();      }        

           return instance;      }// add Activity

public void addActivity(Activity activity) {      mList.add(activity);} public void exit() {       

        try {           

for (Activity activity : mList) {             

 if (activity != null)                  activity.finish();                  }         } catch (Exception e) {                 e.printStackTrace();         } finally {                System.exit(0);         }} public void onLowMemory() {   

      super.onLowMemory();      System.gc();} }在每个Activity的onCreate方法中添加类似代码:[java]public void onCreate(Bundle savedInstanceState) {             super.onCreate(savedInstanceState);                SysApplication.getInstance().addActivity(this);            }       在需要退出程序的时候,调用:

在activity的onDestroy()方法中调用以下代码:SysApplication.getInstance().exit();

 

第二种方式:2,使用广播关闭多个activity。

定义一个基类activity:

public class BaseActivity extends Activity {  

    protected BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {  

        @Override  

        public void onReceive(Context context, Intent intent) {  

            finish();  

        }  

    };  

      

    @Override  

    public void onResume() {  

        super.onResume();  

        // 在当前的activity中注册广播  ,添加广播的过滤器

        IntentFilter filter = new IntentFilter();  

        filter.addAction("ExitApp");  //接收到自定义的广播,在广播中finish掉activity

        this.registerReceiver(this.broadcastReceiver, filter);  

    }  

      

    @Override  

    protected void onDestroy() {  

        // TODO Auto-generated method stub  

        super.onDestroy();  

        this.unregisterReceiver(this.broadcastReceiver);    //反注册

    }  

}  

在你要关闭的Activity里添加myExit()方法,然后在要进行退出程序操作的地方调用myExit()方法就行。

public class Activity1 extends BaseActivity {  

    private Button btn1; 

    @Override  

    protected void onCreate(Bundle savedInstanceState) {  

        // TODO Auto-generated method stub  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.a1);  

          

        btn1 = (Button)findViewById(R.id.btn1);  

        btn1.setOnClickListener(new View.OnClickListener() {  

              

            @Override  

            public void onClick(View v) {  

                Intent i = new Intent(Activity1.this, Activity2.class);  

                startActivity(i);  

            }  

        });  

    }  

    /** 

     * 捕获手机物理菜单键 

     */  

    private long exitTime = 0;  

  

    @Override  

    public boolean onKeyDown(int keyCode, KeyEvent event) {  

        if(keyCode == KeyEvent.KEYCODE_BACK){//&& event.getAction() == KeyEvent.ACTION_DOWN  

            if((System.currentTimeMillis()-exitTime) > 2000){  

                Toast.makeText(getApplicationContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show();  

                exitTime = System.currentTimeMillis();  

            } else {  

                myExit();  

            }  

            return true;  

        }  

        return super.onKeyDown(keyCode, event);  

    }  

      

    protected void myExit() {  

        Intent intent = new Intent();  

        intent.setAction("ExitApp");  

        this.sendBroadcast(intent);  //发送自定义的广播

        super.finish();  

    }  

 

服务Service关闭:对于通过startService()方法启动的服务要调用Context.stopService()方法关闭服务,使用bindService()方法启动的服务要调用Contex.unbindService ()方法关闭服务

7,activity的生命周期与fragment的生命周期

Activity的生命周期:

onCreate()  onStart()  onResume()  onPause()  onStop()  onDestroy()   onRestart()

Fragment的生命周期:

1,onAttach():当fragment与activity绑定时调用(在这个方法中可以获得所在的activity)。

2,onCreate():当创建 fragment 时系统调用此方法

3,onCreateView():当activity要得到fragment的layout时,调用此方法,fragment在其中创建自己的layout(界面),初始化界面。

4,onActivityCreated():当activity的onCreated()方法返回后调用此方法,初始化数据。

5,onStart():开启Fragment

6,onResume():Fragment在一个运行中的activity中并且可见。

7,onPause():另一个activity处于最顶层,但是fragment所在的activity并没有被完全覆盖(顶层的activity是半透明的或不占据整个屏幕)。

8,onStop():Fragment不可见。可能是它所在的activity处于stoped状态或是fragment被删除并添加到后退栈中了。此状态的fragment仍然存在于内存中。

9,onDestroyView():当fragment的layout被销毁时被调用。

10,onDestroy():fragment被销毁时调用。

11,onDetach():fragment与activity解除绑定时被调用。

8,activity与fragment传递数据,  

1 ,Activity向Fragment传递数据    fragment.setArguments(bundle),那么Fragment通过getArguments();获得从activity中传递过来的值  2 ,Fragment向Activity传递数据:有两种方法   第一种,在Fragment内部定义一个回调接口.让包含该Fragment的Activity实现该接口,这样Fragment就可调用该回调方法, 将数据传给Activity。

第二种,Fragment中通过getActivity(),然后进行强制转化,调用Activity中的公有方法,(XXXXActivity)getActivity()).fun();注意使用这种方法,在使用之前,需要使用instanceof判断一下Activity的类型。

9,数据库重点

1)SQLite:

①SQLite概述:

Android平台中嵌入了一个关系型数据库SQLite,和其他数据库不同的是SQLite存储数据时不区分类型。SQLite是手机自带的数据库,每一个数据库就是一个xml文件,每一个XML文件有一张或多张表

②创建SQLite数据库步骤:

a、定义类继承SQLiteOpenHelper

SQLiteOpenHelper是用来打开数据库的一个工具,其中有创建onCreate()数据库和升级upGrade()数据库方法,

在获取数据库连接时,这些方法会自动执行,使用手机自带的SQLite数据库必须创建一个SQLiteOpenHelper子类,

实现其onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int)方法,

如果数据库不存在则创建,存在则检查数据库版本是不是最新,不是最新则升级数据库版本,维护其保持一个最佳的状态

b、获取数据库SQLiteDatabase对象

SQLiteOpenHelper. getWritableDatabase():获取写数据库,当可写时可获取读数据库

SQLiteOpenHelper. getReadableDatabase():获取读数据库

c、执行数据库增删改查操作

执行增删改操作,无返回结果集:

SQLiteDatabase.execSQL(String sql,Object[] params);

执行查询操作:返回Cursor结果集

SQLiteDatabase.rawQuery(String sql,String[] params);

或者执行CRUD操作:

SQLiteDatabase.insert();

SQLiteDatabase.update();

SQLiteDatabase.delete();

SQLiteDatabase.query();

d、关闭资源:(可选操作,每次获取数据库的时候数据库底层会自动关流,建议手动关闭)

SQLiteDatabase.close();

Cursor.close();

10,aidl进程间的通信机制。

1)什么是aidl?

aidl就是Android接口定义语言,即Android Interface Definition Language的缩写,是Google提供的一个规范。

2)aidl技术的作用:

(可以先阐述服务的两种开启方式再开始回答这个问题)

当服务调用者和服务不在同一个应用,此时服务调用者想通过远程绑定服务调用服务里面的方法就必须得通过aidl技术。

3)使用aidl技术的步骤:

①在服务中定义好方法

通过一个中间人来实现调用服务里面的方法,这个中间人就是接口。

而通过aidl技术可将服务应用中的接口和调用者应用中的接口变成同一个接口

②定义一个接口,并在接口中声明方法

③通过绑定服务的方法开启的服务会调用服务的onBind方法,而执行onBind方法会返回一个IBinder接口对象,

  而Binder是IBinder接口的实现类,故我们可以自定义一个内部类MyBinder继承Binder同时实现步骤②中定义好的接口,实现接口未实现的方法。

  此时MyBinder就相当于一个中间人,通过调用这个中间人的方法我们就可以间接的调用服务中的方法

④进入调用服务的应用的本地目录,将接口文件的后缀名改为aidl,同时将接口中的权限修饰符去掉(此时会在英语的gen目录生成相对应的.java文件);

  这样就保证两个应用中相同包相同声明的接口是同一个接口,进而可以通过该中间人接口远程调用服务里面的方法。

⑤将服务中自定义的内部类MyBinder改为继承Stub;

  将远程绑定服务调用服务方法的代码中ServiceConnection类的onServiceConnected方法返回的IBinder接口,

  通过Stub.asInterface(IBinder)方法强转成我们自定义的中间人接口(只有强转为和服务中自定义的接口类型才能调用服务中的方法)

 

或者以下5步操作:

1,把远程服务的方法抽取成一个单独的java接口文件

2,把java接口文件的后缀名改为aidl,去掉public(此时会在英语的gen目录生成相对应的PublicBusiness.java文件)

3,在自动生成的PublicBusiness.java文件中,有一个静态抽象类stub,它继承了binder类,实现了publicBusiness接口,这个类就是新的中间人。

4,把aidl文件复制粘贴到目标项目,注意aidl文件所在包名必须跟原项目中aidl所在包名一致

5,在目标项目中,强转中间人对象时,直接使用Stub.asInterface();

11.android中动画

动画的分类:

安卓中的动画分为三种,

一种是帧动画(Frame),像电影一样一帧一帧的播放。

一种是补间动画(Tweened):又分为四种动画类型,Alpha(透明)、Scale(缩放)、Translate(平移)和Rotate(旋转)

一种是属性动画:使用开源框架nineOldAndroids  

主要有两个类:ValueAnimator  和ViewPropertyAnimator

(valueAnimator不改变动画的值,仅仅把值传递给相应的控件)

nineOldAndroids:里面封装了属性动画

最大特点是:改变了View的位置

 

rotationBy:水平旋转

translationXBy:平移动画

alphaBy.透明动画

scaleBy缩放动画

 

setInterpolator();速度插值器,

overShootInterpolator()弹性的插值器

BoundInterpolator()球落地的插值器

cycleInterpolator(4)左右两边斗的插值器

 

使用开源项目PhotoView,来实现图片手势放大缩放效果,

12.Android下socket网络编程技术

1)网络编程概述:

①网络模型:

****OSI模型

应用层

表示层

会话层

传输层

网络层

数据连接层

物理层

****TCP/IP模型

应用层

传输层

网际层

主机至网络层

②网络通讯要素

IP地址

端口号

传输协议

③网络通讯前提:

**找到对方IP

**数据要发送到指定端口。为了标示不同的应用程序,所以给这些网络应用程序都用数字进行标示,这个标示就叫端口。

定义通信规则。这个规则称为通信协议,国际组织定义了通用协议TCP/IP

1)Android网络应用的概述:

Android完全支持JDK本身的TCP、UDP网络通信API,可以使用Socket、ServerSocket来建立基于TCP/IP协议的网络通信;

也可以使用DatagramSocket/Datagrampacket来建立基于UDP协议的网络通信。

同时Android还支持JDK提供的URL、URLConnection等网络通信的API。

2)TCP和UDP的区别:

①UDP协议:

面向无连接

每个数据报的大小在限制在64k内

因为是面向无连接,所以是不可靠协议

不需要建立连接,速度快

②TCP协议:

必须建立连接,形成传输数据的通道

在连接中可进行大数据量传输

通过三次握手完成连接,是可靠协议

必须建立连接,效率会稍低

注:三次握手:

第一次:我问你:在么?

第二次:你回答:在。

第三次:我反馈:哦,我知道你在。

3)Socket:

**Socket就是为网络服务提供的一种机制

**通信的两端都有Socket

**网络通信其实就是Socket间的通信

**数据在两个Socket间通过IO传输

**玩Socket主要就是记住流程,代码查文档就行

4)UDP(User Datagram Protocol):用户数据协议

①UDP概述:

需要DatagramSocket与DatagramPacket对象来实现UDP协议传输数据

UDP协议是一种面向无连接的协议。面向无连接的协议指的是正式通信前不必与对方先建立连接,不管对方连接状态就直接发送数据。

②UDP协议开发步骤:

**发送端:

建立DatagramSocket服务;

提供数据,并将数据封装到字节数组中;

创建DatagramPacket数据包,并把数据封装到包中,同时指定接收端IP和接收端口

通过Socket服务,利用send方法将数据包发送出去;

关闭DatagramSocket和DatagramPacket服务。

**接收端:

建立DatagramSocket服务,并监听一个端口;

定义一个字节数组和一个数据包,同时将数组封装进数据包;

通过DatagramPacket的receive方法,将接收的数据存入定义好的数据包;

通过DatagramPacke关闭的方法,获取发送数据包中的信息;

关闭DatagramSocket和DatagramPacket服务。

③UDP协议的Demo(必须掌握):

****发送端:

class UDPSend

{

public static void main(String[] args) throws Exception

{

DatagramSocket ds = new DatagramSocket();

byte[] buf = "这是UDP发送端".getBytes();

DatagramPacket dp = new DatagramPacket(

buf,buf.length, InetAddress.getByName("192.168.1.253"),   10000);

ds.send(dp);

ds.close();

}

}

****接收端

class UDPRece

{

public static void main(String[] args) throws Exception

{

DatagramSocket ds = new DatagramSocket(10000);

byte[] buf = new byte[1024];

DatagramPacket dp = new DatagramPacket(buf,buf.length);

ds.receive(dp);//将发送端发送的数据包接收到接收端的数据包中

String ip = dp.getAddress().getHosyAddress();//获取发送端的ip

String data = new String(dp.getData(),0,dp.getLength());//获取数据

int port = dp.getPort();//获取发送端的端口号

sop(ip+":"+data+":"+port);

ds.close();

}

}

注:协议的概念和作用:

什么是协议?

网络通信的一种规则

协议的作用?

保证通信两端实现网络通信

5)TCP/IP协议:Socket和ServerSocket

①基于TCP协议的网络通信概述:

TCP/IP通信协议是一种必须建立连接的可靠的网络通信协议。它在通信两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。

网络虚拟链路一旦建立,两端的程序就可以进行通信。

②TCP/IP协议开发步骤:

**客户端:

建立Socket服务,并指定要连接的主机和端口;

获取Socket流中的输出流OutputStream,将数据写入流中,通过网络发送给服务端;

获取Socket流中的输入流InputStream,获取服务端的反馈信息;

关闭资源。

**服务端:

建立ServerSocket服务,并监听一个端口;

通过ServerSocket服务的accept方法,获取Socket服务对象;

使用客户端对象的读取流获取客户端发送过来的数据;

通过客户端对象的写入流反馈信息给客户端;

关闭资源

③TCP/IP协议的一个Demo(必须要掌握!):

客户端:

class TCPClient

{

public static void main(String[] args)

{

Socket s = new Socket("192.168.1.253",10000);

OutputStream out = s.getOutputStream();

out.write("这是TCP发送的数据".getBytes());

s.close();

}

}

服务端:

class TCPServer

{

public static void main(String[] args)

{

ServerSocket ss = new ServerSocket(10000);

Socket s = ss.accept();

 

String ip = s.getInetAddress().getHostAddress();

sop(ip);

 

InputStream is = s.getInputStream();

byte[] buf = new byte[1024];

int len = is.read(buf);

sop(new String(buf,0,len));

s.close();

ss.close();

}

}

6)HTTP协议:

a、HTTP是Hyper Text Transfer Protocol的缩写

b、是由W3C制定和维护的。目前版本为1.0和1.1

c、是开发web的基石,非常地重要

d、1.0版本:是无状态的协议,即一次连接只响应一次请求,响应完了就关闭此次连接,要想再访问须重新建立连接。而连接都是比较耗资源的。

   1.1版本:是有状态的协议。即可以在一次网络连接基础上发出多次请求和得到多次的响应。当距离上次请求时间过长时,服务器会自动断掉连接,这就是超时机制。

①HTTP协议的组成:

请求部分:

请求行:

GET / HTTP/1.1  包含:请求方式GET请求的资源路径:/协议版本号:HTTP/1.1

请求方式。常用的有GET、POST

GET方式:默认方式。直接输入的网址。

表单数据出现在请求行中。url?username=abc&passWord=123

特点:不安全;有长度限制:<1k

POST方式:可以通过表单form method="post"设置

表单数据会出现在正文中。

特点:安全;没有长度限制

请求消息头:

请求正文:第一个空行之后的全部都是请求正文

响应部分:

响应行:

HTTP/1.1 200 OK    包含:协议版本号:HTTP/1.1响应码:200描述:OK

响应码:(实际用到的30个左右,其他都是W3C保留的)

描述:对响应码的描述

常用响应码:

200:一切正常

302/307:请求的资源路径变更了

304:资源没有被修改过

404:资源不存在,找不到资源

500:服务器程序有错

响应消息头:

响应正文:

第一个空行之后的全部都是响应正文,浏览器显示的就是正文中的内容

29Android下线程池

ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler)

corePoolSize: 核心线程数,能够同时执行的任务数量

maximumPoolSize:除去缓冲队列中等待的任务,最大能容纳的任务数(其实是包括了核心线程池数量)

keepAliveTime:超出workQueue的等待任务的存活时间

unit:时间单位

workQueue:阻塞等待线程的队列,一般使用new LinkedBlockingQueue<Runnable>()这个,如果不指定容量, 会一直往里边添加,没有限制,workQueue永远不会满;

threadFactory:创建线程的工厂,使用系统默认的类

handler:当任务数超过maximumPoolSize时,对任务的处理策略,默认策略是拒绝添加

 

执行流程:当线程数小于corePoolSize时,每添加一个任务,则立即开启线程执行

          当corePoolSize满的时候,后面添加的任务将放入缓冲队列workQueue等待;

  当workQueue也满的时候,看是否超过maximumPoolSize线程数,如果超过,默认拒绝执行

举例说明:

假如:corePoolSize=2,maximumPoolSize=3,workQueue容量为8(最大3+8个任务)

  最开始,执行的任务A,B,(2个)此时corePoolSize已用完,再次执行任务C(1个),则

C将被放入缓冲队列workQueue中等待着,如果后来又添加了7个任务,此时workQueue已满(2+1+7),

  则后面再来的任务将会和maximumPoolSize比较,由于maximumPoolSize为3,所以只能容纳1个了,

  因为有2个在corePoolSize中运行了,所以后面来的任务默认都会被拒绝。

13,Android当中如何去除标题(3种方法)

00001. 第一种:

在setContentView()方法的前面插入代码:?requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏 

第二种:在AndroidManifest.xml文件中定义去掉整个应用的标题栏 <android:theme="@android:style/Theme.NoTitleBar">不过有时候不需要去掉整个程序的应用,只想去掉一个的时候就在Activity中。

第三种方法:在res/values目录下面新建一个style.xml的文件这种方法是有经验的开发者最喜欢的方法,因为它把功能分开的;对于后期的维护非常方便。 <item name="android:windowNoTitle">true</item>    定义完了一个style,接下来就是在AndroidManifest.xml中使用了:android:theme="@style/concealTitle"> 

14.Android的屏幕适配方案(5种方法)

适配方式一:图片适配不同像素密度的手机加载工程资源文件(res)中不同资源图片

适配方式二:dimens.xml文件适配dimens.xml存在于工程资源(res)文件夹中不同values(如:value-1280x720、value-800x480)文件夹下,可用于指定控件大小,不同像素密度手机加载不同values文件夹下的dimens.xml文件

适配方式三:布局文件适配(工程比较浩大,不常用)不同分辨率的手机,加载不同的布局文件已达到适配效果。创建多个layout(如:layout-1280x720、layout-800x480)文件夹用于存放不同像素密度手机所需布局文件。

适配方式四:java代码适配通过android相应api获取当前手机的宽高像素值,按比例分配屏幕中控件的宽高以达到适配效果

适配方式五:权重适配通过android提供的(权重)剩余空间分配,已达到适配效果

屏幕适配原则:

①开发时单位尽量采用dip或者dp单位

②定义布局时尽量采用相对布局或者线性布局或者帧布局

③当屏幕过小或者内容过多时采用ScrollView控件将整个布局文件进行包裹

④点9图片

⑤在AndroidManifest.xml文件的<manifest>元素如下添加子元素

<supports-screens

android:largeScreens="true"

android:normalScreens="true" android:anyDensity="true"

android:smallScreens="true">

</supports-screens>

15 Android下的四种启动模式

1. standard   默认标准的启动模式, 每次startActivity都是创建一个新的activity的实例。

              适用于绝大多数情况。

2. singleTop  单一顶部,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。

              应用场景: 浏览器书签。 避免栈顶的activity被重复的创建,解决用户体验问题。

3. singleTask 单一任务栈 , activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,

 用 onNewIntent() 方法,并且清空当前activity任务栈上面所有的activity

              应用场景:浏览器activity, 整个任务栈只有一个实例,节约内存和cpu的目的

              注意: activity还是运行在当前应用程序的任务栈里面的。不会创建新的任务栈。

4. singleInstance  单一 实例模式

              单一实例,整个手机操作系统里面只有一个实例存在。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。

              应用场景:呼叫来电界面 InCallScreen

16 横竖屏切换时,activity的生命周期以及如何设置横屏或者竖屏。

如果不设置生命周期的过程时:onpause--onStop--onDestroy--onCreate--onStart--onResume

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次

2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

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

4,android:configChanges=”orientation|screenSize|keyboardHidden”

写死:android:screenOrientation=”landscape”横屏或者portrait竖屏

代码设置:setRequestOrientation(ActivityInfo.screen_orientation_landscape|portrait);

17 Android下主线程与子线程的理解

每一个进程有一个主线程(Main Thread),它的主要任务是处理与UI有关的事件(Event),所以又常称为UI线程。主线程可以诞生子线程(Child Thread),而且需要诞生子线程。其主要原因是:主线程(即UI线程)必须时时刻刻(标准为5秒内)去照顾UI的事件,以便快速响应使用者的要求。因此,UI线程没空闲去执行较为费时的函数(或工作),只好诞生子线程去执行较为费时的工作了。在Android平台里,UI线程与其子线程之分工是很明确的:

  子线程负责执行费时的工作。

  主线程负责UI的操作或事件;子线程不可以插手有关UI的事。

在同一进程里的主、子线程之间可以透过MessageQueue来互相沟通。当一个新进程新诞生时,会同时诞生一个主线程,也替主线程建立一个MessageQueue,以及负责管理MessageQueue的Looper类别的对象。子线程可以透过Handler对象而将Message对象丢(Post)到主线程的MessageQueue里,而主线程的Looper物件就会接到这个Message对象,并依据其内容而呼叫适当的函数来处理。

18.Java中jvm的内存分配:(针对速度而言:寄存器>栈>堆>其它)

A,堆:存放的都是new出来的对象,每个对象都包含一个与之对应的class信息。jvm只有一个堆区,被所有的线程所共享。

B,栈:每个线程包含一个栈区,存放基本类型的变量数据和对象的引用。每个栈中的数据是私有的,其它栈不能访问。

C,方法区:所有线程共享的内存区域,加载的是整个程序中永远唯一的元素,如class,static变量和常量。

19,一个对象在内存中产生过程:

Person p=new Person(“xiaoke”,25);

A,因为new用到了Person.class类,所以先找到Person.class类文件,并加载到内存的方法区中

B,执行该类的static代码块。如果有的话,给Person.class类进行初始化。

C,new Pseron()在堆内存中开辟空间,分配内存地址。

D,在堆内存中的建立对象的特有属性,并进行默认的初始化。

E,对属性进行显示初始化。

F,对对象进行构造代码块初始化。

G,对对象进行对应的构造函数初始化。

H,将堆内存地址赋给栈内存中P变量。

20,抽象类与接口的区别:

共性:他们都是不断抽取出来的抽象的概念

区别:

1,抽象类只能被单继承、接口可以被多实现,避免了单继承的局限性。

2.类与接口之间是实现关系,而且一个类在继承一个类的同时可以实现多个接口。

3.设计理念不同:抽象类是继承关系,是is a关系,接口是实现关系,是like a关系

4.抽象类是对一种事物的抽象,即对类的抽象。接口是对行为的抽象

5.接口中不能有静态代码块及静态方法,抽象类却可以有。

6.抽象类中的成员修饰符都是自定义的,而接口中的成员修饰符都是固定的(public static final)

7.抽象类可以定义抽象方法,供子类直接使用,也可以定义非抽象方法。它可以用于定义体系的基本共性的内容。

接口中只能定义抽象的方法(public abstract),需要子类全部实现,它主要用于功能的扩展。(java8中可以定义default修饰的方法)

21,接口中可以有构造函数吗?为什么?

答:不可以,原因:

1,构造器用于初始化成员变量,接口没有成员变量

2,类可以实现多个接口,若多个接口都有自己的构造器,则不好决定构造器的调用次序

3,构造器是属于类自己的,不能继承。因为是纯虚的,接口不需要构造器。

22,java中重载与重写的区别?

方法重载:

1),方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数或参数类型。重载是一个类中多态性的一种表现。2) Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。3) 重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回值类型作为重载函数的区分标准(原因:调用的不确定性)。

方法重写(覆盖):

1) 父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。2) 若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。3) 子类函数的访问修饰权限不能少于父类的;

23,请说出与线程同步以及线程调度相关的方法。 答: - wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; - sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常; - notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关; - notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

24,android内存的优化?

一、 Android的内存机制  Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的。C/C++中的内存机制是“谁污染,谁治理”,java的就比较人性化了,给我们请了一个专门的清洁工(GC)。  那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象(连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。二、Android的内存溢出Android的内存溢出是如何发生的?  Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。为什么会出现内存不够用的情况呢?我想原因主要有两个:

由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。

 

1,数据库的cursor没有关闭。 

2.构造adapter没有使用缓存contentview。 

3.调用registerReceiver()后未调用unregisterReceiver(). 

4.未关闭InputStream/OutputStream。 

5.Bitmap使用后未调用recycle()。 

6.Context泄漏。

7.static关键字 .

25,scrollView与ListView冲突的解决办法:

1,自定义可适应ScrollView的listView,重写onMeasure()方法,

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

00001.         int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,

00002.         MeasureSpec.AT_MOST);

00003.         super.onMeasure(widthMeasureSpec, expandSpec);

 

 

注意需要手动设置scrollView滚动到顶端,sv.smoothScrollTo(0,0);

2,手动设置ListView的高度

/**

 * 动态设置ListView的高度

 * @param listView

 */

public static void setListViewHeightBasedOnChildren(ListView listView) {

    if(listView == null) return;

 

    ListAdapter listAdapter = listView.getAdapter();

    if (listAdapter == null) {

        // pre-condition

        return;

    }

 

    int totalHeight = 0;

    for (int i = 0; i < listAdapter.getCount(); i++) {

        View listItem = listAdapter.getView(i, null, listView);

        listItem.measure(0, 0);

        totalHeight += listItem.getMeasuredHeight();

    }

 

    ViewGroup.LayoutParams params = listView.getLayoutParams();

    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));

    listView.setLayoutParams(params);

}

注意点:

  一是Adapter中getView方法返回的View的必须由LinearLayout组成,因为只有LinearLayout才有measure()方法,如果使用其他的布局如RelativeLayout,在调用listItem.measure(0, 0);时就会抛异常,因为除LinearLayout外的其他布局的这个方法就是直接抛异常的,没理由…。我最初使用的就是这个方法,但是因为子控件的顶层布局是RelativeLayout,所以一直报错,不得不放弃这个方法。  二是需要手动把ScrollView滚动至最顶端,sv.smoothScrollTo(0,0);

因为使用这个方法的话,默认在ScrollView顶端的项是ListView。

3,使用单个ListView取代scrollView中所有的内容:

原ListView上方数据和下方数据,都写进两个xml布局文件中:

 Java代码方面,需要自定义一个Adapter,在Adapter中的getView方法中进行position值的判断,根据position值来决定inflate哪个布局:

4,使用Linearlayout取代 ListView,为其加上BaseAdapter适配:

只需要自定义一个类继承自LinearLayout,为其加上对BaseAdapter的适配。

注意:首先在Activity中手动为LinearLayout添加子项控件,不过需要注意的是,在添加前需要调用其removeAllViews的方法,其次费时间在findViewById中

5,在scrollView中添加添加一个属性:android:fillViewPort=”true”;就可以让listview全屏,

问题是:虽然listView的数据全部显示了,可是listView不能滑动了。这是致命的伤...


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表