Android基础知识

Android基础知识

Android基础知识总结目录

  • Android基础知识
    • 五种布局:
    • Activity
      • Activity生命周期
      • 通过Acitivty的xml标签来改变任务栈的默认行为
      • Activity缓存方法。
        • 一、onSaveInstanceState (Bundle outState)
        • 二、onRestoreInstanceState (Bundle outState)
    • Fragment的生命周期和activity如何的一个关系
    • 为什么在Service中创建子线程而不是Activity中
    • Intent的使用方法,可以传递哪些数据类型。
    • Fragment生命周期
    • Service的两种启动方法,有什么区别
    • 广播(Broadcast Receiver)的两种动态注册和静态注册有什么区别。
    • 目前能否保证service不被杀死
      • Service设置成START_STICKY
      • 提升service优先级
      • 提升service进程优先级
      • onDestroy方法里重启service
      • 监听系统广播判断Service状态
      • 在JNI层,用C代码fork一个进程出来
      • 大招: 放一个像素在前台(手机QQ)
    • 动画有分类,各有什么特点?三种动画的区别
    • Android的数据存储形式。
    • Sqlite的基本操作。
    • 如何判断应用被强杀
    • 应用被强杀如何解决
    • Json有什么优劣势。
    • 怎样退出终止App
      • 一、容器式
      • 二、任务栈式
      • 三、广播式
      • 四、 进程式
      • 五、 任务管理器
      • 六、 广播+singletask
      • 七、singletask简易版
      • 八、懒人式
      • 九、退回系统桌面
      • 十、监听式
      • 十一、容器式升级版
      • 总结
    • Asset目录与res目录的区别。
    • Android怎么加速启动Activity。
    • Android内存优化方法:ListView优化,及时关闭资源,图片缓存等等。
    • Android中弱引用与软引用的应用场景。
    • Bitmap的四种属性,与每种属性队形的大小。
    • View与View Group分类。自定义View过程:onMeasure()、onLayout()、onDraw()。
    • Android长连接,怎么处理心跳机制。
    • View树绘制流程
    • 下拉刷新实现原理
    • 你用过什么框架,是否看过源码,是否知道底层原理。
    • Android5.0、6.0新特性。
      • Android5.0新特性:
      • Android6.0新特性
      • Android7.0新特性
    • Context区别
    • IntentService的使用场景与特点。
    • 图片缓存
    • 查看每个应用程序最高可用内存:
    • Gradle
    • 你是如何自学Android

五种布局:

FrameLayout 、 LinearLayout 、 AbsoluteLayout 、 RelativeLayout 、 TableLayout 全都继承自ViewGroup,各自特点及绘制效率对比。

  • FrameLayout(框架布局)

此布局是五种布局中最简单的布局,Android中并没有对child view的摆布进行控制,这个布局中所有的控件都会默认出现在视图的左上角,我们可以使用android:layout_margin,android:layout_gravity等属性去控制子控件相对布局的位置。

  • LinearLayout(线性布局)
    一行(或一列)只控制一个控件的线性布局,所以当有很多控件需要在一个界面中列出时,可以用LinearLayout布局。 此布局有一个需要格外注意的属性:android:orientation=“horizontal|vertical。

当android:orientation="horizontal时,说明你希望将水平方向的布局交给LinearLayout ,其子元素的android:layout_gravity=“right|left” 等控制水平方向的gravity值都是被忽略的,此时LinearLayout中的子元素都是默认的按照水平从左向右来排,我们可以用android:layout_gravity="top|bottom"等gravity值来控制垂直展示。
反之,可以知道 当android:orientation="vertical时,LinearLayout对其子元素展示上的的处理方式。

  • AbsoluteLayout(绝对布局)
    可以放置多个控件,并且可以自己定义控件的x,y位置

  • RelativeLayout(相对布局)
    这个布局也是相对自由的布局,Android 对该布局的child view的 水平layout& 垂直layout做了解析,由此我们可以FrameLayout的基础上使用标签或者Java代码对垂直方向 以及 水平方向 布局中的views进行任意的控制.
    相关属性:

    android:layout_centerInParent="true|false"
  android:layout_centerHorizontal="true|false"
  android:layout_alignParentRight="true|false"
  • TableLayout(表格布局)
    将子元素的位置分配到行或列中,一个TableLayout由许多的TableRow组成

Activity

Activity生命周期

启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。
Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。
Activity返回前台: onRestart()—>onStart()—>onResume(),再次回到运行状态。
Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity(此时这个Activity引用仍然处在任务栈中,只是这个时候引用指向的对象已经为null),若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()(将重新走一次Activity的初始化生命周期)
锁屏:onPause()->onStop()
解锁:onStart()->onResume()

Android基础知识_第1张图片

通过Acitivty的xml标签来改变任务栈的默认行为

使用android:launchMode="standard|singleInstance|singleTask|singleTop"来控制Acivity任务栈。
任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名.

  • standard : 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(onCreate()->onStart()->onResume())都会执行。
  • singleTop : 栈顶复用模式.这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的onNewIntent()方法会被回调.如果Activity已经存在但是不在栈顶,那么作用与standard模式一样.
  • singleTask: 栈内复用模式.创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈.然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,onNewIntent(),并且singleTask会清理在当前Activity上面的所有Activity.(clear top)
    singleInstance : 加强版的singleTask模式,这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了

Activity的堆栈管理以ActivityRecord为单位,所有的ActivityRecord都放在一个List里面.可以认为一个ActivityRecord就是一个Activity栈

Activity缓存方法。

有a、b两个Activity,当从a进入b之后一段时间,可能系统会把a回收,这时候按back,执行的不是a的onRestart而是onCreate方法,a被重新创建一次,这是a中的临时数据和状态可能就丢失了。

可以用Activity中的onSaveInstanceState()回调方法保存临时数据和状态,这个方法一定会在活动被回收之前调用。方法中有一个Bundle参数,putString()、putInt()等方法需要传入两个参数,一个键一个值。数据保存之后会在onCreate中恢复,onCreate也有一个Bundle类型的参数。

示例代码:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //这里,当Acivity第一次被创建的时候为空
        //所以我们需要判断一下
        if( savedInstanceState != null ){
            savedInstanceState.getString("anAnt");
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putString("anAnt","Android");

    }

一、onSaveInstanceState (Bundle outState)

