深入理解MYSQL的MDL元数据锁

前言

好久没更新,主要是因为Inside君*近沉迷于一部动画片——《新葫芦娃兄弟》。终于抽得闲,完成了本篇关于MySQL MDL锁的深入分析与介绍。虽然之前有很多小伙伴分析过,但总感觉少了点什么,故花了点时间翻看了下源码。Inside君或许不是*牛掰的内核开发人员,但自认为应该是业界*会讲故事的码农,希望本篇能做到通俗易懂,因为MDL锁其实并不好理解。如果同学们还有问题,也可以直接看源码文件mdl.cc。

MDL锁与实现

MySQL5.5版本引入了MDL锁(metadata lock),用于解决或者保证DDL操作与DML操作之间的一致性。例如下面的这种情形:

会话1 会话2
BEGIN;
SELECT * FROM XXX
DROP TABLE XXX
SELECT * FROM XXX

若没有MDL锁的保护,则事务2可以直接执行DDL操作,并且导致事务1出错,5.1版本即是如此。5.5版本加入MDL锁就在于保护这种情况的发生,由于事务1开启了查询,那么获得了MDL锁,锁的模式为SHARED_READ,事务2要执行DDL,则需获得EXCLUSIVE锁,两者互斥,所以事务2需要等待。

InnoDB层已经有了IS、IX这样的意向锁,有同学觉得可以用来实现上述例子的并发控制。但由于MySQL是Server-Engine架构,所以MDL锁是在Server中实现。另外,MDL锁还能实现其他粒度级别的锁,比如全局锁、库级别的锁、表空间级别的锁,这是InnoDB存储引擎层不能直接实现的锁。

但与InnoDB锁的实现一样,MDL锁也是类似对一颗树的各个对象从上至下进行加锁(对树进行加锁具体见:《MySQL技术内幕:InnoDB存储引擎》)。但是MDL锁对象的层次更多,简单来看有如下的层次:

%title插图%num

 

上图中显示了*常见的4种MDL锁的对象,并且注明了常见的SQL语句会触发的锁。与InnoDB层类似的是,某些类型的MDL锁会从上往下一层层进行加锁。比如LOCK TABLE … WRITE这样的SQL语句,其首先会对GLOBAL级别加INTENTION_EXCLUSIVE锁,再对SCHEMA级别加INTENTION_EXCLUSIVE锁,*后对TABLE级别加SHARED_NO_READ_WRITE锁。

这里*令人意外的是还有COMMIT对象层次的锁,其实这主要用于XA事务中。比如分布式事务已经PREPARE成功,但是在XA COMMIT之前有其他会话执行了FLUSH TABLES WITH READ LOCK这样的操作,那么分布式事务的提交就需要等待。

除了上图标注的对象,其实还有TABLESPACE、FUNCTION、PROCEDURE、EVENT等其他对象类型,其实都是为了进行并发控制。只是这些在MySQL数据库中都不常用,故不再赘述(当然也是为了偷懒)。

GLOBAL=0,TABLESPACE,SCHEMA,TABLE,FUNCTION,PROCEDURE,TRIGGER,EVENT,COMMIT,USER_LEVEL_LOCK,LOCKING_SERVICE,NAMESPACE_END

 

目前MDL有如下锁模式,锁之间的兼容性可见源码mdl.cc:

锁模式 对应SQL
MDL_INTENTION_EXCLUSIVE GLOBAL对象、SCHEMA对象操作会加此锁
MDL_SHARED FLUSH TABLES with READ LOCK
MDL_SHARED_HIGH_PRIO 仅对MyISAM存储引擎有效
MDL_SHARED_READ SELECT查询
MDL_SHARED_WRITE DML语句
MDL_SHARED_WRITE_LOW_PRIO 仅对MyISAM存储引擎有效
MDL_SHARED_UPGRADABLE ALTER TABLE
MDL_SHARED_READ_ONLY LOCK xxx READ
MDL_SHARED_NO_WRITE FLUSH TABLES xxx,yyy,zzz READ
MDL_SHARED_NO_READ_WRITE FLUSH TABLE xxx WRITE
MDL_EXCLUSIVE ALTER TABLE xxx PARTITION BY …

MDL锁的性能与并发改进

讲到这同学们会发现MDL锁的开销并不比InnoDB层的行锁要小,而且这可能是一个更为密集的并发瓶颈。MySQL 5.6和5.5版本通常通过调整如下两个参数来进行并发调优:

  • metadata_locks_cache_size: MDL锁的缓存大小
  • metadata_locks_hash_instances:通过分片来提高并发度,与InnoDB AHI类似

MySQL 5.7 MDL锁的*大改进之处在于将MDL锁的机制通过lock free算法来实现,从而提高了在多核并发下数据库的整体性能提升。

MDL锁的诊断

MySQL 5.7版本之前并没有提供一个方便的途径来查看MDL锁,github上有一名为mysql-plugin-mdl-info的项目,通过插件的方式来查看,非常有想法的实现,大赞。好在官方也意识到了这个问题,于是在MySQL 5.7中的performance_schea库下新增了一张表metadata_locks,用其来查看MDL锁那是相当的方便:

不过默认PS并没有打开此功能,需要手工将wait/lock/metadata/sql/mdl监控给打开:

SELECT * FROM performance_schema.setup_instruments;

  UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME ='global_instrumentation';
  UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME ='wait/lock/metadata/sql/mdl';
  select * from performance_schema.metadata_locks\G

 

会话1

mysql> lock table xx read;
Query OK, 0 rows affected (0.21 sec)

会话2:

SELECT * FROM performance_schema.metadata_locks;

复制代码

mysql> SELECT * FROM performance_schema.metadata_locks\G;
*************************** 1. row ***************************
          OBJECT_TYPE: TABLE
        OBJECT_SCHEMA: performance_schema
          OBJECT_NAME: metadata_locks
OBJECT_INSTANCE_BEGIN: 240554768
            LOCK_TYPE: SHARED_READ
        LOCK_DURATION: TRANSACTION
          LOCK_STATUS: GRANTED
               SOURCE: sql_parse.cc:5927
      OWNER_THREAD_ID: 38
       OWNER_EVENT_ID: 10
1 row in set (0.00 sec)

ERROR:
No query specified

复制代码

复制代码

enum enum_mdl_type {
  /*
    An intention exclusive metadata lock. Used only for scoped locks.
    Owner of this type of lock can acquire upgradable exclusive locks on
    individual objects.
    Compatible with other IX locks, but is incompatible with scoped S and
    X locks.
  */
  MDL_INTENTION_EXCLUSIVE= 0,
  /*
    A shared metadata lock.
    To be used in cases when we are interested in object metadata only
    and there is no intention to access object data (e.g. for stored
    routines or during preparing prepared statements).
    We also mis-use this type of lock for open HANDLERs, since lock
    acquired by this statement has to be compatible with lock acquired
    by LOCK TABLES ... WRITE statement, i.e. SNRW (We can't get by by
    acquiring S lock at HANDLER ... OPEN time and upgrading it to SR
    lock for HANDLER ... READ as it doesn't solve problem with need
    to abort DML statements which wait on table level lock while having
    open HANDLER in the same connection).
    To avoid deadlock which may occur when SNRW lock is being upgraded to
    X lock for table on which there is an active S lock which is owned by
    thread which waits in its turn for table-level lock owned by thread
    performing upgrade we have to use thr_abort_locks_for_thread()
    facility in such situation.
    This problem does not arise for locks on stored routines as we don't
    use SNRW locks for them. It also does not arise when S locks are used
    during PREPARE calls as table-level locks are not acquired in this
    case.
  */
  MDL_SHARED,
  /*
    A high priority shared metadata lock.
    Used for cases when there is no intention to access object data (i.e.
    data in the table).
    "High priority" means that, unlike other shared locks, it is granted
    ignoring pending requests for exclusive locks. Intended for use in
    cases when we only need to access metadata and not data, e.g. when
    filling an INFORMATION_SCHEMA table.
    Since SH lock is compatible with SNRW lock, the connection that
    holds SH lock lock should not try to acquire any kind of table-level
    or row-level lock, as this can lead to a deadlock. Moreover, after
    acquiring SH lock, the connection should not wait for any other
    resource, as it might cause starvation for X locks and a potential
    deadlock during upgrade of SNW or SNRW to X lock (e.g. if the
    upgrading connection holds the resource that is being waited for).
  */
  MDL_SHARED_HIGH_PRIO,
  /*
    A shared metadata lock for cases when there is an intention to read data
    from table.
    A connection holding this kind of lock can read table metadata and read
    table data (after acquiring appropriate table and row-level locks).
    This means that one can only acquire TL_READ, TL_READ_NO_INSERT, and
    similar table-level locks on table if one holds SR MDL lock on it.
    To be used for tables in SELECTs, subqueries, and LOCK TABLE ...  READ
    statements.
  */
  MDL_SHARED_READ,
  /*
    A shared metadata lock for cases when there is an intention to modify
    (and not just read) data in the table.
    A connection holding SW lock can read table metadata and modify or read
    table data (after acquiring appropriate table and row-level locks).
    To be used for tables to be modified by INSERT, UPDATE, DELETE
    statements, but not LOCK TABLE ... WRITE or DDL). Also taken by
    SELECT ... FOR UPDATE.
  */
  MDL_SHARED_WRITE,
  /*
    A version of MDL_SHARED_WRITE lock which has lower priority than
    MDL_SHARED_READ_ONLY locks. Used by DML statements modifying
    tables and using the LOW_PRIORITY clause.
  */
  MDL_SHARED_WRITE_LOW_PRIO,
  /*
    An upgradable shared metadata lock which allows concurrent updates and
    reads of table data.
    A connection holding this kind of lock can read table metadata and read
    table data. It should not modify data as this lock is compatible with
    SRO locks.
    Can be upgraded to SNW, SNRW and X locks. Once SU lock is upgraded to X
    or SNRW lock data modification can happen freely.
    To be used for the first phase of ALTER TABLE.
  */
  MDL_SHARED_UPGRADABLE,
  /*
    A shared metadata lock for cases when we need to read data from table
    and block all concurrent modifications to it (for both data and metadata).
    Used by LOCK TABLES READ statement.
  */
  MDL_SHARED_READ_ONLY,
  /*
    An upgradable shared metadata lock which blocks all attempts to update
    table data, allowing reads.
    A connection holding this kind of lock can read table metadata and read
    table data.
    Can be upgraded to X metadata lock.
    Note, that since this type of lock is not compatible with SNRW or SW
    lock types, acquiring appropriate engine-level locks for reading
    (TL_READ* for MyISAM, shared row locks in InnoDB) should be
    contention-free.
    To be used for the first phase of ALTER TABLE, when copying data between
    tables, to allow concurrent SELECTs from the table, but not UPDATEs.
  */
  MDL_SHARED_NO_WRITE,
  /*
    An upgradable shared metadata lock which allows other connections
    to access table metadata, but not data.
    It blocks all attempts to read or update table data, while allowing
    INFORMATION_SCHEMA and SHOW queries.
    A connection holding this kind of lock can read table metadata modify and
    read table data.
    Can be upgraded to X metadata lock.
    To be used for LOCK TABLES WRITE statement.
    Not compatible with any other lock type except S and SH.
  */
  MDL_SHARED_NO_READ_WRITE,
  /*
    An exclusive metadata lock.
    A connection holding this lock can modify both table's metadata and data.
    No other type of metadata lock can be granted while this lock is held.
    To be used for CREATE/DROP/RENAME TABLE statements and for execution of
    certain phases of other DDL statements.
  */
  MDL_EXCLUSIVE,
  /* This should be the last !!! */
  MDL_TYPE_END};


/** Duration of metadata lock. */

enum enum_mdl_duration {
  /**
    Locks with statement duration are automatically released at the end
    of statement or transaction.
  */
  MDL_STATEMENT= 0,
  /**
    Locks with transaction duration are automatically released at the end
    of transaction.
  */
  MDL_TRANSACTION,
  /**
    Locks with explicit duration survive the end of statement and transaction.
    They have to be released explicitly by calling MDL_context::release_lock().
  */
  MDL_EXPLICIT,
  /* This should be the last ! */
  MDL_DURATION_END };


/** Maximal length of key for metadata locking subsystem. */
#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1)




/**
  Metadata lock object key.

  A lock is requested or granted based on a fully qualified name and type.
  E.g. They key for a table consists of  <0 (=table)> + <database> + <table name>.
  Elsewhere in the comments this triple will be referred to simply as "key"
  or "name".
*/

struct MDL_key
{
public:
#ifdef HAVE_PSI_INTERFACE
  static void init_psi_keys();
#endif

  /**
    Object namespaces.
    Sic: when adding a new member to this enum make sure to
    update m_namespace_to_wait_state_name array in mdl.cc!

    Different types of objects exist in different namespaces
     - GLOBAL is used for the global read lock.
     - TABLESPACE is for tablespaces.
     - SCHEMA is for schemas (aka databases).
     - TABLE is for tables and views.
     - FUNCTION is for stored functions.
     - PROCEDURE is for stored procedures.
     - TRIGGER is for triggers.
     - EVENT is for event scheduler events.
     - COMMIT is for enabling the global read lock to block commits.
     - USER_LEVEL_LOCK is for user-level locks.
     - LOCKING_SERVICE is for the name plugin RW-lock service
    Note that although there isn't metadata locking on triggers,
    it's necessary to have a separate namespace for them since
    MDL_key is also used outside of the MDL subsystem.
    Also note that requests waiting for user-level locks get special
    treatment - waiting is aborted if connection to client is lost.
  */
  enum enum_mdl_namespace { GLOBAL=0,
                            TABLESPACE,
                            SCHEMA,
                            TABLE,
                            FUNCTION,
                            PROCEDURE,
                            TRIGGER,
                            EVENT,
                            COMMIT,
                            USER_LEVEL_LOCK,
                            LOCKING_SERVICE,
                            /* This should be the last ! */
                            NAMESPACE_END 
};
 。。。

复制代码

 if (!MY_TEST(table_options & TL_OPTION_ALIAS))
  {
    MDL_REQUEST_INIT(& ptr->mdl_request,
                     MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type,
                     MDL_TRANSACTION);
 TABLE_LIST *ptr;

Android进程保活的一般套路

自己曾经也在这个问题上伤过脑经,前几日刚好有一个朋友说在做IM类的项目,问我进程保活如何处理比较恰当,决定去总结一下,网上搜索一下进程常驻的方案好多好多,但是很多的方案都是不靠谱的或者不是*好的,结合很多资料,今天总结一下Android进程保活的一些方案,都附有完整的实现源码,有些可能你已经知道,但是有些你可能是*次听说,(1像素Activity,前台服务,账号同步,Jobscheduler,相互唤醒,系统服务捆绑,如果你都了解了,请忽略)经过多方面的验证,Android系统中在没有白名单的情况下做一个任何情况下都不被杀死的应用是基本不可能的,但是我们可以做到我们的应用基本不被杀死,如果杀死可以马上满血复活,原谅我讲的特别含蓄,毕竟现在的技术防不胜防啊,不死应用还是可能的。

有几个问题需要思考,系统为什么会杀掉进程,杀的为什么是我的进程,这是按照什么标准来选择的,是一次性干掉多个进程,还是一个接着一个杀,保活套路一堆,如何进行进程保活才是比较恰当……如果这些问题你还还存在,或许这篇文章可以解答。

一、进程初步了解
每一个Android应用启动后至少对应一个进程,有的是多个进程,而且主流应用中多个进程的应用比例较大

1、如何查看进程解基本信息
对于任何一个进程,我们都可以通过adb shell ps|grep 的方式来查看它的基本信息

%title插图%num

2、进程划分
Android中的进程跟封建社会一样,分了三流九等,Android系统把进程的划为了如下几种(重要性从高到低),网上多位大神都详细总结过(备注:严格来说是划分了6种)。

2.1、前台进程(Foreground process)
场景:
– 某个进程持有一个正在与用户交互的Activity并且该Activity正处于resume的状态。
– 某个进程持有一个Service,并且该Service与用户正在交互的Activity绑定。
– 某个进程持有一个Service,并且该Service调用startForeground()方法使之位于前台运行。
– 某个进程持有一个Service,并且该Service正在执行它的某个生命周期回调方法,比如onCreate()、 onStart()或onDestroy()。
– 某个进程持有一个BroadcastReceiver,并且该BroadcastReceiver正在执行其onReceive()方法。

用户正在使用的程序,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等*端情况会杀死。

2.2、可见进程(Visible process)
场景:
– 拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
– 拥有绑定到可见(或前台)Activity 的 Service

用户正在使用,看得到,但是摸不着,没有覆盖到整个屏幕,只有屏幕的一部分可见进程不包含任何前台组件,一般系统也是不会杀死可见进程的,除非要在资源吃紧的情况下,要保持某个或多个前台进程存活

2.3、服务进程(Service process)
场景
– 某个进程中运行着一个Service且该Service是通过startService()启动的,与用户看见的界面没有直接关联。

在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死

2.4、后台进程(Background process)
场景:
– 在用户按了”back”或者”home”后,程序本身看不到了,但是其实还在运行的程序,比如Activity调用了onPause方法

系统可能随时终止它们,回收内存

2.5、空进程(Empty process)
场景:
– 某个进程不包含任何活跃的组件时该进程就会被置为空进程,完全没用,杀了它只有好处没坏处,*个干它!

3、内存阈值
上面是进程的分类,进程是怎么被杀的呢?系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。那这个不足怎么来规定呢,那就是内存阈值,我们可以使用cat /sys/module/lowmemorykiller/parameters/minfree来查看某个手机的内存阈值。

注意这些数字的单位是page. 1 page = 4 kb.上面的六个数字对应的就是(MB): 72,90,108,126,144,180,这些数字也就是对应的内存阀值,内存阈值在不同的手机上不一样,一旦低于该值,Android便开始按顺序关闭进程. 因此Android开始结束优先级*低的空进程,即当可用内存小于180MB(46080*4/1024)。

读到这里,你或许有一个疑问,假设现在内存不足,空进程都被杀光了,现在要杀后台进程,但是手机中后台进程很多,难道要一次性全部都清理掉?当然不是的,进程是有它的优先级的,这个优先级通过进程的adj值来反映,它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收,adj值定义在com.android.server.am.ProcessList类中,这个类路径是${android-sdk-path}\sources\android-23\com\android\server\am\ProcessList.java。oom_adj的值越小,进程的优先级越高,普通进程oom_adj值是大于等于0的,而系统进程oom_adj的值是小于0的,我们可以通过cat /proc/进程id/oom_adj可以看到当前进程的adj值。

看到adj值是0,0就代表这个进程是属于前台进程,我们按下Back键,将应用至于后台,再次查看

adj值变成了8,8代表这个进程是属于不活跃的进程,你可以尝试其他情况下,oom_adj值是多少,但是每个手机的厂商可能不一样,oom_adj值主要有这么几个,可以参考一下。

%title插图%num
备注:(上表的数字可能在不同系统会有一定的出入)

根据上面的adj值,其实系统在进程回收跟内存回收类似也是有一套严格的策略,可以自己去了解,大概是这个样子的,oom_adj越大,占用物理内存越多会被*先kill掉,OK,那么现在对于进程如何保活这个问题就转化成,如何降低oom_adj的值,以及如何使得我们应用占的内存*少。

一、进程保活方案
1、开启一个像素的Activity
据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播,我试过了,的确好使,如下。

public class MainActivity extends AppCompatActivity {

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

如果直接启动一个Activity,当我们按下back键返回桌面的时候,oom_adj的值是8,上面已经提到过,这个进程在资源不够的情况下是容易被回收的。现在造一个一个像素的Activity。

public class LiveActivity extends Activity {

public static final String TAG = LiveActivity.class.getSimpleName();

public static void actionToLiveActivity(Context pContext) {
Intent intent = new Intent(pContext, LiveActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pContext.startActivity(intent);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, “onCreate”);
setContentView(R.layout.activity_live);

Window window = getWindow();
//放在左上角
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams attributes = window.getAttributes();
//宽高设计为1个像素
attributes.width = 1;
attributes.height = 1;
//起始坐标
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);

ScreenManager.getInstance(this).setActivity(this);
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, “onDestroy”);
}
}

为了做的更隐藏,*好设置一下这个Activity的主题,当然也无所谓了

<style name=”LiveStyle”>
<item name=”android:windowIsTranslucent”>true</item>
<item name=”android:windowBackground”>@android:color/transparent</item>
<item name=”android:windowAnimationStyle”>@null</item>
<item name=”android:windowNoTitle”>true</item>
</style>

在屏幕关闭的时候把LiveActivity启动起来,在开屏的时候把LiveActivity 关闭掉,所以要监听系统锁屏广播,以接口的形式通知MainActivity启动或者关闭LiveActivity。

public class ScreenBroadcastListener {

private Context mContext;

private ScreenBroadcastReceiver mScreenReceiver;

private ScreenStateListener mListener;

public ScreenBroadcastListener(Context context) {
mContext = context.getApplicationContext();
mScreenReceiver = new ScreenBroadcastReceiver();
}

interface ScreenStateListener {

void onScreenOn();

void onScreenOff();
}

/**
* screen状态广播接收者
*/
private class ScreenBroadcastReceiver extends BroadcastReceiver {
private String action = null;

@Override
public void onReceive(Context context, Intent intent) {
action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
mListener.onScreenOn();
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
mListener.onScreenOff();
}
}
}

public void registerListener(ScreenStateListener listener) {
mListener = listener;
registerListener();
}

private void registerListener() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, filter);
}
}