当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。
注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些?通过重写一个activity的所有生命周期的onXXX方法,包括onSaveInstanceState和onRestoreInstanceState方法,我们可以清楚地知道当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState方法会在什么时候被执行,有这么几种情况:
1、当用户按下HOME键时。
这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
2、长按HOME键,选择运行其他的程序时。
3、按下电源按键(关闭屏幕显示)时。
4、从activity A中启动一个新的activity时。
5、屏幕方向切换时,例如从竖屏切换到横屏时。(如果不指定configchange属性) 在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行

总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。另外,需要注意的几点:

1.布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,app将不会存储它的状态。
2.由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。 既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了,如果你自己的派生类中有变量影响到UI,或你程序的行为,当然就要把这个变量也保存了,那么就需要自己实现,否则就不需要。
3.由于onSaveInstanceState()方法调用的不确定性,你应该只使用这个方法去记录activity的瞬间状态(UI的状态)。不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在onPause()方法中存储持久化数据(例如应该被存储到数据库中的数据)。
4.onSaveInstanceState()如果被调用,这个方法会在onStop()前被触发,但系统并不保证是否在onPause()之前或者之后触发

二、onRestoreInstanceState (Bundle outState)

至于onRestoreInstanceState方法,需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的,(本人注:我昨晚调试时就发 现原来不一定成对被调用的!)

onRestoreInstanceState被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被 调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行

另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。 还有onRestoreInstanceState在onstart之后执行。 至于这两个函数的使用,给出示范代码(留意自定义代码在调用super的前或后):

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
        savedInstanceState.putBoolean("MyBoolean", true);
        savedInstanceState.putDouble("myDouble", 1.9);
        savedInstanceState.putInt("MyInt", 1);
        savedInstanceState.putString("MyString", "Welcome back to Android");
        // etc.
        super.onSaveInstanceState(savedInstanceState);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
        double myDouble = savedInstanceState.getDouble("myDouble");
        int myInt = savedInstanceState.getInt("MyInt");
        String myString = savedInstanceState.getString("MyString");
}

Fragment的生命周期和activity如何的一个关系

Android基础知识_第2张图片

为什么在Service中创建子线程而不是Activity中

这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。

Intent的使用方法,可以传递哪些数据类型。

通过查询Intent/Bundle的API文档,我们可以获知,Intent/Bundle支持传递基本类型的数据和基本类型的数组数据,以及String/CharSequence类型的数据和String/CharSequence类型的数组数据。而对于其它类型的数据貌似无能为力,其实不然,我们可以在Intent/Bundle的API中看到Intent/Bundle还可以传递Parcelable(包裹化,邮包)和Serializable(序列化)类型的数据,以及它们的数组/列表数据。

所以要让非基本类型和非String/CharSequence类型的数据通过Intent/Bundle来进行传输,我们就需要在数据类型中实现Parcelable接口或是Serializable接口。

Fragment生命周期

Android基础知识_第3张图片
注意和Activity的相比的区别,按照执行顺序

  • onAttach(),onDetach()
  • onCreateView(),onDestroyView()

Service的两种启动方法,有什么区别

1.在Context中通过public boolean bindService(Intent service,ServiceConnection conn,int flags) 方法来进行Service与Context的关联并启动,并且Service的生命周期依附于Context(不求同时同分同秒生!但求同时同分同秒屎!!)。

2.通过public ComponentName startService(Intent service)方法去启动一个Service,此时Service的生命周期与启动它的Context无关。

3.要注意的是,whatever,都需要在xml里注册你的Service,就像这样:

<service
        android:name=".packnameName.youServiceName"
        android:enabled="true" />

广播(Broadcast Receiver)的两种动态注册和静态注册有什么区别。

  • 静态注册:在AndroidManifest.xml文件中进行注册,当App退出后,Receiver仍然可以接收到广播并且进行相应的处理
  • 动态注册:在代码中动态注册,当App退出后,也就没办法再接受广播了
    ContentProvider使用方法
    ContentProvider是用于将数据共享给其他应用。例如在同一部手机里面,有两个APP,第一个APP要访问第二个APP的数据。此时,第二个APP就需要设置ContentProvider。这样,第一个APP就能通过Uri访问第二个APP的数据。

第二部手机的设置,首先需要一个类来继承ContentProvider这个类,继承后需要实现onCreate,query,getType,insert,delete,update这个几个方法,如其名,其作用是给第一个APP调用的,至于第一个APP是如何调用的,下面会提到。
给出一个继承ContentProvider的例子:

1.先是提供的数据类型等数据的类。

package org.juetion.cp;  

import android.net.Uri;  
import android.provider.BaseColumns;  

/** 
 * 提供的数据类型等数据。 
 * Created by juetionke on 13-12-21. 
 */  
public class MyProviderMetaData {  


    public static final String AUTHORIY = "org.juetion.cp.MyContentProvider";  
    /** 
     * 数据库名称 
     */  
    public static final String DATABASE_NAME = "MyProvider.db";  
    /** 
     * 数据库版本 
     */  
    public static final int DATABASE_VERSION = 1;  
    /** 
     * 表名 
     */  
    public static final String USERS_TABLE_NAME = "users";  

    /** 
     * 继承了BaseColumns,所以已经有了_ID 
     */  
    public static final class UserTableMetaData implements BaseColumns {  
        /** 
         * 表名 
         */  
        public static final String TABLE_NAME = "users";  
        /** 
         * 访问该ContentProvider的URI 
         */  
        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORIY + "/users");  
        /** 
         * 该ContentProvider所返回的数据类型定义 
         */  
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/org.juetion.user";  
        public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/org.juetion.user";  
        /** 
         * 列名 
         */  
        public static final String USER_NAME = "name";  
        public static final String USER_AGE = "age";  
        /** 
         * 默认的排序方法 
         */  
        public static final String DEFAULT_SORT_ORDER = "_id desc";  
    }  
}  

2,继承ContentProvider的类:

package org.juetion.cp;  

import android.content.ContentProvider;  
import android.content.ContentUris;  
import android.content.ContentValues;  
import android.content.UriMatcher;  
import android.database.Cursor;  
import android.database.sqlite.SQLiteDatabase;  
import android.database.sqlite.SQLiteQueryBuilder;  
import android.net.Uri;  
import android.text.TextUtils;  
import android.util.Log;  

import org.juetion.sqlite3.DatabaseHelper;  

import java.util.HashMap;  

/** 
 * Created by juetionke on 13-12-21. 
 */  
public class MyContentProvider extends ContentProvider {  

    /** 
     * 定义规则 
     */  
    public static final UriMatcher uriMatcher;  
    public static final int USERS_COLLECTION = 1;//用于标记  
    public static final int USERS_SINGLE = 2;//用于标记  
    private DatabaseHelper databaseHelper;//这里的数据共享是共享Sqlite里的数据,当然,可以试用其他,如文本数据共享。  
    static {  
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);//试用一个没有规则的Uri。然后下面自己匹配。  
        uriMatcher.addURI(MyProviderMetaData.AUTHORIY,"/users",USERS_COLLECTION);//自己定义的规则,有点像路由器,是uri匹配的方案。  
        uriMatcher.addURI(MyProviderMetaData.AUTHORIY,"/users/#",USERS_SINGLE);//同上。  
    }  

    /** 
     * 为列定义别名 
     */  
    public static HashMap<String,String> usersMap;  
    static {  
        usersMap = new HashMap<String, String>();  
        usersMap.put(MyProviderMetaData.UserTableMetaData._ID, MyProviderMetaData.UserTableMetaData._ID);  
        usersMap.put(MyProviderMetaData.UserTableMetaData.USER_NAME, MyProviderMetaData.UserTableMetaData.USER_NAME);  
        usersMap.put(MyProviderMetaData.UserTableMetaData.USER_AGE, MyProviderMetaData.UserTableMetaData.USER_AGE);  
    }  


    @Override  
    public boolean onCreate() {  
        Log.i("juetion","onCreate");  
        databaseHelper = new DatabaseHelper(getContext(), MyProviderMetaData.DATABASE_NAME);//这里的实现,常见前篇关于Sqlite的文章。  
        return true;  
    }  

    @Override  
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {  
        Log.i("juetion","query");  
        SQLiteQueryBuilder sqLiteQueryBuilder = new SQLiteQueryBuilder();//写入查询条件,有点像Hibernate。  
        switch (uriMatcher.match(uri)) {//判断查询的是单个数据还是多个数据。  
            case USERS_SINGLE:  
                sqLiteQueryBuilder.setTables(MyProviderMetaData.UserTableMetaData.TABLE_NAME);//需要查询的表  
                sqLiteQueryBuilder.setProjectionMap(usersMap);//列的别名定义  
                sqLiteQueryBuilder.appendWhere(MyProviderMetaData.UserTableMetaData._ID + "=" + uri.getPathSegments().get(1));  
                //查询条件,uri.getPathSegments().get(1),getPathSegments是将内容根据/划分成list。  
                break;  
            case USERS_COLLECTION:  
                sqLiteQueryBuilder.setTables(MyProviderMetaData.UserTableMetaData.TABLE_NAME);  
                sqLiteQueryBuilder.setProjectionMap(usersMap);  
                break;  
        }  
        String orderBy;//判断sortOrder是否为空,加入默认。  
        if (TextUtils.isEmpty(sortOrder)) {  
            orderBy = MyProviderMetaData.UserTableMetaData.DEFAULT_SORT_ORDER;  
        } else {  
            orderBy = sortOrder;  
        }  
        SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase();  
        Cursor cursor = sqLiteQueryBuilder.query(sqLiteDatabase, projection, selection, selectionArgs, null, null, sortOrder);//可以使用下面的方法,不过此时sqLiteDatabase将会没有用。  
        //Cursor cursor = sqLiteDatabase.query(MyProviderMetaData.UserTableMetaData.TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy);  
        cursor.setNotificationUri(getContext().getContentResolver(),uri);  
        return cursor;  
    }  

    /** 
     * 根据传入的URI,返回URI说表示的数据类型 
     * @param uri 
     * @return 
     */  
    @Override  
    public String getType(Uri uri) {  
        Log.i("juetion","getType");  
        switch (uriMatcher.match(uri)) {//匹配uri的规则  
            case USERS_COLLECTION:  
                return MyProviderMetaData.UserTableMetaData.CONTENT_TYPE;  
            case USERS_SINGLE:  
                return MyProviderMetaData.UserTableMetaData.CONTENT_TYPE_ITEM;  
            default:  
                throw new IllegalArgumentException("Unknown URI" + uri);  
        }  
    }  

    @Override  
    public Uri insert(Uri uri, ContentValues values) {  
        Log.i("juetion","insert");  
        SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase();  
        long rowId = sqLiteDatabase.insert(MyProviderMetaData.UserTableMetaData.TABLE_NAME, null, values);  
        if (rowId > 0) {  
            Uri insertUserUri = ContentUris.withAppendedId(MyProviderMetaData.UserTableMetaData.CONTENT_URI, rowId);//简单来说就是字符串拼凑一下。只不过是uri专用的。  
            //通知监听器  
            getContext().getContentResolver().notifyChange(insertUserUri,null);  
            return insertUserUri;  
        }else  
            throw new IllegalArgumentException("Failed to insert row into" + uri);  
    }  

    @Override  
    public int delete(Uri uri, String selection, String[] selectionArgs) {  
        Log.i("juetion","delete");  
        return 0;  
    }  

    @Override  
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {  
        Log.i("juetion","update");  
        return 0;  
    }  
}  

这两个类里面都有的对应的注释,都能看懂吧?
还有重要的一点,再第二个APP的AndroidManifest.xml里面需要添加

<!-- provider name填写ContentProvider那个类的全称,authorities填写MyProviderMetaData里的AUTHORIY -->  
        <provider  
            android:authorities="org.juetion.cp.MyContentProvider"  
            android:name="org.juetion.cp.MyContentProvider"/>  

以上代码都是在第二个APP里面的。
--------我是可爱的分界线------------
以下代码是在第一个APP里面的。
关于使用。只需要将uri的string提供给第一个APP。
例如在第一个APP的Activity调用数据插入:

ContentValues contentValues = new ContentValues();  
                    contentValues.put("name","zhangsan");  
                    contentValues.put("age",19);  
                    Uri uri = getContentResolver().insert(Uri.parse("content://org.juetion.cp.MyContentProvider/users"),contentValues);  
                    Log.i("juetion", "insert uri-->" + uri.toString());  

例如在第一个APP的Activity调用数据的查询:

Cursor cursor = getContentResolver().query(Uri.parse("content://org.juetion.cp.MyContentProvider/users"),  
                            new String[]{"name", "age"},  
                            null, null, null);  
                    while (cursor.moveToNext()) {  
                        Log.i("juetion", cursor.getString(cursor.getColumnIndex("name")));  
                    }  

正如上面的例子,使用的时候是在Activity里面调用getContentResolver()的。

目前能否保证service不被杀死

Service设置成START_STICKY

  • kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样

提升service优先级

  • 在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。
  • 【结论】目前看来,priority这个属性貌似只适用于broadcast,对于Service来说可能无效

提升service进程优先级

  • Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收
  • 当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以在startForeground()使用startForeground()将service放到前台状态。这样在低内存时被kill的几率会低一些。
  • 【结论】如果在极度极度低内存的压力下,该service还是会被kill掉,并且不一定会restart()

onDestroy方法里重启service

  • service +broadcast 方式,就是当service走onDestory()的时候,发送一个自定义的广播,当收到广播的时候,重新启动service
  • 也可以直接在onDestroy()里startService
  • 【结论】当使用类似口口管家等第三方应用或是在setting里-应用-强制停止时,APP进程可能就直接被干掉了,onDestroy方法都进不来,所以还是无法保证

监听系统广播判断Service状态

  • 通过系统的一些广播,比如:手机重启、界面唤醒、应用状态改变等等监听并捕获到,然后判断我们的Service是否还存活,别忘记加权限
  • 【结论】这也能算是一种措施,不过感觉监听多了会导致Service很混乱,带来诸多不便

在JNI层,用C代码fork一个进程出来

  • 这样产生的进程,会被系统认为是两个不同的进程.但是Android5.0之后可能不行
    root之后放到system/app变成系统级应用

大招: 放一个像素在前台(手机QQ)

动画有分类,各有什么特点?三种动画的区别

  • tween 补间动画。通过指定View的初末状态和变化时间、方式,对View的内容完成一系列的图形变换来实现动画效果。 Alpha Scale Translate Rotate。

  • frame 帧动画 AnimationDrawable 控制 animation-list xml布局

  • PropertyAnimation 属性动画
    Android3.0之后增加了属性动画:属性动画可以定义在res-animator的资源文件中,它是用来在特定的时间修改对象的属性,例如背景颜色和alpha等等,常用的Java类有:ValueAnimator 、ObjectAnimator和AnimatorSet

Android的数据存储形式。

  • SQLite:SQLite是一个轻量级的数据库,支持基本的SQL语法,是常被采用的一种数据存储方式。 Android为此数据库提供了一个名为SQLiteDatabase的类,封装了一些操作数据库的api

  • SharedPreference: 除SQLite数据库外,另一种常用的数据存储方式,其本质就是一个xml文件,常用于存储较简单的参数设置。

  • File: 即常说的文件(I/O)存储方法,常用语存储大数量的数据,但是缺点是更新数据将是一件困难的事情。

  • ContentProvider: Android系统中能实现所有应用程序共享的一种数据存储方式,由于数据通常在各应用间的是互相私密的,所以此存储方式较少使用,但是其又是必不可少的一种存储方式。例如音频,视频,图片和通讯录,一般都可以采用此种方式进行存储。每个Content Provider都会对外提供一个公共的URI(包装成Uri对象),如果应用程序有数据需要共享时,就需要使用Content Provider为这些数据定义一个URI,然后其他的应用程序就通过Content Provider传入这个URI来对数据进行操作。

Sqlite的基本操作。

一、概述

SQLite是Android系统的核心数据存储服务之一,它是一个轻型的嵌入式数据库,占用非常少的资源却能提供很好很快的数据存取服务,许多大型的需要数据存储的android项目都有用到SQLite(也可以用于桌面应用程序)。

下面介绍一下SQLite的创建数据库、表的操作,以及基本的增删改查操作。

二、基本操作API简介

在Android中,SQLiteDatabase类提供了SQLite的底层API,但在使用SQLite数据库时,我们往往不会直接操作SQLiteDatabase这个类,而是自己创建一个继承自SQLitOpenHelper的子类来实现数据库操作。这样做的目的一是为了以后如果数据库升级不至于要改动太多代码,已实现封装;二则是为了我们使用更方便。

1、创建数据库和表

SQLiteOpenHelper是一个抽象类,在这个类里有两个抽象方法,OnCreate和OnUpgrade,前者用于第一次创建数据库,后者用于数据库升级,创建类DBServices如下:

public class DBServices extends SQLiteOpenHelper{

    final static int version = 1;
    final static String dbName = "plan";