public class ScreenManager {

private Context mContext;

private WeakReference<Activity> mActivityWref;

public static ScreenManager gDefualt;

public static ScreenManager getInstance(Context pContext) {
if (gDefualt == null) {
gDefualt = new ScreenManager(pContext.getApplicationContext());
}
return gDefualt;
}
private ScreenManager(Context pContext) {
this.mContext = pContext;
}

public void setActivity(Activity pActivity) {
mActivityWref = new WeakReference<Activity>(pActivity);
}

public void startActivity() {
LiveActivity.actionToLiveActivity(mContext);
}

public void finishActivity() {
//结束掉LiveActivity
if (mActivityWref != null) {
Activity activity = mActivityWref.get();
if (activity != null) {
activity.finish();
}
}
}
}

现在MainActivity改成如下

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this);
ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
@Override
public void onScreenOn() {
screenManager.finishActivity();
}

@Override
public void onScreenOff() {
screenManager.startActivity();
}
});
}
}

按下back之后,进行锁屏,现在测试一下oom_adj的值

果然将进程的优先级提高了。

但是还有一个问题,内存也是一个考虑的因素,内存越多会被*先kill掉,所以把上面的业务逻辑放到Service中,而Service是在另外一个 进程中,在MainActivity开启这个服务就行了,这样这个进程就更加的轻量,

public class LiveService extends Service {

public static void toLiveService(Context pContext){
Intent intent=new Intent(pContext,LiveService.class);
pContext.startService(intent);
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//屏幕关闭的时候启动一个1像素的Activity,开屏的时候关闭Activity
final ScreenManager screenManager = ScreenManager.getInstance(LiveService.this);
ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
@Override
public void onScreenOn() {
screenManager.finishActivity();
}
@Override
public void onScreenOff() {
screenManager.startActivity();
}
});
return START_REDELIVER_INTENT;
}
}

<service android:name=”.LiveService”
android:process=”:live_service”/>

OK,通过上面的操作,我们的应用就始终和前台进程是一样的优先级了,为了省电,系统检测到锁屏事件后一段时间内会杀死后台进程,如果采取这种方案,就可以避免了这个问题。但是还是有被杀掉的可能,所以我们还需要做双进程守护,关于双进程守护,比较适合的就是aidl的那种方式,但是这个不是完全的靠谱,原理是A进程死的时候,B还在活着,B可以将A进程拉起来,反之,B进程死的时候,A还活着,A可以将B拉起来。所以双进程守护的前提是,系统杀进程只能一个个的去杀,如果一次性杀两个,这种方法也是不OK的。

事实上
那么我们先来看看Android5.0以下的源码,ActivityManagerService是如何关闭在应用退出后清理内存的

Process.killProcessQuiet(pid);

应用退出后,ActivityManagerService就把主进程给杀死了,但是,在Android5.0以后,ActivityManagerService却是这样处理的:

Process.killProcessQuiet(app.pid);
Process.killProcessGroup(app.info.uid, app.pid);

在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了。所以在Android5.0以后的手机应用在进程被杀死后,要采用其他方案。

2、前台服务
这种大部分人都了解,据说这个微信也用过的进程保活方案,移步微信Android客户端后台保活经验分享,这方案实际利用了Android前台service的漏洞。
原理如下
对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除。

public class KeepLiveService extends Service {

public static final int NOTIFICATION_ID=0x11;

public KeepLiveService() {
}

@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException(“Not yet implemented”);
}

@Override
public void onCreate() {
super.onCreate();
//API 18以下,直接发送Notification并将其置为前台
if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
startForeground(NOTIFICATION_ID, new Notification());
} else {
//API 18以上,发送Notification并将其置为前台后,启动InnerService
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, InnerService.class));
}
}

public class InnerService extends Service{
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//发送与KeepLiveService中ID相同的Notification,然后将其取消并取消自己的前台显示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
stopSelf();
}
},100);

}
}
}

在没有采取前台服务之前,启动应用,oom_adj值是0,按下返回键之后,变成9(不同ROM可能不一样)

在采取前台服务之后,启动应用,oom_adj值是0,按下返回键之后,变成2(不同ROM可能不一样),确实进程的优先级有所提高。

3、相互唤醒
相互唤醒的意思就是,假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。这个完全有可能的。此外,开机,网络切换、拍照、拍视频时候,利用系统产生的广播也能唤醒app,不过Android N已经将这三种广播取消了。

如果应用想保活,要是QQ,微信愿意救你也行,有多少手机上没有QQ,微信呢?或者像友盟,信鸽这种推送SDK,也存在唤醒app的功能。
拉活方法

4、JobSheduler
JobSheduler是作为进程死后复活的一种手段,native进程方式*大缺点是费电, Native 进程费电的原因是感知主进程是否存活有两种实现方式,在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。其次5.0以上系统不支持。 但是JobSheduler可以替代在Android5.0以上native进程方式,这种方式即使用户强制关闭,也能被拉起来,亲测可行。

JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
@Override
public void onCreate() {
super.onCreate();
startJobSheduler();
}

public void startJobSheduler() {
try {
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
builder.setPeriodic(5);
builder.setPersisted(true);
JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
} catch (Exception ex) {
ex.printStackTrace();
}
}

@Override
public boolean onStartJob(JobParameters jobParameters) {
return false;
}

@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}

5、粘性服务&与系统服务捆绑
这个是系统自带的,onStartCommand方法必须具有一个整形的返回值,这个整形的返回值用来告诉系统在服务启动完毕后,如果被Kill,系统将如何操作,这种方案虽然可以,但是在某些情况or某些定制ROM上可能失效,我认为可以多做一种保保守方案。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}

START_STICKY
如果系统在onStartCommand返回后被销毁,系统将会重新创建服务并依次调用onCreate和onStartCommand(注意:根据测试Android2.3.3以下版本只会调用onCreate根本不会调用onStartCommand,Android4.0可以办到),这种相当于服务又重新启动恢复到之前的状态了)。

START_NOT_STICKY
如果系统在onStartCommand返回后被销毁,如果返回该值,则在执行完onStartCommand方法后如果Service被杀掉系统将不会重启该服务。

START_REDELIVER_INTENT
START_STICKY的兼容版本,不同的是其不保证服务被杀后一定能重启。

相比与粘性服务与系统服务捆绑更厉害一点,这个来自爱哥的研究,这里说的系统服务很好理解,比如NotificationListenerService,NotificationListenerService就是一个监听通知的服务,只要手机收到了通知,NotificationListenerService都能监听到,即时用户把进程杀死,也能重启,所以说要是把这个服务放到我们的进程之中,那么就可以呵呵了

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class LiveService extends NotificationListenerService {

public LiveService() {

}

@Override
public void onNotificationPosted(StatusBarNotification sbn) {
}

@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
}
}

但是这种方式需要权限

<service
android:name=”.LiveService”
android:permission=”android.permission.BIND_NOTIFICATION_LISTENER_SERVICE”>
<intent-filter>
<action android:name=”android.service.notification.NotificationListenerService” />
</intent-filter>
</service>

所以你的应用要是有消息推送的话,那么可以用这种方式去欺骗用户。

结束:
听说账号同步唤醒APP这种机制很不错,用户强制停止都杀不起创建一个账号并设置同步器,创建周期同步,系统会自动调用同步器,这样就能激活我们的APP,局限是国产机会修改*短同步周期(魅蓝NOTE2长达30分钟),并且需要联网才能使用。在国内各大ROM”欣欣向荣”的大背景下,关于进程保活,不加入白名单,我也很想知道有没有一个应用永活的方案,这种方案性能好,不费电,或许做不到,或许有牛人可以,但是,通过上面几种措施,在*大部分的机型下,*大部分用户手机中,我们的进程寿命确实得到了提高。

Android适配不同分辨率手机屏幕的开发