    public DBServices(Context context){
        super(context,dbName,null,version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        //创建今日计划表
        String create_today_plan_sql = "CREATE TABLE [_today_plan] ("
                + "[_Date] varchar(10) not null," 
                + "[Item] varchar(200),"
                + "[Check] varchar(5) )";
        db.execSQL(create_today_plan_sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub

    }
}

示例里定义两个变量,一个是数据库的版本号,一个是数据库名。当Android应用运行时,SQLiteOpenHelper会先检查是否已经存在数据库,如果不存在,就创建数据库,然后打开数据库,最后调用OnCreate方法,所以我们需要再OnCreate中创建表(视图等);如果数据库已存在,而版本号比上次创建的数据库版本号高,就调用OnUpgrade,用于升级。

2、数据的增——insert

在创建了数据库和表之后,我们就可以给数据库和表添加数据了。
添加数据的操作和其他数据库一样,也是使用insert,只是SQLite的insert是函数,而且使用起来非常方便,下面是方法(属于上面的DBServices类,附录里有完整代码):

public void insert(String table, String nullColumnHack, ContentValues values){

        SQLiteDatabase db = this.getWritableDatabase();
        db.insert(table, nullColumnHack, values);
    }

参数说明:
table:表名,直接使用字符串指定;
nullColumnHack:指定null值的列,SQLite里不允许空行,使用这个参数可以指定一个列的值为null,当存入行为空时,这个列的值就被指定为null;
values:使用类似map键值对映射的数据结构ContentValues来指定插入的数据

添加数据示例:


   String[] args = {
                    today,
                    content,
                    Boolean.toString(checked)
            };
            String[] column = {
                    "[_Date]",
                    "[Item]",
                    "[Check]"
            };
            //数据库中添加数据
            ContentValues c = new ContentValues();
            for(int i=0;i<args.length;i++){
                c.put(column[i], args[i]);
            }
            dbServices.insert("_today_plan", null, c);

3、数据的删——delete

删除和添加一样,也是通过传入参数调用方法来实现,方法:

public void delete(String table , String whereClause , String[] whereArgs){
        SQLiteDatabase db = this.getWritableDatabase();
        db.delete(table, whereClause, whereArgs);
        Log.d("Delete",whereClause);
    }

参数说明:
table:表名;
whereClause:可选,指定删除条件,相当于SQL语句WHERE语句之后的类容,可通过?来指定参数;
whereArgs:当whereClause指定了?参数,这个字符串数组里就是?所代表的参数,个数应与?数一致;

删除数据示例:

String args[] ={
    today,
        content,
        Boolean.toString(checked)
};
dbServices.delete("_today_plan", "[_Date]=? and [Item]=? and [Check]=?"
,args);

4、数据的修改——update

修改与添加、删除相差不多,下面是update方法:

public void update(String table, ContentValues values,
        String whereClause, String[] whereArgs){
        SQLiteDatabase db = this.getWritableDatabase();
        db.update(table, values, whereClause, whereArgs);
    }

参数说明:
table:表名;
values:同上,是需要修改的列和值的映射集合;
whereClause:修改的行所需符合的条件;
whereArgs:指定条件里的参数;

修改数据示例:

String args[] ={
    today,
        content,
        Boolean.toString(!m)
};
ContentValues c = new ContentValues();
c.put("[Check]", Boolean.toString(m));
dbServices.update("_today_plan", c,"[_Date]=? and [StartTime]=? and [Item]=? and [Check]=?"
,args);

5、数据的查询——read

在这里就和前面有所不同了,读取数据所用的方法是直接执行查询语句,获取游标,然后通过游标来遍历数据库,方法如下:

public Cursor read(String sql ,String[] args){
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(sql, args);
        Log.d("Database",cursor.getColumnName(0));
        return cursor;
    }

方法说明:
请注意:在这里db获取的是只读的数据库(getReadableDatabase),而在上述三种操作里都是使用的可写数据库(getWritableDatabase);至于游标的用法此处就不再赘述,只需看一下API的名字就能掌握基本用法,但最后一定要记得将游标关闭(close)!

三、附录

附录了DBServices类,仅作参考

package com.plan;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DBServices extends SQLiteOpenHelper{

    final static int version = 1;
    final static String dbName = "plan";

    public DBServices(Context context){
        super(context,dbName,null,version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        //创建今日计划表
        String create_today_plan_sql = "CREATE TABLE [_today_plan] ("
                + "[_Date] varchar(10) not null," 
                + "[Item] varchar(200),"
                + "[Check] varchar(5) )";
        db.execSQL(create_today_plan_sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub

    }

    public Cursor read(String sql ,String[] args){
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(sql, args);
        Log.d("Database",cursor.getColumnName(0));
        return cursor;
    }

    public void insert(String table, String nullColumnHack, ContentValues values){

        SQLiteDatabase db = this.getWritableDatabase();
        db.insert(table, nullColumnHack, values);
    }

    public void delete(String table , String whereClause , String[] whereArgs){
        SQLiteDatabase db = this.getWritableDatabase();
        db.delete(table, whereClause, whereArgs);
        Log.d("Delete",whereClause);
    }

    public void update(String table, ContentValues values,
        String whereClause, String[] whereArgs){
        SQLiteDatabase db = this.getWritableDatabase();
        db.update(table, values, whereClause, whereArgs);
    }

}

有关SQLite的高级特性,如索引、视图以及触发器等,大家可以去看看SQlite的官方文档。

上述类在手机重启时,数据库会重新创建,原因是数据库没有关闭(但因为要提供游标,所以控制起来会比较困难),所以这里提出一个解决办法,就是数据库的操作(建表、增、删、改)均使用事务方式,示例如下:

db.beginTransaction();          //事务开始
//建表、增、删、改、查
db.setTransactionSuccessful();      //事务成功
db.endTransaction();                //提交事务

如何判断应用被强杀

在Application中定义一个static常量,赋值为-1,在欢迎界面改为0,如果被强杀,application重新初始化,在父类Activity判断该常量的值。

应用被强杀如何解决

如果在每一个Activity的onCreate里判断是否被强杀,冗余了,封装到Activity的父类中,如果被强杀,跳转回主界面,如果没有被强杀,执行Activity的初始化操作,给主界面传递intent参数,主界面会调用onNewIntent方法,在onNewIntent跳转到欢迎页面,重新来一遍流程。

Json有什么优劣势。

  • 优点:
    1.占带宽小(格式是压缩的)
  1. js通过eval()进行Json读取(便于客户端读取)
  2. JSON支持多种语言(c、c++、PHP等),便于服务端解析
  • 缺点:
  1. 没有XML格式这么推广的深入人心和使用广泛, 没有XML那么通用性
  2. JSON格式目前在Web Service中推广还属于初级阶段PS: 据说Google的Ajax是使用 JSON+模板 做的

怎样退出终止App

一、容器式

建立一个全局容器,把所有的Activity存储起来,退出时循环遍历finish所有Activity


import android.app.Activity;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by LY on 2017/6/19.
 * 随时随地的退出程序
 * 通过一个List来暂存活动,然后提供了一个addActivity()方法用于向List中添加一个活动,
 * 提供了一个removeActivity()方法用于从List中移除活动,
 * 最后提供了一个finishAllActivity()方法用于将List中存储的活动全部都销毁掉。
 *
 * 使用方式:
 * 在BaseActivity中的onCreate()方法中调用addActivity()方法,表明将当前正在创建的活动添加到活动管理器里。
 * 然后在BaseActivity中的onDestory()方法中调用removeActivity()方法,,表明将马上要销毁的活动从活动管理中移除。
 * 从此以后,不管你想在什么地方退出程序,只需要调用ActivityCollectorUtil.finishAllActivity()方法就可以了。
 */

public class ActivityCollectorUtil {

    public static List<Activity> activities = new ArrayList<Activity>();

    /**
     * 添加新的activity到集合中
     *
     * @param activity
     */
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }

    /**
     * 从集合中移除相应的activity
     *
     * @param activity
     */
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }

    /**
     * 关闭所有的活动(activity),即退出程序
     */
    public static void finishAllActivity() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
}
//使用方式
public class BaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollectorUtil.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollectorUtil.removeActivity(this);
    }
}