我们经常会遇见这样的场景,做一款简单的App,在自己的手机上设计了layout的xml文件,UI做的还挺好看,挺合适的。但是换了一个手机就会发现变的巨丑,可能只是集中在局部(原来的分辨率低,新的机器分辨率高),或者是手机屏幕放不下了(原来的分辨率高,心的机器分辨率低)。

解决办法就是:

我们可以在res文件路径下新建适配不同分辨率的手机的layout文件,与layout同级

%title插图%num

命名为layout-分辨率,请注意大数在前,所以一般就是高x宽,举例layout-2244×1080(HUAWEI P20分辨率)。
在新建的文件下存放的资源和layout下是一致的,不过需要自己做适配,结合不同的显示比例来调整里面的资源文件。手机会根据自己的分辨率来选择不同的layout资源文件,不需要自己在写代码选择,比较智能。

注1:在一个xml资源文件做了修改之后,记得刚更新到所有的layout文件中,否则换个测试机就会出现空指针异常,很无脑的bug,别问我为什么,因为我犯过。

注2:对不不同分辨率的适配,还有其他的解决办法,可能会觉着这样加了很多的文件,会增大安装包,但是就我的测试来看,这样的*美观的,因为我们针对不同的分辨率做了不同的适配,可以做很多微调(只关注常用机型,很冷门的可以放弃,机器找不到合适自己的就会委屈一下去选择相似分辨率的或者是直接用layout,不会报错,只会丑 -_-! )

android 手机屏幕密度等级和屏幕逻辑尺寸

在 android 开发中常常会使用到手机屏幕密度和屏幕逻辑尺寸来进行屏幕适配,这里就列出常见手机的屏幕参数列表:

%title插图%num

像素密度等级:是 rom 厂商设定的值,一般是取实际屏幕密度*接近的屏幕密度等级,但是也可以自主设定,目前 android sdk 中支持的等级有 ldpi、mdpi、tvdpi、hdpi、xhdpi、xxhdpi、xxxhdpi

等级像素密度:像素密度等级对应的像素密度

逻辑像素密度:是 rom 厂商设定的值,一般是取实际屏幕密度*接近的屏幕密度等级对应的值,但是也可以自主设定,通过系统 api,getResources().getDisplayMetrics().densityDpi 可以获取到该值

像素:就是屏幕的实际像素单元个数

尺寸:就是屏幕的实际尺寸大小

逻辑尺寸:实际像素数*160/逻辑像素密度,这也是 px 转 dp 的公式

真实像素密度:利用勾股定理算对角线上像素数/对角线尺寸

dp,dp 翻译过来叫做设备无关像素,对于真实像素密度等于像素密度等级上的逻辑值的设备,1dp = 1/160 inch,对于不等于逻辑值的设备,比如上述表格第二个设备,180.27 != 160 这个差值 android 操作系统会进行等比缩放来弥补。比如在这个设备上用的 160dp 到*后真正在屏幕上用多少个像素表示呢,这其实经过了 2 个步骤:
dp 转程序中的 px,按照 dp 转 px 的公式,在该设备上 160dp=160px,160dp 和我们程序里面用 160px 完全等价
程序中的 px 转显示屏上的 px,程序中160px 在该设备显示屏上用的是 180 px。160px -> 180px 这个过程是android操作系统自动完成的,我们不需要关心

Android 万能适配方案和UI屏幕适配 不同分辨率 *全面 *易懂的

屏幕尺寸
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米(下面有图文介绍)

比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等

 

屏幕分辨率

屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素*横向像素,如1960*1080。

屏幕像素密度

屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。

当设备的物理尺寸存在差异的时候,dp就显得无能为力了。为4.3寸屏幕准备的UI,运行在5.0寸的屏幕上,很可能在右侧和下侧存在大量的空白。而5.0寸的UI运行到4.3寸的设备上,很可能显示不下。
一句话,总结下,dp能够让同一数值在不同的分辨率展示出大致相同的尺寸大小。但是当设备的尺寸差异较大的时候,就无能为力了。适配的问题还需要我们自己去做,于是我们可能会这么做:

%title插图%num

上述代码片段来自网络,也就是说,我们为了优质的用户体验,依然需要去针对不同的dpi设置,编写多套数值文件。

可以看出,dp并没有能解决适配问题。下面看百分比。

 

我们再来看看一些适配的tips

多用match_parent
多用weight
自定义view解决
其实上述3点tip,归根结底还是利用百分比,match_parent相当于100%参考父控件;weight即按比例分配;自定义view无非是因为里面多数尺寸是按照百分比计算的;

 

 

dp:

Density-independent pixel (dp)独立像素密度。标准是160dip.即1dp对应1个pixel,计算公式如:px = dp * (dpi / 160),屏幕密度越大,1dp对应 的像素点越多。
上面的公式中有个dpi,dpi为DPI是Dots Per Inch(每英寸所打印的点数),也就是当设备的dpi为160的时候1px=1dp;

 

 

计算值

ppi的运算方式是:

PPI = √(长度像素数² + 宽度像素数²) / 屏幕对角线英寸数

dp:Density-independent pixels,以160PPI屏幕为标准,则1dp=1px,

dp和px的换算公式 :
dp*ppi/160 = px。比如1dp x 320ppi/160 = 2px。

 

 

 

屏幕尺寸、分辨率、像素密度三者关系(以上三者的关系)

 

像素密度的公式

 

一部手机的分辨率是宽x高,屏幕大小是以寸为单位,那么三者的关系是:

 

假设一部手机的分辨率是1080×1920(px),屏幕大小是5寸,问密度是多少?
%title插图%num

像素密度计算公式

屏幕多少英寸指的是对角线的长度。像素密度是指(以1920×1080,5英寸为例),1920和1080的平方和开根号(就是直角三角形斜边长的算法),开出来等于2202.9,除以5英寸就得到ppi441左右
例:”HTC One(32GB/单卡/国际版)
4.7英寸屏幕,分辨率1920×1080 求解像素密度?
解:√(1920^2+1080^2)=2202.9071
2202.9/5=468.7021(ppi)≈469ppi
答:此屏幕像素密度约是469ppi.
屏幕多少英寸指的是对角线的长度。像素密度是指(以1920×1080,5英寸为例),1920和1080的平方和开根号(就是直角三角形斜边长的算法),开出来等于2202.9,除以5英寸就得到ppi441左右
例:”HTC One(32GB/单卡/国际版)
4.7英寸屏幕,分辨率1920×1080 求解像素密度?
解:√(1920^2+1080^2)=2202.9071
2202.9/5=468.7021(ppi)≈469ppi
答:此屏幕像素密度约是469ppi.

<span style=”color:#2f2f2f”>因为ui设计师给你的设计图是以px为单位的,Android开发则是使用dp作为单位的,那么我们需要进行转换:</span>

%title插图%num

为了保证用户获得一致的用户体验效果:使得某一元素在Android不同尺寸、不同分辨率的手机上具备相同的显示效果

在进行开发的时候,我们需要把合适大小的图片放在合适的文件夹里面。下面以图标设计为例进行介绍。

 

1).使用自动拉伸位图

 

2).请务必使用 sp 指定文字大小:

 

3).使用布局别名

 

*小宽度限定符仅适用于 Android 3.2 及更高版本。因此,如果我们仍需使用与较低版本兼容的概括尺寸范围(小、正常、大和特大)。例如,如果要将用户界面设计成在手机上显示单面板,但在 7 英寸平板电脑、电视和其他较大的设备上显示多面板,那么我们就需要提供以下文件:

res/layout/main.xml: 单面板布局

res/layout-large: 多面板布局

res/layout-sw600dp: 多面板布局

后两个文件是相同的,因为其中一个用于和 Android 3.2 设备匹配,而另一个则是为使用较低版本 Android 的平板电脑和电视准备的。

要避免平板电脑和电视的文件出现重复(以及由此带来的维护问题),您可以使用别名文件。例如,您可以定义以下布局:

res/layout/main.xml,单面板布局

res/layout/main_twopanes.xml,双面板布局

然后添加这两个文件:

 

res/values-large/layout.xml:

res/values-sw600dp/layout.xml:

后两个文件的内容相同,但它们并未实际定义布局。它们只是将 main 设置成了 main_twopanes 的别名。由于这些文件包含 large 和 sw600dp 选择器,因此无论 Android 版本如何,

系统都会将这些文件应用到平板电脑和电视上(版本低于 3.2 的平板电脑和电视会匹配 large,版本高于 3.2 的平板电脑和电视则会匹配 sw600dp)。

 

图片的适配方案:

 

什么叫*适合的图片?比如我的手机屏幕密度是xxhdpi,那么drawable-xxhdpi文件夹下的图片就是*适合的图片。

 

缩放原理:

 

例如,一个启动图标的尺寸为48×48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48×48 px,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72×72 px),

在 XDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96×96 px),依此类推。

虽然 Android 也支持低像素密度 (LDPI) 的屏幕,但无需为此费神,系统会自动将 HDPI 尺寸的图标缩小到 1/2 进行匹配。

放大系数的关系:和屏幕像素密度有关

mdpi密度的*高dpi值是160,而xxhdpi密度的*高dpi值是480,因此是一个3倍的关系。放大系数和文件夹的密度有关系!

 

xxxhdpi密度的*高dpi值是640,480是它的0.75倍

 

正常Xhdpi的图片,如果放在了xxhdpi里面,图片会缩小,放在hdpi的里面图片会放大!

 

 

一张原图片被缩小了之后显示其实并没有什么副作用,但是一张原图片被放大了之后显示就意味着要占用更多的内存了。

但是一张原图片被放大了之后显示就意味着要占用更多的内存了。因为图片被放大了,像素点也就变多了,而每个像素点都是要占用内存的。

 