这种方法比较简单,是目前许多人常用的, 但是可以看到activityStack持有这Activity的强引用,也就是说当某个Activity异常退出时,activityStack没有即使释放掉引用,就会导致内存问题。

二、任务栈式

利用一个单例模式的Activity栈来管理所有Activity

  • 自定义 Application类,储存每一个Activity,并实现关闭所有Activity的操作
public class MyApplication extends Application {    
    private List<Activity> activityList = new LinkedList<Activity>();
    private static MyApplication instance;
    private MyApplication() {
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
    }
    
    //单例模式中获取唯一的MyApplication实例
    public static MyApplication getInstance() {
        if(null == instance) {
            instance = new MyApplication();
        }
        return instance;
    }
    
   //添加Activity到容器中
    public void addActivity(Activity activity)  {
        activityList.add(activity);
    }
    
   //遍历所有Activity并finish
    public void exit() {
        for(Activity activity:activityList) {
            activity.finish();
        }       
        activityList.clear();
    }
}
  • 在父类BaseActivity中添加继承子类Activity到栈中
public class BaseActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 添加Activity到堆栈
        MyApplication.getInstance().addActivity(this);
    }
    ...
}
  • 在需要结束所有Activity的时候调用exit方法
MyApplication.getInstance().exit();

存在问题:同方法一

三、广播式

通过在BaseActivity中注册一个广播,当退出时发送一个广播,finish退出。

/**
 * Created by LY on 2017/6/19.
 * 通过广播的方式退出应用
 */
public class BaseActivity extends Activity {

    private static final String EXITACTION = "action.exit";

    private ExitReceiver exitReceiver = new ExitReceiver();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        IntentFilter filter = new IntentFilter();
        filter.addAction(EXITACTION);
        registerReceiver(exitReceiver, filter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(exitReceiver);
    }

    class ExitReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            BaseActivity.this.finish();
        }

    }

}

发送广播退出app

public class MainActivity extends BaseActivity {
    private long exitTime = 0;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
// 物理返回键,双击退出
    @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(MainActivity.this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
                exitTime = System.currentTimeMillis();
            } else {
                //发送广播退出程序
                Intent intent = new Intent("com.xxx.BaseActivity");
                intent.putExtra(EXITACTION, 1);
                sendBroadcast(intent);
            }
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}

四、 进程式

  • android.os.Process.killProcess(android.os.Process.myPid());
    可以杀死当前应用活动的进程,这一操作将会把所有该进程内的资源(包括线程全部清理掉)。当然,由于ActivityManager时刻监听着进程,一旦发现进程被非正常Kill,它将会试图去重启这个进程。
  • System.exit(0);正常退出
    System.exit(1);//非正常退出
    Java中结束进程的方法,调用它将关闭当前的JVM虚拟机。

这两种都能达到同样的效果,但是在模拟器上都会弹出 Unfortunately , XXX has stopped 消息提示框,但确实能退出应用。部分真机直接失效,只能finish当前Activity(比如我手上这台小米note,国产的几款ROM fw层改动太多,使用这种方式需慎重) 。
KillProcess() 和 System.exit(),许多人都使用过,当你栈里只有一个Activity的时候,这个措施是行之有效的。但当关闭多个Activity的时候,栈里有多个Activity时,这两个方法就不起作用了。
因为通过杀进程方式退出,会被系统认为异常退出,会保存应用的一些状态信息比如Activity运行栈,然后会恢复这个应用。当恢复一个Android应用程序时,会先从栈里面移除异常的Activity,相当于Back键操作。

五、 任务管理器

系统将终止一切和这个程序包关联的,所有共享同一uid的process全部杀掉,还会停止相关的服务以及移除所有的Activity,并且会发送一个广播。

<uses-permission android:name="android.permission.RESTART_PACKAGES"/>
//注意在Android 2.2(API 8)之后,restartPackage方法已经过时
ActivityManager am = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
am.restartPackage(getPackageName());

但是在Android 2.2(API 8)之后,restartPackage方法已经过时,不可以将应用程序结束,需要使用ActivityManager类的 killBackgroundProcesses方法。

<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>  
ActivityManager am = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
am.killBackgroundProcesses(getPackageName());

使用时需要注意:该方法虽然可以立即杀死与指定包相关联的所有后台进程,但是这些进程如果在将来某一时刻需要使用,便会重新启动。

六、 广播+singletask

我们知道Activity有四种加载模式,而singleTask就是其中的一种,使用这个模式之后,当startActivity时,它先会在当前栈中查询是否存在Activity的实例,如果存在,则将其至于栈顶,并将其之上的所有Activity移除栈。我们打开一个app,首先是一个splash页面,然后会finish掉splash页面。跳转到主页。然后会在主页进行N次的跳转,期间会产生数量不定的Activity,有的被销毁,有的驻留在栈中,但是栈底永远是我们的HomeActivity。这样就让问题变得简单很多了。我们只需两步操作即可优雅的实现app的退出。

  • 在HomeActivity注册一个退出广播,和第二个广播式一样,但是这里只需要在HomeActivity一个页面注册即可。

  • 设置HomeActivity的启动模式为singleTask。

当我们需要退出的时候只需要startActivity(this,HomeActivity,class), 再发送一个退出广播。上面代码首先会把栈中HomeActivity之上的所有Activity移除出栈,然后接到广播finish自己。一切OK ! 没有弹框,不用考虑机型Rom适配。不会有内存问题。

七、singletask简易版

第五种注册广播的方式略显麻烦,有一种更简单的方式,思路也很简单,
* 设置MainActivity的加载模式为singleTask
* 重写MainActivity中的onNewIntent方法
* 需要退出时在Intent中添加退出的tag

//第一步,设置MainActivity的加载模式为singleTask
android:launchMode="singleTask"
//第二步,重写MainActivity的onNewIntent方法
    private static final String TAG_EXIT = "exit";

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (intent != null) {
            boolean isExit = intent.getBooleanExtra(TAG_EXIT, false);
            if (isExit) {
                this.finish();
            }
        }
    }

//第三步,调用退出方法。
/**退出程序*/
protected void exit() {
    // 这里使用clear + new task的方式清空整个任务栈,只保留新打开的Main页面
    // 然后Main页面接收到退出的标志位exit=true,finish自己,这样就关闭了全部页面
    Intent intent = new Intent(this, MainActivity.class);
    intent.putExtra("exit", true);
    startActivity(intent);
}