我们仍然可以通过例子来直观地体会一下,首先将android_logo.png图片移动到drawable-xxhdpi目录下,运行程序后我们通过Android Monitor来观察程序内存使用情况:

 

 

 

可以看到,程序所占用的内存大概稳定在19.45M左右。然后将android_logo.png图片移动到drawable-mdpi目录下,重新运行程序,结果如下图所示:

 

 

 

现在涨到23.40M了,占用内存明显增加了。如果你将图片移动到drawable-ldpi目录下,你会发现占用内存会更高。

通过这个例子同时也验证了一个问题,我相信有不少比较有经验的Android程序员可能都遇到过这个情况,就是当你的项目变得越来越大,

有的时候加载一张drawable-hdpi下的图片,程序就直接OOM崩掉了,但如果将这张图放到drawable-xhdpi或drawable-xxhdpi下就不会崩掉,其实就是这个道理。

那么经过上面一系列的分析,答案自然也就出来了,图片资源应该尽量放在高密度文件夹下,这样可以节省图片的内存开支,而UI在设计图片的时候也应该尽量面向高密度屏幕的设备来进行设计。

就目前来讲,*佳放置图片资源的文件夹就是drawable-xxhdpi。那么有的朋友可能会问了,不是还有更高密度的drawable-xxxhdpi吗?干吗不放在这里?

这是因为,市面上480dpi到640dpi的设备实在是太少了,如果针对这种级别的屏幕密度来设计图片,图片在不缩放的情况下本身就已经很大了,

 

 

因为分辨率不一样,所以不能用px;因为屏幕宽度不一样,所以要小心的用dp,那么我们可不可以用另外一种方法来统一单位,不管分辨率是多大,屏幕宽度用一个固定的值的单位来统计呢?

 

百分比适配方法,对于控件的宽和高!

 

<TextView
android:layout_width=”@dimen/x360″
android:layout_height=”@dimen/x360″
android:background=”@color/colorAccent”
android:gravity=”center”
android:text=”360×360″/>

<TextView
android:layout_width=”@dimen/x180″
android:layout_height=”@dimen/x180″
android:background=”@color/colorPrimaryDark”
android:gravity=”center”
android:text=”180×180″/>
<dimen name=”x356″>356px</dimen>
<dimen name=”x357″>357px</dimen>
<dimen name=”x358″>358px</dimen>
<dimen name=”x359″>359px</dimen>
<dimen name=”x360″>360px</dimen>
<dimen name=”x358″>537px</dimen>
<dimen name=”x359″>538.5px</dimen>
<dimen name=”x360″>540px</dimen>
<dimen name=”x361″>541.5px</dimen>
<dimen name=”x362″>543px</dimen>
<dimen name=”x363″>544.5px</dimen>
 

总结: 不同分辨率的文件夹有不同x360的px值,是通过程序计算自动生成的!

 

适配缺点:

所以说,这个方案虽然是一劳永逸,但是由于实际上还是使用的px作为长度的度量单位,所以多少和google的要求有所背离,不好说以后会不会出现什么不可预测的问题。

其次,如果要使用这个方案,你必须尽可能多的包含所有的分辨率,因为这个是使用这个方案的基础,如果有分辨率缺少,会造成显示效果很差,甚至出错的风险,而这又势必会增加软件包的大小和维护的难度,

所以大家自己斟酌,择优使用。

 

对于没有考虑到屏幕尺寸,可能会出现意外的情况;
apk的大小会增加;
注意的问题:

要有一个通用的布局,不然有些手机会报错!

 

 

 

虚拟按键的问题

 

设计高度 = 屏幕高度 – 状态栏高度
布局高度 = 屏幕高度 – 状态栏高度 – 虚拟按键高度

要解决这个问题,其实很简单,我说过,这不是一个技术问题,因此不必使用 fitsSystemWindows 属性,也避免了副作用。

只需要在布局时,正确理解设计师的意图,比如,如果一个按钮在*底部,你应该用 layout_gravity=”bottom” 而不是用 marginTop 或者其他方式来把它撑到底部。

 

 

我开始以为他们是导航栏上的虚拟按键,因为确实我将按键隐藏后可以成功适配,然后我就在网上通过如下代码来获取他们的真实分辨率

 

/**
* @param context
* @return 获取屏幕原始尺寸高度,包括虚拟功能键高度
*/
public static int getTotalHeight(Context context) {
int dpi = 0;
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
@SuppressWarnings(“rawtypes”)
Class c;
try {
c = Class.forName(“android.view.Display”);
@SuppressWarnings(“unchecked”)
Method method = c.getMethod(“getRealMetrics”, DisplayMetrics.class);
method.invoke(display, displayMetrics);
dpi = displayMetrics.heightPixels;
} catch (Exception e) {
e.printStackTrace();
}
return dpi;
}

/**
* @param context
* @return 获取屏幕内容高度不包括虚拟按键
*/
public static int getScreenHeight(Context context) {
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.heightPixels;
}
 

这里我用了三部华为手机通过上面的方法获取分辨率
华为荣耀8 1080 1920 得到1080 1812
华为畅享5 720 1280 得到720 1184
华为畅玩5x 108 1920 得到 1080 1776

通过这两个方法我们可以得到手机的分辨率高度和手机去除虚拟按键的高度,两者相减就是手机的虚拟按键的高度
调用后得到的结果是 总高度 : 2560 内容高度 : 2408 虚拟按键 : 152
如果想要适配该机型,其实也很简单,只需要把原来的values-2560×1440文件夹复制一份重新名为values-2408×1440即可,

http://blog.csdn.net/c15522627353/article/details/52452490

 

Google的 百分比布局库(percent-support-lib)

 

PercentRelativeLayout、PercentFrameLayout,不过貌似没有LinearLayout,有人会说LinearLayout有weight属性呀。但是,weight属性只能支持一个方向呀~~哈,没事,刚好给我们一个机会去自定义一个PercentLinearLayout。

 

<android.support.percent.PercentRelativeLayout
xmlns:android=”http://schemas.android.com/apk/res/android”xmlns:app=”http://schemas.android.com/apk/res-auto”android:layout_width=”match_parent”android:layout_height=”match_parent”>

<Viewandroid:id=”@+id/top_left”android:layout_width=”0dp”android:layout_height=”0dp”android:layout_alignParentTop=”true”android:background=”#ff44aacc”app:layout_heightPercent=”20%”app:layout_widthPercent=”70%” />

<Viewandroid:id=”@+id/top_right”android:layout_width=”0dp”android:layout_height=”0dp”android:layout_alignParentTop=”true”android:layout_toRightOf=”@+id/top_left”android:background=”#ffe40000″app:layout_heightPercent=”20%”app:layout_widthPercent=”30%” />

<Viewandroid:id=”@+id/bottom”android:layout_width=”match_parent”android:layout_height=”0dp”android:layout_below=”@+id/top_left”android:background=”#ff00ff22″app:layout_heightPercent=”80%” />
</android.support.percent.PercentRelativeLayout>
 

 

Android UI适配小结:

 

1.一般情况下,输出几套,各为多少分辨率不同的质量的切图?
2.一般情况下,输出几套分辨率不同的标注图(如常说的主流四套分辨率,1080*1920,720*1280,480*800.240*320), 标注单位为px还是自己计算的dp(这个单位您一般是自己计算还是交给程序,如果自己计算的话比较好的方法),创建画布时*版从多少分辨率的画布开始设计。
3.是否考虑带有虚拟按键的适配不同方法?
4.遇到与主流分辨率相近但长宽比不同的分辨率时的适配方式。
5.字号的适配方式,字号单位。
6.与程序适配沟通时需要注意的要点。
7.其它我未提及或者没考虑到的但您觉得非常重要的问题。

 

 

1.一般只输出主流尺寸的切图,如480*800 1280*720
2.开发写标准尺寸是按照320P来的,得按照你自己的设计稿来标注,不过得算好比例:比如你出了480*800的设计稿,你所有的标注的所有尺寸都得除以1.5;要是出了720*1280的设计稿,你所有的标注的所有尺寸都得除以2,以此类推
3.无需考虑,系统自适应
4.只要宽度分辨率一样,适配一样
5.字号跟第2点一样道理
6.程序员对UI实现都不严谨(因为他们觉得UI不重要),要时时刻刻盯着

 

主流APP:

对于平板来说:都是要单独一套App的

 

而分辨率可以以1280*720或者是1960*1080作为主要分辨率进行设计。

 

1280:720    的屏占比     16:9

1960*1280的屏占比      49:32

 

效果图一套(720p),切图也是 720p (有时候要求1080p,看公司要求高不高)。

 

UI切图都是以1280*720 开发写标准尺寸是按照320P来的

假若还是不适配的话也可以根据不适配的机型来设定特别的值。

 

为什么Web页面设计人员从来没有说过,尼玛适配好麻烦?

其实就是一个原因,网页提供了百分比计算大小。

 

自定义控件以PX为单位计算的,那么怎么适配呢!
接下来会讲

Android bind其他或第三方APK Service方法

有时候我们会使用其他模块Service接口,这里介绍一个Bind其他APK service的方法。

假设A要bind B的Service,这里B我们假设是“com.android.music.MediaPlaybackService”.

1.检查B的Service是否允许其他模块引用

–打开B的Manifest,只有带有android:exported=”true”属性才可以:

<service android:name=”com.android.music.MediaPlaybackService”
android:exported=”true” />

======================================

2.将B的aidl复制一份到A中

–B/com/android/music/IMediaPlaybackService.aidl 复制到A/com/android/music/IMediaPlaybackService.aidl

–即B的aidl文件放到A后,需要保持和B中一样目录结构和内容。

–如果用android studio编译,注意检查是否build.gradle是否配置了对应的aidl文件夹(如果没有添加上),形如:

sourceSets {
main {
manifest.srcFile ‘src/main/AndroidManifest.xml’
java.srcDirs = [‘src/main/java’]
resources.srcDirs = [‘src/main/res’]
res.srcDirs = [‘src/main/res’]
assets.srcDirs = [‘src/main/assets’]
aidl.srcDirs = [‘src/main/java’]
}

======================================

3.A中实现Bind

–定义一个接口变量mMusicService

IMediaPlaybackService mMusicService = null;

–定义一个ServiceConnection变量osc

private ServiceConnection osc = new ServiceConnection() {
public void onServiceConnected(ComponentName classname, IBinder obj) {
mMusicService = IMediaPlaybackService.Stub.asInterface(obj);
Log.e(TAG, “MusicServiceg Connect!!”);
}
public void onServiceDisconnected(ComponentName classname) {
Log.e(TAG, “MusicServiceg DisConnect!!”);
mMusicService = null;
}
};

–通过intent bind B的Service

Intent playBackIntent = new Intent(Intent.ACTION_MAIN);
ComponentName componentName = new ComponentName(
“com.android.music”,
“com.android.music.MediaPlaybackService”);
playBackIntent.setComponent(componentName);
Log.e(TAG, “MusicService start bind!!”);
mContext.bindService(playBackIntent, osc, 0);

======================================

 

至此已经可以bind到MediaPlaybackService了,可以正常调用B的接口

保持Service不被Kill掉的方法–双Service守护 & Android实现双进程守护

本文分为两个部分,*部分为双Service守护,第二部分为双进程守护

*部分:

一、Service简介:
java.lang.Object

↳android.content.Context

↳android.content.ContextWrapper

↳android.app.Service

Service是应用程序Application的一个组件(component)。
它的作用有两点:1.用来提供一个长期在后台运行并且不与用户交互的操作,2.也可以为其他应用程序提供服务。
Service必须和其他四大组件一样,使用<service>标签在AndroidManifest.xml中进行声明。
启动service有两种方式Context.startService() 和 Context.bindService()。

注意,除了特别指定外,service并不是单独的进程,一般service在其宿主进程的主线程(UI Thread)中运行【当然也可以在新的线程中startService,这样Service就不是在MainThread了】。这意味着,如果您的服务要做任何 耗时(如 MP3 播放) 或阻塞 (比如网络) 操作,它应该产生它自己的线程,用来做那项工作。(service不是单独的进程也不是单独的线程)

Service提供了两大功能:
Context.startService()用来在后台启动一个服务;
Context.bindService()用来绑定其他服务,以此来获取其他service提供的服务;

 

本地服务 Local Service 用于应用程序内部

它可以启动并运行,直至有人停止了它或它自己停止。在这种方式下,它以调用Context.startService()启动,而以调用Context.stopService()结束。它可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。

【用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好】

 

远程服务 Remote Service 用于android系统内部的应用程序之间

它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用Context.bindService()方法建立,以调用 Context.unbindService()关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

【可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可】

 

 

二、Service运行方式和生命周期图:

以startService()启动服务,系统将通过传入的Intent在底层搜索相关符合Intent里面信息的service。如果服务没有启动则先运行onCreate,然后运行onStartCommand (可在里面处理启动时传过来的Intent和其他参数),直到明显调用stopService或者stopSelf才将停止Service。无论运行startService多少次,只要调用一次stopService或者stopSelf,Service都会停止。使用stopSelf(int)方法可以保证在处理好intent后再停止。onStartCommand ,在2.0后被引入用于service的启动函数,2.0之前为public void onStart(Intent intent, int startId) 。

以bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止。onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。采用Context.bindService()方法启动服务时只能调用onUnbind()方法解除调用者与服务解除,服务结束时会调用onDestroy()方法。

 

(注意这个新老API的改变)

void onStart(Intent intent, int startId)
This method was deprecated      in API level 5.    Implement onStartCommand(Intent, int, int) instead.

 

int onStartCommand(Intent intent, int flags, int startId)
Called by the system every time a client explicitly starts the service by calling  startService(Intent), providing the arguments it supplied and a  unique integer token representing the start request.

 

三、Service的优先级

官方文档告诉我们,Android系统会尽量保持拥有service的进程运行,只要在该service已经被启动(start)或者客户端连接(bindService)到它。当内存不足时,需要保持,拥有service的进程具有较高的优先级。

1. 如果service正在调用onCreate,onStartCommand或者onDestory方法,那么用于当前service的进程则变为前台进程以避免被killed。
2. 如果当前service已经被启动(start),拥有它的进程则比那些用户可见的进程优先级低一些,但是比那些不可见的进程更重要,这就意味着service一般不会被killed.
3. 如果客户端已经连接到service (bindService),那么拥有Service的进程则拥有*高的优先级,可以认为service是可见的。
4. 如果service可以使用startForeground(int, Notification)方法来将service设置为前台状态,那么系统就认为是对用户可见的,并不会在内存不足时killed。
5. 如果有其他的应用组件作为Service,Activity等运行在相同的进程中,那么将会增加该进程的重要性。

 

四、保持service不被kill掉

方法一:

START_STICKY is used for services that are explicitly started and stopped as needed, while START_NOT_STICKY or START_REDELIVER_INTENT are used for services that should only remain running while processing any commands sent to them

onStartCommand方法几个返回值简介:

1、START_STICKY

在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。不久后service就会再次尝试重新创建,因为保留在开始状态,在创建     service后将保证调用onstartCommand。如果没有传递任何开始命令给service,那将获取到null的intent。

2、START_NOT_STICKY

在运行onStartCommand后service进程被kill后,并且没有新的intent传递给它。Service将移出开始状态,并且直到新的明显的方法(startService)调用才重新创建。因为如果没有传递任何未决定的intent那么service是不会启动,也就是期间onstartCommand不会接收到任何null的intent。

3、START_REDELIVER_INTENT

在运行onStartCommand后service进程被kill后,系统将会再次启动service,并传入*后一个intent给onstartCommand。直到调用stopSelf(int)才停止传递intent。如果在被kill后还有未处理好的intent,那被kill后服务还是会自动启动。因此onstartCommand不会接收到任何null的intent。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
【结论】 手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了….

方法二:

提升service优先级

在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = “1000”这个属性设置*高优先级,1000是*高值,如果数字越小则优先级越低,同时适用于广播。

<service
android:name=”com.dbjtech.acbxt.waiqin.UploadService”
android:enabled=”true” >
<intent-filter android:priority=”1000″ >
<action android:name=”com.dbjtech.myservice” />
</intent-filter>
</service>
【结论】目前看来,priority这个属性貌似只适用于broadcast,对于Service来说可能无效

 

方法三:

提升service进程优先级

Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。Android将进程分为6个等级,它们按优先级顺序由高到低依次是:

1.前台进程( FOREGROUND_APP)
2.可视进程(VISIBLE_APP )
3. 次要服务进程(SECONDARY_SERVER )
4.后台进程 (HIDDEN_APP)
5.内容供应节点(CONTENT_PROVIDER)
6.空进程(EMPTY_APP)

当service运行在低内存的环境时,将会kill掉一些存在的进程。因此进程的优先级将会很重要,可以使用startForeground将service放到前台状态。这样在低内存时被kill的几率会低一些。

在onStartCommand方法内添加如下代码:

Notification notification = new Notification(R.drawable.ic_launcher,getString(R.string.app_name), System.currentTimeMillis());

PendingIntent pendingintent = PendingIntent.getActivity(this, 0,new Intent(this, AppMain.class), 0);
notification.setLatestEventInfo(this, “uploadservice”, “请保持程序在后台运行”, pendingintent);
startForeground(0x111, notification);

注意在onDestroy里还需要stopForeground(true),运行时在下拉列表会看到自己的APP在:

 

【结论】如果在*度*度低内存的压力下,该service还是会被kill掉,并且不一定会restart

保持Service不被Kill掉的方法–双Service守护,代码如下:

 

AndroidManifest.xml:
<activity
android:name=”.MainActivity”
android:label=”@string/app_name” >
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<service
android:name=”ServiceOne”
android:process=”:remote” >
<intent-filter>
<action android:name=”com.example.servicedemo.ServiceOne” />
</intent-filter>
</service>

<service
android:name=”ServiceTwo”
android:process=”:remote” >
<intent-filter>
<action android:name=”com.example.servicedemo.ServiceTwo” />
</intent-filter>
</service>
MainActivity.java:
package com.example.servicedemo;

import java.util.ArrayList;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

public class MainActivity extends Activity {

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

Intent serviceOne = new Intent();
serviceOne.setClass(MainActivity.this, ServiceOne.class);
startService(serviceOne);

Intent serviceTwo = new Intent();
serviceTwo.setClass(MainActivity.this, ServiceTwo.class);
startService(serviceTwo);
}

public static boolean isServiceWorked(Context context, String serviceName) {
ActivityManager myManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ArrayList<RunningServiceInfo> runningService = (ArrayList<RunningServiceInfo>) myManager.getRunningServices(Integer.MAX_VALUE);
for (int i = 0; i < runningService.size(); i++) {
if (runningService.get(i).service.getClassName().toString().equals(serviceName)) {
return true;
}
}
return false;
}
}

ServiceOne.java:
package com.example.servicedemo;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class ServiceOne extends Service {

public final static String TAG = “com.example.servicedemo.ServiceOne”;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, “onStartCommand”);

thread.start();
return START_STICKY;
}

Thread thread = new Thread(new Runnable() {

@Override
public void run() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {

@Override
public void run() {
Log.e(TAG, “ServiceOne Run: “+System.currentTimeMillis());
boolean b = MainActivity.isServiceWorked(ServiceOne.this, “com.example.servicedemo.ServiceTwo”);
if(!b) {
Intent service = new Intent(ServiceOne.this, ServiceTwo.class);
startService(service);
Log.e(TAG, “Start ServiceTwo”);
}
}
};
timer.schedule(task, 0, 1000);
}
});

@Override
public IBinder onBind(Intent arg0) {
return null;
}

}

ServiceTwo.java:
package com.example.servicedemo;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class ServiceTwo extends Service {

public final static String TAG = “com.example.servicedemo.ServiceTwo”;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, “onStartCommand”);

thread.start();
return START_REDELIVER_INTENT;
}

Thread thread = new Thread(new Runnable() {

@Override
public void run() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {

@Override
public void run() {
Log.e(TAG, “ServiceTwo Run: ” + System.currentTimeMillis());
boolean b = MainActivity.isServiceWorked(ServiceTwo.this, “com.example.servicedemo.ServiceOne”);
if(!b) {
Intent service = new Intent(ServiceTwo.this, ServiceOne.class);
startService(service);
}
}
};
timer.schedule(task, 0, 1000);
}
});

@Override
public IBinder onBind(Intent arg0) {
return null;
}

}
第二部分:

做过android开发的人应该都知道应用会在系统资源匮乏的情况下被系统杀死!当后台的应用被系统回收之后,如何重新恢复它呢?网上对此问题有很多的讨论。这里先总结一下网上流传的各种解决方案,看看这些办法是不是真的可行。
1.提高优先级
这个办法对普通应用而言,应该只是降低了应用被杀死的概率,但是如果真的被系统回收了,还是无法让应用自动重新启动!

2.让service.onStartCommand返回START_STICKY
通过实验发现,如果在adb shell当中kill掉进程模拟应用被意外杀死的情况(或者用360手机卫士进行清理操作),如果服务的onStartCommand返回START_STICKY,在eclipse的进程管理器中会发现过一小会后被杀死的进程的确又会出现在任务管理器中,貌似这是一个可行的办法。但是如果在系统设置的App管理中选择强行关闭应用,这时候会发现即使onStartCommand返回了START_STICKY,应用还是没能重新启动起来!

3.android:persistent=”true”
网上还提出了设置这个属性的办法,通过实验发现即使设置了这个属性,应用程序被kill之后还是不能重新启动起来的!

4.让应用成为系统应用
实验发现即使成为系统应用,被杀死之后也不能自动重新启动。但是如果对一个系统应用设置了persistent=”true”,情况就不一样了。实验表明对一个设置了persistent属性的系统应用,即使kill掉会立刻重启。一个设置了persistent=”true”的系统应用,在android中具有core service优先级,这种优先级的应用对系统的low memory killer是免疫的!

OK,说了半天,只有core service优先级的应用才能保证在被意外杀死之后做到立刻满血复活。而普通应用要想成为系统应用就必须要用目标机器的签名文件进行签名,但这样又造成了应用无法保证兼容所有不同厂商的产品。那么该怎么办呢?这里就来说一说双进程守护。网上也有人提到过双进程守护的办法,但是很少能搜索到类似的源码!如果从进程管理器重观察会发现新浪微博或者360卫视都有两个相关的进程,其中一个就是守护进程,由此可以猜到这些商业级的软件也采用了双进程守护的办法。

什么是双进程守护呢?顾名思义就是两个进程互相监视对方,发现对方挂掉就立刻重启!不知道应该把这样的一对进程是叫做相依为命呢还是难兄难弟好呢,但总之,双进程守护的确是一个解决问题的办法!相信说到这里,很多人已经迫切的想知道如何实现双进程守护了。这篇文章就介绍一个用NDK来实现双进程保护的办法,不过首先说明一点,下面要介绍的方法中,会损失不少的效率,反应到现实中就是会使手机的耗电量变大!但是这篇文章仅仅是抛砖引玉,相信看完之后会有更多高人指点出更妙的实现办法。

需要了解些什么?
这篇文章中实现双进程保护的方法基本上是纯的NDK开发,或者说全部是用C++来实现的,需要双进程保护的程序,只需要在程序的任何地方调用一下JAVA接口即可。下面几个知识点是需要了解的:
1.linux中多进程;
2.unix domain套接字实现跨进程通信;
3.linux的信号处理;
4.exec函数族的用法;

其实这些东西本身并不是多复杂的技术,只是我们把他们组合起来实现了一个双进程守护而已,没有想象中那么神秘!在正式贴出代码之前,先来说说几个实现双进程守护时的关键点:
1.父进程如何监视到子进程(监视进程)的死亡?
很简单,在linux中,子进程被终止时,会向父进程发送SIG_CHLD信号,于是我们可以安装信号处理函数,并在此信号处理函数中重新启动创建监视进程;
2.子进程(监视进程)如何监视到父进程死亡?
当父进程死亡以后,子进程就成为了孤儿进程由Init进程领养,于是我们可以在一个循环中读取子进程的父进程PID,当变为1就说明其父进程已经死亡,于是可以重启父进程。这里因为采用了循环,所以就引出了之前提到的耗电量的问题。
3.父子进程间的通信
有一种办法是父子进程间建立通信通道,然后通过监视此通道来感知对方的存在,这样不会存在之前提到的耗电量的问题,在本文的实现中,为了简单,还是采用了轮询父进程PID的办法,但是还是留出了父子进程的通信通道,虽然暂时没有用到,但可备不时之需!

 

腾讯的面试官问我:应用程序死了如何恢复?确实,双进程守护只能做到进程被杀死后重新启动,但是重启后如何恢复到之前的状态这是一个问题。因为进程被意外杀死的情况,onSaveInstance是来不及执行的,所以程序的状态没法保存!对于双进程守护来说,不知道是不是可以再父进程进入后台以后(onStop),把数据收集起来保存到子进程中,然后父进程重启以后从子进程中取出这些信息呢?这是一个办法,但是上面说明的双进程守护程序的实现中还做不到,因为父进程重启以后,子进程也挂掉重新建立了,要想实现优雅的恢复,还得在做出点改进才是!只能实时保存数据到数据库等。

bindService获取代理是同步还是异步

Android中bindService是一个异步的过程,什么意思呢?使用bindService无非是想获得一个Binder服务的Proxy,但这个代理获取到的时机并非由bindService发起端控制,而是由Service端来控制,也就是说bindService之后,APP端并不会立刻获得Proxy,而是要等待Service通知APP端,具体流程可简化如下:

  • APP端先通过bindService去AMS登记,说明自己需要绑定这样一个服务,并留下派送地址
  • APP回来,继续做其他事情,可以看做是非阻塞的
  • AMS通知Service端启动这个服务
  • Service启动,并通知AMS启动完毕
  • AMS跟住之前APP端留下的地址通知APP端,并将Proxy代理传递给APP端

通过代码来看更直接

  1.     void test(){
  2.         bindService(intent, new ServiceConnection() {
  3.             @Override
  4.             public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
  5.                iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
  6.                Log.v(TAG, “onServiceConnected…” );
  7.             }
  8.            @Override
  9.             public void onServiceDisconnected(ComponentName componentName) {
  10.            }
  11.         }, Context.BIND_AUTO_CREATE);
  12.         Log.v(TAG, “end…” );
  13.     }

bindService的过程中,上面代码的Log应该是怎么样的呢?如果bindService是一个同步过程,那么Log应该如下:

  1. TAG  onServiceConnected …
  2. TAG  end …

但是由于是个异步过程,真实的Log如下

  1. TAG  end …
  2. TAG  onServiceConnected …

也就是说bindService不会阻塞等待APP端获取Proxy,而是直接返回,这些都可以从源码获得支持,略过,直接去ActivityManagerNative去看

  1. public int bindService(IApplicationThread caller, IBinder token,
  2.         Intent service, String resolvedType, IServiceConnection connection,
  3.         int flags, int userId) throws RemoteException {
  4.     Parcel data = Parcel.obtain();
  5.     Parcel reply = Parcel.obtain();
  6.     data.writeInterfaceToken(IActivityManager.descriptor);
  7.     data.writeStrongBinder(caller != null ? caller.asBinder() : null);
  8.     data.writeStrongBinder(token);
  9.     service.writeToParcel(data, 0);
  10.     data.writeString(resolvedType);
  11.     data.writeStrongBinder(connection.asBinder());
  12.     data.writeInt(flags);
  13.     data.writeInt(userId);
  14.     <!–阻塞等待–>
  15.     mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0);
  16.     reply.readException();
  17.     int res = reply.readInt();
  18.     data.recycle();
  19.     reply.recycle();
  20.     return res;
  21. }

mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0)确实会让APP端调用线程阻塞,等待AMS执行BIND_SERVICE_TRANSACTION请求,不过AMS在执行这个请求的时候并非是唤醒Service才返回,它返回的时机更早,接着看ActivityManagerService,

  1. public int bindService(IApplicationThread caller, IBinder token,
  2.         Intent service, String resolvedType,
  3.         IServiceConnection connection, int flags, int userId) {
  4.     …
  5.     synchronized(this) {
  6.         return mServices.bindServiceLocked(caller, token, service, resolvedType,
  7.                 connection, flags, userId);
  8.     }
  9. }

ActivityManagerService直接调用ActiveServices的函数bindServiceLocked,请求绑定Service,到这里APP端线程依旧阻塞,等待AMS端返回,假定Service所处的进程已经启动但是Service没有启动,这时ActiveServices会进一步调用bindServiceLocked->realStartServiceLocked来启动Service,有趣的就在这里:

  1.  private final void realStartServiceLocked(ServiceRecord r,
  2.             ProcessRecord app) throws RemoteException {
  3.         …
  4.         <!–请求Service端启动Service–>
  5.             app.thread.scheduleCreateService(r, r.serviceInfo,
  6.                     mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo));
  7.         …
  8.         <!–请求绑定Service–>
  9.         requestServiceBindingsLocked(r);