八、懒人式

这种方式更加简单,只需要如下两步操作
1、将MainActivity设置为singleTask
2、将退出出口放置在MainActivity
我们可以看到很多应用都是双击两次home键退出应用,就是基于这样的方式来实现的,这里在贴一下如何处理连续两次点击退出的源码

private boolean mIsExit;
@Override
    /**
     * 双击返回键退出
     */
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (mIsExit) {
                this.finish();

            } else {
                Toast.makeText(this, "再按一次退出", Toast.LENGTH_SHORT).show();
                mIsExit = true;
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mIsExit = false;
                    }
                }, 2000);
            }
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }

九、退回系统桌面

Android应用开发中,有一种场景,就是我们不希望用户直接按Back键退出Activity,而是希望应用隐藏到后台的假退出,类似于按Home键的效果。(例如QQ、微信等、音乐播放器类软件等)。

  • 方法一(java):
/**退出程序**/
protected void exit() {
    Intent startMain = new Intent(Intent.ACTION_MAIN);
    startMain.addCategory(Intent.CATEGORY_HOME);
    startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(startMain);
    System.exit(0);
}
  • 方法二(xml):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xxx.xxx"   
    android:versionCode="1"
    android:versionName="1.0.1">
  <application>     
    <activity
        android:name="com.xxx.xxx.MainActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:clearTaskOnLaunch="true"
        android:stateNotNeeded="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.HOME" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity> 
   ...
  </application>
</manifest>

十、监听式

从Android 4.0(API 14)开始,Application中多了一个可以设置全局监听Activity生命周期的方法:registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback),其中传入的参数 ActivityLifecycleCallbacks能得到全局所有Activity生命周期的回调,所以我们可以从Application中全局监听所有Activity并对其进行管理。

public class MyApplication extends Application {
    protected static Context context;   

    /**
     * 维护Activity 的list
     */
    private static List<Activity> mActivitys = Collections.synchronizedList(new LinkedList<Activity>());

    @Override
    public void onCreate() {
        super.onCreate();
        context = this.getApplicationContext();
        registerActivityListener();
    }

    public static Context getContext() {
        return context;
    }

    /**
     * @param activity 作用说明 :添加一个activity到管理里
     */
    public void pushActivity(Activity activity) {
        mActivitys.add(activity);
    }

    /**
     * @param activity 作用说明 :删除一个activity在管理里
     */
    public void popActivity(Activity activity) {
        mActivitys.remove(activity);
    }

    /**
     * get current Activity 获取当前Activity(栈中最后一个压入的)
     */
    public static Activity currentActivity() {
        if (mActivitys == null||mActivitys.isEmpty()) {
            return null;
        }
        Activity activity = mActivitys.get(mActivitys.size()-1);
        return activity;
    }

    /**
     * 结束当前Activity(栈中最后一个压入的)
     */
    public static void finishCurrentActivity() {
        if (mActivitys == null||mActivitys.isEmpty()) {
            return;
        }
        Activity activity = mActivitys.get(mActivitys.size()-1);
        finishActivity(activity);
    }

    /**
     * 结束指定的Activity
     */
    public static void finishActivity(Activity activity) {
        if (mActivitys == null||mActivitys.isEmpty()) {
            return;
        }
        if (activity != null) {
            mActivitys.remove(activity);
            activity.finish();
            activity = null;
        }
    }

    /**
     * 结束指定类名的Activity
     */
    public static void finishActivity(Class<?> cls) {
        if (mActivitys == null||mActivitys.isEmpty()) {
            return;
        }
        for (Activity activity : mActivitys) {
            if (activity.getClass().equals(cls)) {
                finishActivity(activity);
            }
        }
    }

    /**
     * 按照指定类名找到activity
     *
     * @param cls
     * @return
     */
    public static Activity findActivity(Class<?> cls) {
        Activity targetActivity = null;
        if (mActivitys != null) {
            for (Activity activity : mActivitys) {
                if (activity.getClass().equals(cls)) {
                    targetActivity = activity;
                    break;
                }
            }
        }
        return targetActivity;
    }

    /**
     * @return 作用说明 :获取当前最顶部activity的实例
     */
    public Activity getTopActivity() {
        Activity mBaseActivity = null;
        synchronized (mActivitys) {
            final int size = mActivitys.size() - 1;
            if (size < 0) {
                return null;
            }
            mBaseActivity = mActivitys.get(size);
        }
        return mBaseActivity;

    }

    /**
     * @return 作用说明 :获取当前最顶部的acitivity 名字
     */
    public String getTopActivityName() {
        Activity mBaseActivity = null;
        synchronized (mActivitys) {
            final int size = mActivitys.size() - 1;
            if (size < 0) {
                return null;
            }
            mBaseActivity = mActivitys.get(size);
        }
        return mBaseActivity.getClass().getName();
    }

    /**
     * 结束所有Activity
     */
    public static void finishAllActivity() {
        if (mActivitys == null) {
            return;
        }
        for (Activity activity : mActivitys) {
            activity.finish();
        }
        mActivitys.clear();
    }

    /**
     * 退出应用程序
     */
    public  static void appExit() {
        try {
            finishAllActivity();
        } catch (Exception e) {
        }
    }

    private void registerActivityListener() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                    /**
                     *  监听到 Activity创建事件 将该 Activity 加入list
                     */
                    pushActivity(activity);
                }

                @Override
                public void onActivityStarted(Activity activity) {

                }

                @Override
                public void onActivityResumed(Activity activity) {

                }

                @Override
                public void onActivityPaused(Activity activity) {

                }

                @Override
                public void onActivityStopped(Activity activity) {

                }

                @Override
                public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

                }

                @Override
                public void onActivityDestroyed(Activity activity) {
                    if (null==mActivitys||mActivitys.isEmpty()) {
                        return;
                    }
                    if (mActivitys.contains(activity)) {
                        /**
                         *  监听到 Activity销毁事件 将该Activity 从list中移除
                         */
                        popActivity(activity);
                    }
                }
            });
        }
    }

}

十一、容器式升级版

跟容器式思路一样,添加了软应用,解决内存泄露问题。

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class ActivityManager {

    /**
     * 注意,此处为单例模式,在BaseActivity中,只会返回一个对象。否则,每次被继承的BaseActivity在子Activity被创建的时候,
     * 都会得到一个新的对象。每个新的对象下,又会创建自己的HashMap,效果就是,一个HashMap只存了一个activity,
     * 显然与我们想要的结果不一样。
     * 所以,必须使用单例模式
     */
    
    private static ActivityManager activityManager;

    public static ActivityManager getActivityManager() {
        if (activityManager == null) {
            activityManager = new ActivityManager();
        }
        return activityManager;
    }

    //此处,可有可无。
    private ActivityManager() {
    }

    /**
     * task map,用于记录activity栈,方便退出程序(这里为了不影响系统回收activity,所以用软引用)
     */
    private final HashMap<String, SoftReference<Activity>> taskMap = new HashMap<String, SoftReference<Activity>>();

    /**
     * 往应用task map加入activity
     */
    public final void putActivity(Activity atv) {
        taskMap.put(atv.toString(), new SoftReference<Activity>(atv));
        Log.i("PutActivity", "" + atv);
    }

    /**
     * 往应用task map加入activity
     */
    public final void removeActivity(Activity atv) {
        taskMap.remove(atv.toString());
    }

    /**
     * 清除应用的task栈,如果程序正常运行这会导致应用退回到桌面
     */
    public final void exit() {
        for (Iterator<Entry<String, SoftReference<Activity>>> iterator = taskMap
                .entrySet().iterator(); iterator.hasNext();) {
            SoftReference<Activity> activityReference = iterator.next()
                    .getValue();
            Activity activity = activityReference.get();
            Log.i("ActivityList", "" + activity);
            if (activity != null) {
                activity.finish();
            }
        }
        taskMap.clear();
    }

}

创建自己的根activity,重写onCreate与onDestory

import android.app.Activity;
import android.os.Bundle;

public class BaseActivity extends Activity {

    private ActivityManager manager = ActivityManager.getActivityManager();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        manager.putActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        manager.removeActivity(this);
    }

    public void exit() {
        manager.exit();
    }

}

以后创建的activity都继承这个根activity就可以了,调用exit()退出应用如下:

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class FragmentsActivity extends BaseActivity {
    private Button exit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);

        exit = (Button) findViewById(R.id.exit);
        exit.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                // TODO Auto-generated method stub
                exit();
            }
        });

    }

}

总结

以上即是Android中常用的退出应用的方法,还有一个抛异常来退出app的我就没有写了,因为那个实在不怎么的。这些方法各有利弊,有时间可以自己尝试下。

Asset目录与res目录的区别。

res 目录下面有很多文件,例如 drawable,mipmap,raw 等。res 下面除了 raw 文件不会被压缩外,其余文件都会被压缩。同时 res目录下的文件可以通过R 文件访问。Asset 也是用来存储资源,但是 asset 文件内容只能通过路径或者 AssetManager 读取。

Android怎么加速启动Activity。

分两种情况,启动应用 和 普通Activity 启动

  • 应用 :Application 的构造方法,onCreate 方法中不要进行耗时操作,数据预读取(例如 init 数据) 放在异步中操作
  • 启动普通的Activity:A 启动B 时不要在 A 的 onPause 中执行耗时操作。因为 B 的 onResume 方法必须等待 A 的 onPause 执行完成后才能运行

Android内存优化方法:ListView优化,及时关闭资源,图片缓存等等。

Android中弱引用与软引用的应用场景。

Bitmap的四种属性,与每种属性队形的大小。

View与View Group分类。自定义View过程:onMeasure()、onLayout()、onDraw()。

如何自定义控件:

自定义属性的声明和获取

分析需要的自定义属性
在res/values/attrs.xml定义声明
在layout文件中进行使用
在View的构造方法中进行获取
测量onMeasure
布局onLayout(ViewGroup)

绘制onDraw

onTouchEvent

onInterceptTouchEvent(ViewGroup)

状态的恢复与保存

Android长连接,怎么处理心跳机制。

View树绘制流程

下拉刷新实现原理

你用过什么框架,是否看过源码,是否知道底层原理。

Android5.0、6.0新特性。

Android5.0新特性:

  • MaterialDesign设计风格
  • 支持多种设备
  • 支持64位ART虚拟机

Android6.0新特性

  • 大量漂亮流畅的动画
  • 支持快速充电的切换
  • 支持文件夹拖拽应用
  • 相机新增专业模式

Android7.0新特性

  • 分屏多任务
  • 增强的Java8语言模式
  • 夜间模式

Context区别

  • Activity和Service以及Application的Context是不一样的,Activity继承自ContextThemeWraper.其他的继承自ContextWrapper
  • 每一个Activity和Service以及Application的Context都是一个新的ContextImpl对象
  • getApplication()用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法,getApplicationContext()比getApplication()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。
  • Activity在创建的时候会new一个ContextImpl对象并在attach方法中关联它,Application和Service也差不多。ContextWrapper的方法内部都是转调ContextImpl的方法
  • 创建对话框传入Application的Context是不可以的
  • 尽管Application、Activity、Service都有自己的ContextImpl,并且每个ContextImpl都有自己的mResources成员,但是由于它们的mResources成员都来自于唯一的ResourcesManager实例,所以它们看似不同的mResources其实都指向的是同一块内存
  • Context的数量等于Activity的个数 + Service的个数 + 1,这个1为Application

IntentService的使用场景与特点。

IntentService是Service的子类,是一个异步的,会自动停止的服务,很好解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题
优点:

  • 一方面不需要自己去new Thread
  • 另一方面不需要考虑在什么时候关闭该Service

onStartCommand中回调了onStart,onStart中通过mServiceHandler发送消息到该handler的handleMessage中去。最后handleMessage中回调onHandleIntent(intent)。

图片缓存

查看每个应用程序最高可用内存:

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
    Log.d("TAG", "Max memory is " + maxMemory + "KB");

Gradle

构建工具、Groovy语法、Java

Jar包里面只有代码,aar里面不光有代码还包括代码还包括资源文件,比如 drawable 文件,xml 资源文件。对于一些不常变动的 Android Library,我们可以直接引用 aar,加快编译速度

你是如何自学Android

首先是看书和看视频敲代码,然后看大牛的博客,做一些项目,向github提交代码,觉得自己API掌握的不错之后,开始看进阶的书,以及看源码,看完源码学习到一些思想,开始自己造轮子,开始想代码的提升,比如设计模式,架构,重构等。

你可能感兴趣的:(Android)