app.thread.scheduleCreateService也是一个Binder通信过程,他其实是AMS异步请求ActivityThread中的ApplicationThread服务,系统服务请求客户端的本地服务一般都是异步的:

  1. // 插入消息,等待主线程执行
  2. public final void scheduleCreateService(IBinder token,
  3.         ServiceInfo info, CompatibilityInfo compatInfo) {
  4.     CreateServiceData s = new CreateServiceData();
  5.     s.token = token;
  6.     s.info = info;
  7.     s.compatInfo = compatInfo;
  8.     <!–向Loop的MessagerQueue插入一条消息就返回–>
  9.     queueOrSendMessage(H.CREATE_SERVICE, s);
  10. }

不过,这个请求直接向Service端的ActivityThread线程中直接插入一个消息就返回了,而并未等到该请求执行,因为AMS使用的非常频繁,不可能老等待客户端完成一些任务,所以AMS端向客户端发送完命令就直接返回,这个时候其实Service还没有被创建,也就是这个请求只是完成了一半,onServiceConnected也并不会执行,onServiceConnected什么时候执行呢?app.thread.scheduleCreateService向APP端插入*条消息,是用来创建Service的, requestServiceBindingsLocked其实就是第二条消息,用来处理绑定的

  1.  private final boolean requestServiceBindingLocked(ServiceRecord r,
  2.             IntentBindRecord i, boolean rebind) {
  3.                 …
  4.            <!– 第二个消息,请求处理绑定–>
  5.             r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind);

第二条消息是处理一些绑定需求,Android的Hanlder消息处理机制保证了第二条消息一定是在*条消息之后执行,

  1.  public final void scheduleBindService(IBinder token, Intent intent,
  2.         boolean rebind) {
  3.     BindServiceData s = new BindServiceData();
  4.     s.token = token;
  5.     s.intent = intent;
  6.     s.rebind = rebind;
  7.     queueOrSendMessage(H.BIND_SERVICE, s);
  8. }

以上两条消息插入后,AMS端被唤醒,进而重新唤醒之前阻塞的bindService端,而这个时候,Service并不一定被创建,所以说这是个未知的异步过程,Service端处理*条消息的时会创建Service,

  1.  private void handleCreateService(CreateServiceData data) {
  2.     …
  3.     LoadedApk packageInfo = getPackageInfoNoCheck(
  4.             data.info.applicationInfo, data.compatInfo);
  5.     Service service = null;
  6.     try {
  7.         java.lang.ClassLoader cl = packageInfo.getClassLoader();
  8.         service = (Service) cl.loadClass(data.info.name).newInstance();
  9.    …

执行第二条消息的时候, 会向AMS请求publishService,其实就是告诉AMS,服务启动完毕,可以向之前请求APP端派发代理了。

  1.  private void handleBindService(BindServiceData data) {
  2.     Service s = mServices.get(data.token);
  3.     if (s != null) {
  4.        try {
  5.         data.intent.setExtrasClassLoader(s.getClassLoader());
  6.         try {
  7.             if (!data.rebind) {
  8.                 IBinder binder = s.onBind(data.intent);
  9.                 ActivityManagerNative.getDefault().publishService(
  10.                         data.token, data.intent, binder);
  11.             …

AMS端收到publishService消息之后,才会向APP端发送通知,进而通过Binder回调APP端onServiceConnected函数,同时传递Proxy Binder服务代理

  1. void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
  2.     …
  3.      try {
  4.     <!–通过binder 回到APP端的onServiceConnected–>
  5.         c.conn.connected(r.name, service);
  6.     } catch (Exception e) {}

到这里,onServiceConnected才会被回调,不过,至于Service端那两条消息什么时候执行,谁也不能保证,也许因为特殊原因,那两条消息永远不被执行,那onServiceConnected也就不会被回调,但是这不会影响AMS与APP端处理其他问题,因为这些消息是否被执行已经不能阻塞他们两个了,简单流程如下:

640?wx_fmt=pngbindService的异步流程

*后,其实startService也是异步。

Android中BindService方式使用的理解

*近学习了一下Android里面的Service的应用,在BindService部分小卡了一下,主要是开始没有彻底理解为什么要这么实现。

BindService和Started Service都是Service,有什么地方不一样呢:1. Started Service中使用StartService()方法来进行方法的调用,调用者和服务之间没有联系,即使调用者退出了,服务依然在进行【onCreate()- >onStartCommand()->startService()->onDestroy()】,注意其中没有onStart(),主要是被onStartCommand()方法给取代了,onStart方法不推荐使用了。2. BindService中使用bindService()方法来绑定服务,调用者和绑定者绑在一起,调用者一旦退出服务也就终止了【onCreate()->onBind()->onUnbind()->onDestroy()】。调用者Activity:

 

  1. package com.zys.service;
  2. import com.zys.service.BindService.MyBinder;
  3. import android.R.bool;
  4. import android.app.Activity;
  5. import android.content.ComponentName;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.content.ServiceConnection;
  9. import android.os.Bundle;
  10. import android.os.IBinder;
  11. import android.view.View;
  12. import android.view.View.OnClickListener;
  13. import android.widget.Button;
  14. public class MainActivity extends Activity {
  15. private Button startBtn;
  16. private Button stopBtn;
  17. private boolean flag;
  18. /** Called when the activity is first created. */
  19. @Override
  20. public void onCreate(Bundle savedInstanceState) {
  21. super.onCreate(savedInstanceState);
  22. setContentView(R.layout.main);
  23. flag = false;
  24. //设置
  25. startBtn = (Button)this.findViewById(R.id.startBtn);
  26. stopBtn = (Button)this.findViewById(R.id.stopBtn);
  27. startBtn.setOnClickListener(listener);
  28. stopBtn.setOnClickListener(listener);
  29. }
  30. private OnClickListener listener = new OnClickListener() {
  1. @Override
  2. public void onClick(View v) {
  3. // TODO Auto-generated method stub
  4. switch (v.getId()) {
  5. case R.id.startBtn:
  6. bindService();
  7. break;
  8. case R.id.stopBtn:
  9. unBind();
  10. break;
  11. default:
  12. break;
  13. }
  14. }
  15. };
  16. private void bindService(){
  17. Intent intent = new Intent(MainActivity.this,BindService.class);
  18. bindService(intent, conn, Context.BIND_AUTO_CREATE);
  19. }
  20. private void unBind(){
  21. if(flag == true){
  22. unbindService(conn);
  23. flag = false;
  24. }
  25. }
  26. private ServiceConnection conn = new ServiceConnection() {
  27. @Override
  28. public void onServiceDisconnected(ComponentName name) {
  29. // TODO Auto-generated method stub
  30. }
  31. @Override
  32. public void onServiceConnected(ComponentName name, IBinder service) {
  33. // TODO Auto-generated method stub
  34. MyBinder binder = (MyBinder)service;
  35. BindService bindService = binder.getService();
  36. bindService.MyMethod();
  37. flag = true;
  38. }
  39. };
  40. }

服务BindService

  1. package com.zys.service;
  2. import java.io.FileDescriptor;
  3. import android.app.Service;
  4. import android.content.Intent;
  5. import android.os.Binder;
  6. import android.os.IBinder;
  7. import android.os.IInterface;
  8. import android.os.Parcel;
  9. import android.os.RemoteException;
  10. import android.util.Log;
  11. public class BindService extends Service {
  12. private static final String TAG = “BindService”;
  13. public void MyMethod(){
  14. Log.i(TAG, “BindService–>MyMethod()”);
  15. }
  16. @Override
  17. public IBinder onBind(Intent intent) {
  18. // TODO Auto-generated method stub
  19. return myBinder;
  20. }
  21. public class MyBinder extends Binder{
  22. public BindService getService(){
  23. return BindService.this;
  24. }
  25. }
  26. private MyBinder myBinder = new MyBinder();
  27. }

 

由于Android 中的Service使用了onBind 的方法去绑定服务,返回一个Ibinder对象进行操作,而我们要获取具体的Service方法的内容的时候,我们需要Ibinder对象返回具体的Service对象才能操作,所以说具体的Service对象必须首先实现Binder对象,这个样子的话我们才能利用bindService的方法对Service进行绑定,获取Binder对象之后获取具体的Service对象,然后才获取Service中的方法等等。所以我们需要注意的是bindService的方式去绑定服务获取的必定是实现了Binder的对象,所以这是我们必须使用Binder的方式去获取Service的方式而不是直接使用Service的类,这个是Android内部实现所约束的。

方法过程如下:

Intent intent = new Intent(MainActivity.this,BindService.class)->新建了BindService对象->新建了MyBinder对象

->bindService(intent, conn, Context.BIND_AUTO_CREATE);->onBind()函数  —–传递MyBinder对象——->onServiceConnected()

–> 通过传递的Binder对象获取刚刚和Binder对象对应的BindService 对象  –>调用Service中定义的方法。

这个其中必须通过Binder对象,因为是通过Binder对象来传递的,通过Binder对象获取Service对象,然后获取所需的服务,所以Service必须实现Binder,以便传递和使用。

 

Android Studio 找不到R文件解决方法汇总

一、新建的Activity中R文件找不到,其他文件中的R文件可以正常使用。
解决方法:在该Activity中引入R包即可:import com.example.zcj.password.R;

%title插图%num

在子目录下新建Activity文件都会出现这个问题,可以通过设置自动导入包:File-Settings-Editor-General-Auto Import,将Optimize imports on the fly和Add unambiguous imports on the fly勾选即可。

%title插图%num

二、新导入的项目找不到R文件(所有文件都找不到R文件),
解决方法:Android Studio-Build-Clean Project-Rebuild Project;  Rebuild之后,AS中会显示错误信息,根据提示修改即可。

三、error提示为“Error:Execution failed for task ‘:app:compileDebugAidl’.> aidl is missing” 。
解决方法:http://blog.csdn.net/hao2244/article/details/46663885

四、Gradle版本问题

解决方法:修改工程中的 build.gradle版本,classpath ‘com.android.tools.build:gradle:3.1.0’,然后修改gradle-wrapper.properties 文件中的内容distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip,修改成相应的版本,然后重新同步一下,rebuild一下即可。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速