【0171】Android 面试-基本知识

1.课程基本讲解

2.activity面试问题

2.1 Activity的生命周期

2.1.1 Activity的四种状态

【running状态】Activity是处于活动状态的,用户可以点击屏幕,然后屏幕可以做出响应;此时的Activity处于栈顶;

【paused状态】Activity失去焦点,或者是被一个非全屏的Activity占据,或者是一个透明的Activity占据;

  此时的状态是Activity只是不可以和用户进行交互,但是可以内存的对象和变量是保存的,除非在内存紧张的时候会将此Activity销毁;

【stoped状态】不可见,但是在后台,内存的对象和变量是保存的,除非在内存紧张的时候会将此Activity销毁;

【killed状态】Activity被彻底销毁,内存的对象和变量已经不存在了;

2.1.2 Activity的生命周期

2.1.3 Android 进程优先级

【前台】可以与用户进行交互;

【可见】用户可见但是不可以交互;

【服务】在后台开启的service服务;

 【后台】前台的进程点击home键之后所处的进程就是后台进程,除非在内存紧张的时候会将此进程资源被回收;

【空进程】数据的暂存,优先级最低,随时可能被回收;

2.2 android 任务栈

2.3 android 的启动模式

【说明】详见参见《Android 开发艺术探索》

 

 

2.4 scheme跳转协议

scheme是一种页面内跳转协议,主要用于支持一下几种场景:

             服务器下发跳转路径,客户端根据服务器下发跳转路径跳转相应的页面

             H5页面点击锚点,根据锚点具体跳转路径App端跳转具体的页面

            App端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面

下面我将简单介绍一下scheme的基本概念以及以上三种场景下scheme的具体应用。

URL scheme 概述

URL scheme 的作用

客户端应用可以向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用启动本应用

通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如车辆详情页、订单详情页、消息通知页、促销广告页等等。

也可以执行某些指定动作,如订单支付等。

也可以在应用内通过 html 页来直接调用显示 app 内的某个页面

URL scheme 的格式

客户端自定义的 URL 作为从一个应用调用另一个的基础,遵循 RFC 1808 (Relative Uniform Resource Locators) 标准。这跟我们常见的网页内容 URL 格式一样。

一个普通的 URL 分为几个部分,schemehostrelativePathquery

比如:http://www.baidu.com/s?rsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709

这个URL中,scheme 为 httphost 为 www.baidu.comrelativePath 为 /squery 为 rsv_bp=1&rsv_spt=1&wd=NSurl&inputT=2709

一个应用中使用的 URL 例子(该 URL 会调起车辆详情页):

uumobile://mobile/carDetail?car_id=123456,其中 scheme 为 uumobilehost 为 mobilerelativePath 为 /carDetailquery 为 car_id=123456

Scheme定义Activity

1)在Androidmanifest.xml中定义scheme

<!-- scheme协议 -->
        <activity
            Android:name=".UI.translate.NativeAppActivity"
            Android:label="@string/app_name">

            <!-- 要想在别的App上能成功调起App,必须添加intent过滤器 -->
            <intent-filter>

                <!-- 协议部分,随便设置 -->
                <data Android:scheme="uumobile" />
                <!-- 下面这几行也必须得设置 -->
                <category Android:name="Android.intent.category.DEFAULT" />
                <category Android:name="Android.intent.category.BROWSABLE" />

                <action Android:name="Android.intent.action.VIEW" />
            </intent-filter>
        </activity>

这样我们便定义了能够接受scheme请求的activity实例,当网页或者是Android代码发送这种规则scheme的请求的时候就能够调用NativeAppActivity了。

2)当然就是实现NativeAppActivity

/**
 * Created by admin
 */
public class NativeAppActivity extends Activity{
    public String tag = "NativeAppActivity";
    public Activity mContext = null;

    public void onCreate(Bundle b)
    {
        super.onCreate(b);
        mContext = this;
        Uri uri = getIntent().getData();
        if (uri != null)
        {
            List<String> pathSegments = uri.getPathSegments();
            String uriQuery = uri.getQuery();
            Intent intent;
            if (pathSegments != null && pathSegments.size() > 0) {
                // 解析SCHEME
                if (someif) {
                  dosomething();
                }
                else {
                    // 若解析不到SCHEME,则关闭NativeAppActivity;
                    finish();
                }
            } else {
                finish();
            }
        } else {
            finish();
        }
    }

}

NativeAppActivity这个类中主要用于实现对scheme的解析然后做出相应的动作,比如请求scheme跳转登录页面,我们可以这样定义

uumobile://appname/gotoLogin

然后我们解析出scheme如果是这样的结构就跳转登录页面。。。

这里简单说一下,我们可以通过Intent对象获取调用的scheme的host等信息

this.getIntent().getScheme();//获得Scheme名称  
this.getIntent().getDataString();//获得Uri全部路径 

3)通过服务器下发跳转路径跳转相应页面

startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("uumobile://yongche/123123123")));

这里的”uumobile://yongche/123123123”就是服务器下发的跳转路径,当我们执行startActivity的时候就会调起NativeAppActivity,然后我们通过在NativeAppActivity解析scheme的内容,跳转相应的页面。

4)通过在H5页面的锚点跳转相应的页面

@Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        //解析scheme
        if (url.indexOf(H5Constant.SCHEME) != -1) {
            try {
                Uri uri = Uri.parse(url);
                String[] urlSplit = url.split("\\?");
                Map<String, String> queryMap = new HashMap<String, String>();
                String h5Url = null;
                if (urlSplit.length == 2) {
                    queryMap = H5Constant.parseUriQuery(urlSplit[1]);
                    h5Url = queryMap.get(H5Constant.MURL);
                }
                // 跳转NativeAppActivity解析
                {
                    // 若设置刷新,则刷新页面
                    if (queryMap.containsKey(H5Constant.RELOADPRE) && "1".equals(queryMap.get(H5Constant.RELOADPRE))) {
                        h5Fragment.isNeedFlushPreH5 = true;
                    }
                    Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                    h5Activity.startActivityForResult(intent, H5Constant.h5RequestCode);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
        // 打电话
        else if (url.indexOf("tel://") != -1) {
            final String number = url.substring("tel://".length());
            Config.callPhoneByNumber(h5Activity, number);
            return true;
        } else if (url.indexOf("tel:") != -1) {
            final String number = url.substring("tel:".length());
            Config.callPhoneByNumber(h5Activity, number);
            return true;
        }
        // 其他跳转方式
        else {
            view.loadUrl(url);
            //如果不需要其他对点击链接事件的处理返回true,否则返回false
            return false;
        }
    }

可以发现我们为Webview设置了WebViewClient,并重写了WebViewClient的shouldOverrideUrlLoading方法,然后我们解析锚点的url,

并根据解析的内容调起NativeAppActivity的scheme Activity,然后在NativeAppActivity中解析scheme的内容并跳转相应的页面。

5)根据服务器下发通知栏消息,App跳转相应的页面

public class NotificationActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        L.i("接收到通知点击事件...");
        Intent realIntent = getIntent().getParcelableExtra(NotifeConstant.REAL_INTENT);
        // 解析scheme并跳转
        gotoRealScheme(this, realIntent);
    }


    /**
     * notification中跳转SCHEME,根据有效时间判断跳转URL地址
     *  跳转之后更具网络请求判断用户当前状态
     */
    private void gotoRealScheme(Context context, Intent realIntent) {
        if (realIntent == null || context == null) {
            finish();
            return;
        }
        try {
            L.i("开始解析通知中的参数...");
            long startShowTime = realIntent.getLongExtra(NotifeConstant.START_SHOW_TIME, 0);
            // 有效期时间,单位:s(秒)
            long validTime = realIntent.getLongExtra(NotifeConstant.VALID_TIME, 0);
            long currentTime = System.currentTimeMillis();
            String validActionUrl = realIntent.getStringExtra(NotifeConstant.VALID_ACTION_URL);
            String invalidActionUrl = realIntent.getStringExtra(NotifeConstant.INVALID_ACTION_URL);
            Intent schemeIntent;
            L.i("开始根据URL构建Intent对象...");
            if ((currentTime - startShowTime) / 1000L <= validTime) {
                schemeIntent = H5Constant.buildSchemeFromUrl(validActionUrl);
            } else {
                schemeIntent = H5Constant.buildSchemeFromUrl(invalidActionUrl);
            }
            if (schemeIntent != null) {
                // 设置当前页面为通知栏打开
                Config.isNotificationOpen = true;
                context.startActivity(schemeIntent);
                finish();
                //对通知栏点击事件统计
                MobclickAgent.onEvent(context, UMCountConstant.PUSH_NOTIFICATION_CLICK);
            } else {
                finish();
            }
        } catch (Exception e) {
            // 异常情况下退出当前Activity
            finish();
        }
    }
}

服务器下发的所有的通知都是先跳转这里的NotificationActivity,然后在这里执行跳转其他Activity的逻辑,而这里的H5Constant的buildSchemeFromUrl方法就是构造跳转页面Intent对象的,

我们可以看一buildSchemeFromUrl方法的具体实现:

/**
     * 从scheme的url中构建出Intent,用于界面跳转
     *
     * @param url
     * @return
     */
    public static Intent buildSchemeFromUrl(String url) {
        if (url != null && url.indexOf(H5Constant.SCHEME) != -1) {
            Uri uri = Uri.parse(url);
            String[] urlSplit = url.split("\\?");
            Map<String, String> queryMap = new HashMap<String, String>();
            String h5Url = null;
            if (urlSplit.length == 2) {
                queryMap = H5Constant.parseUriQuery(urlSplit[1]);
                h5Url = queryMap.get(H5Constant.MURL);
            }
            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
            if (!TextUtils.isEmpty(h5Url)) {
                intent.putExtra(H5Constant.MURL, h5Url);
            }
            return intent;
        }
        return null;
    }

这样我们就搞构造除了跳转NativeAppActivity的Intent对象,并将scheme字符串传递给了NativeAppActivity,这样在NativeAppActivity中就可以解析scheme字符串并执行相应的跳转逻辑了。

总结: 
Android中的scheme是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面; 
通过scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等

3.Fragment 讲解

3.1 Fragment 的认识

【说明】在使用的当中,fragment的使用也比较多,fragment更节省内存,切换的效果更好,具有自己的生命周期;

              但是:fragment非对立,需要嵌在Acitivity中使用

3.2 fragment加载到Activity的两种方式-静态加载和动态加载

【常用的是动态加载】

3.3 FragmentPagerAdapter和FragmentStatePagerAdapter的区别

【说明】前者使用到页面较少的时候,后者使用到页面较多的时候;

在页面切换的时候,FragmentPagerAdapter只是将页面分离,没有将销毁内存中的数据

FragmentStatePagerAdapter在页面切换的时候,会将页面的数据内存中销毁

3.4 fragment的生命周期

 

 

上面只是展示了 Fragment 与 Activity 生命周期最基本的关系,如果通过 addToBackStack() 将 Fragment 放入回退栈,

然后通过 popBackStack() 出栈,Fragment 的生命周期会如何变化呢?

如果 Fragment 与 ViewPager 结合使用,Fragment 的生命周期又是如何?如果通过 hide() 和 show() 方法来展示隐藏,这时 Fragment 的生命周期

本文将对Android开发中的Activity&Fragment生命周期进行全面解析

Fragment的生命周期

先来看张官方说明图

Fragment的生命周期

详细解读每个方法的调用场景

  • onAttach方法 
    Fragment和Activity建立关联的时候调用(获得activity的传递的值)
  • onCreateView方法 
    为Fragment创建视图(加载布局)时调用(给当前的fragment绘制UI布局,可以使用线程更新UI)
  • onActivityCreated方法 
    当Activity中的onCreate方法执行完后调用(表示activity执行oncreate方法完成了的时候会调用此方法)
  • onDestroyView方法 
    Fragment中的布局被移除时调用(表示fragment销毁相关联的UI布局)
  • onDetach方法 
    Fragment和Activity解除关联的时候调用(脱离activity)

fragment生命周期解析

  • 当一个fragment被创建的时候: 
    onAttach() 
    onCreate() 
    onCreateView() 
    onActivityCreated()

  • 当这个fragment对用户可见的时候,它会经历以下状态。 
    onStart() 
    onResume()

  • 当这个fragment进入“后台模式”的时候,它会经历以下状态。 
    onPause() 
    onStop()

  • 当这个fragment被销毁了(或者持有它的activity被销毁了): 
    onPause() 
    onStop() 
    onDestroyView() 
    onDestroy() 
    onDetach()

  • 就像Activity一样,在以下的状态中,可以使用Bundle对象保存一个fragment的对象。 
    onCreate() 
    onCreateView() 
    onActivityCreated()

其他场景的调用

  • 屏幕灭掉 /回到桌面 
    onPause() onSaveInstanceState() onStop()

  • 屏幕解锁 /回到应用 
    onStart() onResume()

  • 切换到其他Fragment 
    onPause() onStop() onDestroyView()

  • 切换回本身的Fragment 
    onCreateView() onActivityCreated() onStart() onResume()

  • 退出应用 
    onPause() onStop() onDestroyView() onDestroy() onDetach()

Fragment和Activity的生命周期很相似,以下是对比图

Fragment和Activity的生命周期对比

3.5 fragment的通信

3.6 fragment的增加删除替换

一直想总结一下Fragment与Fragment、Activity通信的问题,今天有时间一共总结了三种,权当抛砖引玉

如果大家还有更好的方式来实现Fragment和Fragment、Activity的通信,欢迎提出来,我们一起学习。好了,我们先来看看今天要实现的一个效果图:

左边是一个Fragment,右边是一个Fragment,当我们点击左边的Fragment的时候,右边的Fragment中显示出我们所点击的人的作品,我们就来看看怎样实现两个Fragment之间的通信。

1.直接在一个Fragment中调用另外一个Fragment中的方法

我们可以直接在一个Fragment中调用另外一个Fragment的公开方法,前提是要先拿到另外一个Fragment的实例,我们先来看看怎样在左边的Fragment中拿到右边Fragment的实例:

  1. ContentFragment cf = (ContentFragment) getActivity()  
  2.                             .getFragmentManager().findFragmentById(  
  3.                                     R.id.content_fg);  
  4.                     cf.showPro(name);  

我们通过宿主Activity拿到FragmentManager,进而再拿到右边的Fragment,然后调用右边Fragment里边的showPro方法,其中要传入的参数是左边点击的人名,我们看看右边Fragment中的showPro方法:

  1. public void showPro(String key) {  
  2.     list = map.get(key);  
  3.     adapter = new ArrayAdapter<String>(getActivity(),  
  4.             android.R.layout.simple_list_item_1, list);  
  5.     lv.setAdapter(adapter);  
  6. }  

先根据传进来的人名拿到该人作品的数据集,然后构造一个adapter赋值给listview,很简单吧。

使用这种方式我们可以直接在一个Fragment中调用另一个Fragment的公开方法,从而实现两个Fragment的通信。

这种方法适于那些我们在布局文件中就已经定义了的Fragment,这种Fragment每个都有id,可以通过FragmentManager找到,但是如果我们使用了ViewPager,即每个Fragment都是动态添加进来的,

这个时候我们无法通过FragmentManager获得另外一个Fragment的实例,那么该怎么办呢?这时我们就要用到下面这种方式了。

2.使用接口

接口可以实现两个Fragment之间的通信,也可以实现Fragment和Activity之间的通信,这大概是用的比较多的一种方式,也是个人比较推荐的一种方式,使用接口来实现两个Fragment之间通信,
要通过宿主Activity中转一下,如果是Fragment和宿主Activity通信则直接调用即可,我们先看看上图中点击韩愈的时候怎么出现他的作品。
首先在左边的Fragment中定义一个接口:
  1. public interface showPro {  
  2.     public void showProByName(String name);  
  3. }  
然后定义一个接口变量:
  1. private showPro mCallback; 
    1. @Override  
    2. public void onAttach(Activity activity) {  
    3.     super.onAttach(activity);  
    4.     if (activity != null) {  
    5.         mCallback = (showPro) activity;  
    6.     }  
    7. }
我们要在宿主Activity中实现这个接口
当然,这个方法的具体实现在宿主Activity中,当宿主Activity实现了showPro接口之后,接着就要实现它里边的方法了:
  1. public class MainActivity extends Activity implements showPro {  
  2.   
  3.     private ContentFragment cf;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         getActionBar().hide();  
  10.         cf = (ContentFragment) getFragmentManager().findFragmentById(  
  11.                 R.id.content_fg);  
  12.     }  
  13.   
  14.     @Override  
  15.     public void showProByName(String name) {  
  16.         cf.showPro(name);  
  17.     }  
  18. }  
这样当右侧的Fragment调用onAttach方法时我们就可以实例化这个接口了

 

右侧的fragment中实现了被MainActivity调用的方法:showPro();
public void showPro(String key) {  
list = map.get(key); //此处使用的hashMap存储的数据;
adapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, list);
lv.setAdapter(adapter);
}
当mCallback实例化之后,那么我们在点击韩愈的时候就可以调用这里边的showProByName方法了:
  1. lv.setOnItemClickListener(new OnItemClickListener() {  
  2.   
  3.     @Override  
  4.     public void onItemClick(AdapterView<?> parent, View view,  
  5.             int position, long id) {  
  6.         TextView nameTV = (TextView) view;  
  7.         String name = nameTV.getText().toString();  
  8.         if ("韩愈".equals(name)) {  
  9.             mCallback.showProByName(name);  
  10.         }  
  11.     }  
  12. });  
我们在接口的方法中调用右边Fragment中的showPro方法,并将当前人名作为参数传入,这个方法与1中相同,我就不贴代码了。这个方法与1中介绍的方法相比,虽然有点麻烦,
但是可以有效的解决在一个Fragment中拿不到另一个Fragment实例的问题,
具体应用场景就是ViewPager中的Fragment之间通信。

3.使用广播

不论我们有没有用ViewPager,都可以用广播实现两个Fragment之间的通信,广播算是这里最灵活的通信方式了,我们看看在左边Fragment中发送广播:
  1. Intent intent = new Intent("showPro");  
  2.                     intent.putExtra("name", name);  
  3.                     LocalBroadcastManager.getInstance(getActivity())  
  4.                             .sendBroadcast(intent);  

在右边Fragment中接收广播:
  1. LocalBroadcastManager localBroadcastManager = LocalBroadcastManager  
  2.         .getInstance(getActivity());  
  3. IntentFilter intentFilter = new IntentFilter();  
  4. intentFilter.addAction("showPro");  
  5. BroadcastReceiver br = new BroadcastReceiver() {  
  6.   
  7.     @Override  
  8.     public void onReceive(Context context, Intent intent) {  
  9.         String key = intent.getStringExtra("name");  
  10.         list = map.get(key);  
  11.         adapter = new ArrayAdapter<String>(getActivity(),  
  12.                 android.R.layout.simple_list_item_1, list);  
  13.         lv.setAdapter(adapter);  
  14.     }  
  15.   
  16. };  
  17. localBroadcastManager.registerReceiver(br, intentFilter);  
这些都是广播基本的使用方法。
 
个人觉得,虽然广播用起来最方便,但是在选择使用哪种通信方式的时候,优先选择上面两种,如果前两种方式都不可以,再考虑使用广播。
【源码】com.lenve.fragment.NameFragment
  1 package com.lenve.fragment;
  2 
  3 import android.app.Activity;
  4 import android.app.Fragment;
  5 import android.content.Intent;
  6 import android.os.Bundle;
  7 import android.support.v4.content.LocalBroadcastManager;
  8 import android.view.LayoutInflater;
  9 import android.view.View;
 10 import android.view.ViewGroup;
 11 import android.widget.AdapterView;
 12 import android.widget.AdapterView.OnItemClickListener;
 13 import android.widget.ArrayAdapter;
 14 import android.widget.ListView;
 15 import android.widget.TextView;
 16 
 17 import java.util.ArrayList;
 18 import java.util.List;
 19 
 20 public class NameFragment extends Fragment {
 21 
 22     private List<String> names;
 23     private ListView lv;
 24     private showPro mCallback;
 25     private ArrayAdapter<String> adapter;
 26 
 27     public interface showPro {
 28         public void showProByName(String name);
 29     }
 30 
 31     @Override
 32     public void onCreate(Bundle savedInstanceState) {
 33         super.onCreate(savedInstanceState);
 34         initData();
 35     }
 36 
 37     private void initData() {
 38         names = new ArrayList<String>();
 39         names.add("韩愈");
 40         names.add("柳宗元");
 41         names.add("苏轼");
 42         names.add("苏辙");
 43         names.add("苏洵");
 44         names.add("欧阳修");
 45         names.add("曾巩");
 46         names.add("王安石");
 47     }
 48 
 49     @Override
 50     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 51             Bundle savedInstanceState) {
 52         View v = inflater.inflate(R.layout.name_fg, null);
 53         initLv(v);
 54         return v;
 55     }
 56 
 57     private void initLv(View v) {
 58         lv = (ListView) v.findViewById(R.id.name_lv);
 59         adapter = new ArrayAdapter<String>(getActivity(),
 60                 android.R.layout.simple_list_item_1, names);
 61         lv.setAdapter(adapter);
 62         lv.setOnItemClickListener(new OnItemClickListener() {
 63 
 64             @Override
 65             public void onItemClick(AdapterView<?> parent, View view,
 66                     int position, long id) {
 67                 TextView nameTV = (TextView) view;
 68                 String name = nameTV.getText().toString();
 69 
 70                 if ("韩愈".equals(name)) {
 71 
 72                     mCallback.showProByName(name);
 73 
 74                 } else if ("柳宗元".equals(name)) {
 75                     ContentFragment cf = (ContentFragment) getActivity()
 76                             .getFragmentManager().findFragmentById(
 77                                     R.id.content_fg);
 78                     cf.showPro(name);
 79 
 80                 } else if ("苏轼".equals(name) || "苏辙".equals(name)) {
 81                     Intent intent = new Intent("showPro");
 82                     intent.putExtra("name", name);
 83                     LocalBroadcastManager.getInstance(getActivity())
 84                             .sendBroadcast(intent);
 85 
 86                 } else if ("苏洵".equals(name)) {
 87                     ((MainActivity) getActivity()).showProByName(name);
 88                 }
 89             }
 90         });
 91     }
 92 
 93     @Override
 94     public void onAttach(Activity activity) {
 95         super.onAttach(activity);
 96         if (activity != null) {
 97             mCallback = (showPro) activity;
 98         }
 99     }
100 
101     public void clearData() {
102         names = new ArrayList<String>();
103         adapter = new ArrayAdapter<String>(getActivity(),
104                 android.R.layout.simple_list_item_1, names);
105         lv.setAdapter(adapter);
106     }
107 }

 【源码】com.lenve.fragment.ContentFragment

  1 package com.lenve.fragment;
  2 
  3 import java.util.ArrayList;
  4 import java.util.HashMap;
  5 import java.util.List;
  6 import java.util.Map;
  7 
  8 import android.app.Fragment;
  9 import android.content.BroadcastReceiver;
 10 import android.content.Context;
 11 import android.content.Intent;
 12 import android.content.IntentFilter;
 13 import android.os.Bundle;
 14 import android.support.v4.content.LocalBroadcastManager;
 15 import android.view.LayoutInflater;
 16 import android.view.View;
 17 import android.view.ViewGroup;
 18 import android.widget.ArrayAdapter;
 19 import android.widget.ListView;
 20 
 21 public class ContentFragment extends Fragment {
 22 
 23     private Map<String, List<String>> map;
 24     private List<String> list;
 25     private ListView lv;
 26     private ArrayAdapter<String> adapter;
 27 
 28     @Override
 29     public void onCreate(Bundle savedInstanceState) {
 30         super.onCreate(savedInstanceState);
 31         initData();
 32         initBroadcast();
 33     }
 34 
 35     @Override
 36     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 37             Bundle savedInstanceState) {
 38         View v = inflater.inflate(R.layout.content_fg, null);
 39         initLv(v);
 40         return v;
 41     }
 42 
 43     private void initLv(View v) {
 44         lv = (ListView) v.findViewById(R.id.content_lv);
 45         list = new ArrayList<String>();
 46         adapter = new ArrayAdapter<String>(getActivity(),
 47                 android.R.layout.simple_list_item_1, list);
 48         lv.setAdapter(adapter);
 49     }
 50 
 51     private void initData() {
 52         map = new HashMap<String, List<String>>();
 53         list = new ArrayList<String>();
 54         list.add("师说");
 55         list.add("马说");
 56         list.add("早春呈水部张十八员外");
 57         map.put("韩愈", list);
 58         list = new ArrayList<String>();
 59         list.add("小石潭记");
 60         list.add("江雪");
 61         list.add("捕蛇者说");
 62         list.add("小石城山记");
 63         map.put("柳宗元", list);
 64         list = new ArrayList<String>();
 65         list.add("水调歌头");
 66         list.add("念奴娇·赤壁怀古");
 67         list.add("江城子·密州出猎");
 68         list.add("赤壁赋");
 69         list.add("题西林壁");
 70         map.put("苏轼", list);
 71         list = new ArrayList<String>();
 72         list.add("黄州快哉亭记");
 73         list.add("上枢密韩太尉书");
 74         map.put("苏辙", list);
 75         list = new ArrayList<String>();
 76         list.add("六国论");
 77         list.add("九日和韩魏公");
 78         list.add("心术");
 79         list.add("管仲论");
 80         map.put("苏洵", list);
 81     }
 82 
 83     public void showPro(String key) {
 84         list = map.get(key);
 85         adapter = new ArrayAdapter<String>(getActivity(),
 86                 android.R.layout.simple_list_item_1, list);
 87         lv.setAdapter(adapter);
 88     }
 89 
 90     private void initBroadcast() {
 91         LocalBroadcastManager localBroadcastManager = LocalBroadcastManager
 92                 .getInstance(getActivity());
 93         IntentFilter intentFilter = new IntentFilter();
 94         intentFilter.addAction("showPro");
 95         BroadcastReceiver br = new BroadcastReceiver() {
 96 
 97             @Override
 98             public void onReceive(Context context, Intent intent) {
 99                 String key = intent.getStringExtra("name");
100                 list = map.get(key);
101                 adapter = new ArrayAdapter<String>(getActivity(),
102                         android.R.layout.simple_list_item_1, list);
103                 lv.setAdapter(adapter);
104             }
105 
106         };
107         localBroadcastManager.registerReceiver(br, intentFilter);
108     }
109 }

 

【源码】com.lenve.fragment.MainActivity
 1 package com.lenve.fragment;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.view.View;
 6 
 7 import com.lenve.fragment.NameFragment.showPro;
 8 
 9 public class MainActivity extends Activity implements showPro {
10 
11     private ContentFragment cf;
12     private NameFragment nf;
13 
14     @Override
15     protected void onCreate(Bundle savedInstanceState) {
16         super.onCreate(savedInstanceState);
17         setContentView(R.layout.activity_main);
18         getActionBar().hide();
19         cf = (ContentFragment) getFragmentManager().findFragmentById(
20                 R.id.content_fg);
21         nf = (NameFragment) getFragmentManager().findFragmentById(R.id.name_fg);
22     }
23 
24     @Override
25     public void showProByName(String name) {
26         cf.showPro(name);
27     }
28 
29     public void onClick(View v) {
30         switch (v.getId()) {
31         case R.id.clear_data:
32             nf.clearData();
33             break;
34 
35         default:
36             break;
37         }
38     }
39 }

4.Fragment直接调用Activity中的public方法

我们也可以直接在Fragment中调用Activity中的公开方法,如下:
  1. ((MainActivity) getActivity()).showProByName(name);  
这里的showProByName就是我们上文贴出来的那个MainActivity中的方法。

3.3 使用Bundle

但是还可以用另外一种方法,就是用官方提供的Bundler去实现Activity传递数据到Fragment. 
Activity(传递数据):
public class MainActivity extends Activity {
    private FragmentManager mManager;
    private  Button mBtnPutData;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mBtnPutData = (Button) findViewById(R.id.btn_putData);

        //默认显示的Fragment
        mManager= getFragmentManager();
        FragmentTransaction transaction = mManager.beginTransaction();
        RightFragment fragment = new RightFragment();
        transaction.replace(R.id.fl_content, fragment);
        transaction.commit();

        //向RightFragment传值
        mBtnPutData.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                FragmentTransaction transaction = mManager.beginTransaction();
                RightFragment fragment = new RightFragment();
                //创建Bundle对象
                Bundle bundle = new Bundle();
                //向Bundle输入数据
                bundle.putInt("data", 100);
                //对fragment设置Bundle参数
                fragment.setArguments(bundle);
                transaction.replace(R.id.fl_content, fragment);
                transaction.commit();
            }
        });
    }
}

Fragment(接收数据)

public class RightFragment extends Fragment{
    private EditText mEtData;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
        View  inflate = inflater.inflate(R.layout.right_fragment, container,false);
        mEtData = (EditText) inflate.findViewById(R.id.et_data);
        //获取Activity传递的Bundle对象
        Bundle bundle = getArguments();
        if(bundle != null){
            //从bundle里面获取key为data的int类型数值
            int data = bundle.getInt("data");
            //将值设置到控件上
            et_data.setText("data="+data);
        }
        return inflate;
    }
}

4.Service

4.1 Service是什么?

【说明】

【1】service可以被Activity/broadcast等等启动,一旦启动,就会一直在后台运行;即使启动的控件已经销毁,但是service仍然存在;

【2】service可以与Activity进行绑定,然后进行数据交互;

【3】service与Activity是在不同的进程中,可以跨进程通信;

【4】service和broadcast都运行在主线程,不能长时间的做耗时的操作;---必须注意

 

4.2 service和Thread的区别

【说明】

【1】service运行是Android特有的,依托于主线程进行操作,非独立;

         而Thread是程序执行的最小单元,线程、可以执行一些异步的操作,可以自己独立进行运行的;

【2】service和Thread没有任何的关联;service运行在主线程,不能做耗时的操作;Thread是独立的子线程,可以独立的运行;两者是完全不同的;

【3】证明service和主线程是运行在同一个线程中的;

 

【4】 service 和Thread不能联系在一起;服务和后台也是两个不同概念;

【5】service运行做耗时的操作,也需要一定要开启单独的子线程;

 【问】为什么在service中创建子线程操作,而不在Activity中创建子线程?

【非常非常重要】Activity很难对子线程Thread进行控制;特别是当Activity被销毁的时候,无法对子线程进行控制;

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

 【说明】service创建后台任务是安全的,service可以完全对后台进行控制;

 【6】耗时的操作需要放到Thread中;service:长时间在后台运行,不需要与UI交互,可以使用service;

【service的作用】:
(1).它用于处理一些不干扰用户使用的后台操作。如下载,网络获取。播放音乐,他可以通过INTENT来开启,同时也可以绑定到宿主对象(调用者例如ACTIVITY上)来使用。 
(2).如果说Activity是显示前台页面的信息,那么Service就是在后台进行操作的。如果Service和前台UI进行交互的话可以通过发送广播或者通知栏的方式。
【启动方式】: 
(1).Service自己不能运行,需要通过某一个Activity或者其它Context对象来调用。 
(2).Service的启动方式有两种: 
Context.startService()和Context.bindService()两种方式启动Service。
如果在service的onCreate()方法或者onStart()方法中有耗时的操作,要新开启一个线程。
在需要service的地方通过以上两种方式来启动。 注意: 平常使用多的是startService方法,可以把一些耗时的任务放到后台去处理,当处理完成后,可以通过广播或者通知栏来通知前台。

4.3 Service创建的两种方式?

【说明】通过Activity启动service,即使Activity销毁了,但是service是仍然可以在后台运行的;

4.3.1 startIntent方式

4.3.2 Bindservice方法

【说明】

【1】多个Activity可以绑定一个Service,

【2】提供了客户端和服务端的接口,允许activity和service进行数据的交互和请求的发送等等; 

【3】如果Activity处于不同的进程中时,可以完成进程间的通信;

【4】所有的操作要在Acitivity绑定到service之后才能运行;

【5】如果activity销毁,则service自动销毁,不需要手动调用stopservice;

【说明】客户端指的是activity,服务端指的是service;

【转载】

第一种方式:通过StartService启动Service

通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。

要创建一个这样的Service,你需要让该类继承Service类,然后重写以下方法:

  • onCreate()
    1.如果service没被创建过,调用startService()后会执行onCreate()回调;
    2.如果service已处于运行中,调用startService()不会执行onCreate()方法。
    也就是说,onCreate()只会在第一次创建service时候调用,多次执行startService()不会重复调用onCreate(),此方法适合完成一些初始化工作。

  • onStartCommand()
    如果多次执行了Context的startService()方法,那么Service的onStartCommand()方法也会相应的多次调用。

  • onStartCommand()方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。

  • onBind()
    Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。

  • onDestory()
    在销毁的时候会执行Service该方法。

这几个方法都是回调方法,且在主线程中执行,由android操作系统在合适的时机调用。

startService代码实例

创建TestOneService,并在manifest里注册。

需要注意,项目中的每一个Service都必须在AndroidManifest.xml中注册才行,所以还需要编辑AndroidManifest.xml文件,代码如下所示:

[html] view plain copy
 
 <?xml version="1.0" encoding="utf-8"?>  
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     package="com.example.servicetest"  
  3.     android:versionCode="1"  
  4.     android:versionName="1.0" >  
  5.   
  6.     <uses-sdk  
  7.         android:minSdkVersion="14"  
  8.         android:targetSdkVersion="17" />  
  9.   
  10.     <application  
  11.         android:allowBackup="true"  
  12.         android:icon="@drawable/ic_launcher"  
  13.         android:label="@string/app_name"  
  14.         android:theme="@style/AppTheme" >  
  15.           
  16.     ……  
  17.   
  18.         <service android:name="com.example.servicetest.TestOneService" >  
  19.         </service>  
  20.     </application>  
  21.   
  22. </manifest>  

 

在MainActivty中操作TestOneService,code如下:

[java] view plain copy
 
  1.  
  2.   
  3. public class TestOneService extends Service{  
  4.   
  5.     @Override  
  6.     public void onCreate() {  
  7.         Log.i("Kathy","onCreate - Thread ID = " + Thread.currentThread().getId());  
  8.         super.onCreate();  
  9.     }  
  10.   
  11.     @Override  
  12.     public int onStartCommand(Intent intent, int flags, int startId) {  
  13.         Log.i("Kathy", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId());  
  14.         return super.onStartCommand(intent, flags, startId);  
  15.     }  
  16.   
  17.     @Nullable  
  18.     @Override  
  19.     public IBinder onBind(Intent intent) {  
  20.         Log.i("Kathy", "onBind - Thread ID = " + Thread.currentThread().getId());  
  21.         return null;  
  22.     }  
  23.   
  24.     @Override  
  25.     public void onDestroy() {  
  26.         Log.i("Kathy", "onDestroy - Thread ID = " + Thread.currentThread().getId());  
  27.         super.onDestroy();  
  28.     }  
  29. }  

 

在MainActivity中三次startService,之后stopService。

[java] view plain copy
 
  1. /** 
  2.  * Created by Kathy on 17-2-6. 
  3.  */  
  4.   
  5. public class MainActivity extends AppCompatActivity {  
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         Log.i("Kathy", "Thread ID = " + Thread.currentThread().getId());  
  12.         Log.i("Kathy", "before StartService");  
  13.   
  14.         //连续启动Service  
  15.         Intent intentOne = new Intent(this, TestOneService.class);  
  16.         startService(intentOne);  
  17.         Intent intentTwo = new Intent(this, TestOneService.class);  
  18.         startService(intentTwo);  
  19.         Intent intentThree = new Intent(this, TestOneService.class);  
  20.         startService(intentThree);  
  21.   
  22.         //停止Service  
  23.         Intent intentFour = new Intent(this, TestOneService.class);  
  24.         stopService(intentFour);  
  25.   
  26.         //再次启动Service  
  27.         Intent intentFive = new Intent(this, TestOneService.class);  
  28.         startService(intentFive);  
  29.   
  30.         Log.i("Kathy", "after StartService");  
  31.     }  
  32. }  

打印出的Log如下

 
  1. 02-06 15:19:45.090 8938-8938/? I/Kathy: Thread ID = 1  
  2. 02-06 15:19:45.090 8938-8938/? I/Kathy: before StartService  
  3. 02-06 15:19:45.233 8938-8938/? I/Kathy: onCreate - Thread ID = 1  
  4. 02-06 15:19:45.234 8938-8938/? I/Kathy: onStartCommand - startId = 1, Thread ID = 1  
  5. 02-06 15:19:45.234 8938-8938/? I/Kathy: onStartCommand - startId = 2, Thread ID = 1  
  6. 02-06 15:19:45.235 8938-8938/? I/Kathy: onStartCommand - startId = 3, Thread ID = 1  
  7. 02-06 15:19:45.236 8938-8938/? I/Kathy: onDestroy - Thread ID = 1  
  8. 02-06 15:19:45.237 8938-8938/? I/Kathy: onCreate - Thread ID = 1  
  9. 02-06 15:19:45.237 8938-8938/? I/Kathy: onStartCommand - startId = 1, Thread ID = 1  
  10. 02-06 15:19:45.238 8938-8938/? I/Kathy: after StartService  

分析:
1.主线程打印出是1,所有回调方法中打印出的执行线程ID都是1,证明回调方法都是在主线程中执行的。
2.三次调用startService,只触发一次onCreate回调,触发了三次onStartCommand回调,且startId分别为1,2,3。证明 多次startService不会重复执行onCreate回调,但每次都会执行onStartCommand回调。

第二种方式:通过bindService启动Service

bindService启动服务特点:
1.bindService启动的服务和调用者之间是典型的client-server模式。调用者是client,service则是server端。service只有一个,但绑定到service上面的client可以有一个或很多个。这里所提到的client指的是组件,比如某个Activity。
2.client可以通过IBinder接口获取Service实例,从而实现在client端直接调用Service中的方法以实现灵活交互,这在通过startService方法启动中是无法实现的。
3.bindService启动服务的生命周期与其绑定的client息息相关。当client销毁时,client会自动与Service解除绑定。当然,client也可以明确调用Context的unbindService()方法与Service解除绑定。当没有任何client与Service绑定时,Service会自行销毁。

bindService代码实例

交互界面设计如下:


ActivityA界面布局.png

1.创建一个TestTwoService继承Service(Server)
2.创建ActivityA,可以通过bindService绑定服务(client)
3.创建ActivityB,可以通过bindService绑定服务(client)
4.ActivityA可以跳转到ActivityB

TestTwoService创建如下:
要想让Service支持bindService调用方式,需要做以下事情:
1.在Service的onBind()方法中返回IBinder类型的实例。
2.onBInd()方法返回的IBinder的实例需要能够返回Service实例本身。通常,最简单的方法就是在service中创建binder的内部类,加入类似getService()的方法返回Service,这样绑定的client就可以通过getService()方法获得Service实例了。

[java] view plain copy
  1.  
  2.   
  3. public class TestTwoService extends Service{  
  4.   
  5.     //client 可以通过Binder获取Service实例  
  6.     public class MyBinder extends Binder {  
  7.         public TestTwoService getService() {  
  8.             return TestTwoService.this;  
  9.         }  
  10.     }  
  11.   
  12.     //通过binder实现调用者client与Service之间的通信  
  13.     private MyBinder binder = new MyBinder();  
  14.   
  15.     private final Random generator = new Random();  
  16.   
  17.     @Override  
  18.     public void onCreate() {  
  19.         Log.i("Kathy","TestTwoService - onCreate - Thread = " + Thread.currentThread().getName());  
  20.         super.onCreate();  
  21.     }  
  22.   
  23.     @Override  
  24.     public int onStartCommand(Intent intent, int flags, int startId) {  
  25.         Log.i("Kathy", "TestTwoService - onStartCommand - startId = " + startId + ", Thread = " + Thread.currentThread().getName());  
  26.         return START_NOT_STICKY;  
  27.     }  
  28.   
  29.     @Nullable  
  30.     @Override  
  31.     public IBinder onBind(Intent intent) {  
  32.         Log.i("Kathy", "TestTwoService - onBind - Thread = " + Thread.currentThread().getName());  
  33.         return binder;  
  34.     }  
  35.   
  36.     @Override  
  37.     public boolean onUnbind(Intent intent) {  
  38.         Log.i("Kathy", "TestTwoService - onUnbind - from = " + intent.getStringExtra("from"));  
  39.         return false;  
  40.     }  
  41.   
  42.     @Override  
  43.     public void onDestroy() {  
  44.         Log.i("Kathy", "TestTwoService - onDestroy - Thread = " + Thread.currentThread().getName());  
  45.         super.onDestroy();  
  46.     }  
  47.   
  48.     //getRandomNumber是Service暴露出去供client调用的公共方法  
  49.     public int getRandomNumber() {  
  50.         return generator.nextInt();  
  51.     }  
  52. }  

client端要做的事情:
1.创建ServiceConnection类型实例,并重写onServiceConnected()方法和onServiceDisconnected()方法。
2.当执行到onServiceConnected回调时,可通过IBinder实例得到Service实例对象,这样可实现client与Service的连接。
3.onServiceDisconnected回调被执行时,表示client与Service断开连接,在此可以写一些断开连接后需要做的处理。

创建ActivityA,代码如下

[java] view plain copy
  1. /** 
  2.  * Created by Kathy on 17-2-6. 
  3.  */  
  4. public class ActivityA extends Activity implements Button.OnClickListener {  
  5.     private TestTwoService service = null;  
  6.     private boolean isBind = false;  
  7.   
  8.     private ServiceConnection conn = new ServiceConnection() {  
  9.         @Override  
  10.         public void onServiceConnected(ComponentName name, IBinder binder) {  
  11.             isBind = true;  
  12.             TestTwoService.MyBinder myBinder = (TestTwoService.MyBinder) binder;  
  13.             service = myBinder.getService();  
  14.             Log.i("Kathy", "ActivityA - onServiceConnected");  
  15.             int num = service.getRandomNumber();  
  16.             Log.i("Kathy", "ActivityA - getRandomNumber = " + num);  
  17.         }  
  18.   
  19.         @Override  
  20.         public void onServiceDisconnected(ComponentName name) {  
  21.             isBind = false;  
  22.             Log.i("Kathy", "ActivityA - onServiceDisconnected");  
  23.         }  
  24.     };  
  25.   
  26.     protected void onCreate(Bundle savedInstanceState) {  
  27.         super.onCreate(savedInstanceState);  
  28.         setContentView(R.layout.activity_a);  
  29.         Log.i("Kathy", "ActivityA - onCreate - Thread = " + Thread.currentThread().getName());  
  30.   
  31.         findViewById(R.id.btnBindService).setOnClickListener(this);  
  32.         findViewById(R.id.btnUnbindService).setOnClickListener(this);  
  33.         findViewById(R.id.btnStartActivityB).setOnClickListener(this);  
  34.         findViewById(R.id.btnFinish).setOnClickListener(this);  
  35.     }  
  36.   
  37.     @Override  
  38.     public void onClick(View v) {  
  39.         if (v.getId() == R.id.btnBindService) {  
  40.             //单击了“bindService”按钮  
  41.             Intent intent = new Intent(this, TestTwoService.class);  
  42.             intent.putExtra("from", "ActivityA");  
  43.             Log.i("Kathy", "----------------------------------------------------------------------");  
  44.             Log.i("Kathy", "ActivityA 执行 bindService");  
  45.             bindService(intent, conn, BIND_AUTO_CREATE);  
  46.         } else if (v.getId() == R.id.btnUnbindService) {  
  47.             //单击了“unbindService”按钮  
  48.             if (isBind) {  
  49.                 Log.i("Kathy",  
  50.                         "----------------------------------------------------------------------");  
  51.                 Log.i("Kathy", "ActivityA 执行 unbindService");  
  52.                 unbindService(conn);  
  53.             }  
  54.         } else if (v.getId() == R.id.btnStartActivityB) {  
  55.             //单击了“start ActivityB”按钮  
  56.             Intent intent = new Intent(this, ActivityB.class);  
  57.             Log.i("Kathy",  
  58.                     "----------------------------------------------------------------------");  
  59.             Log.i("Kathy", "ActivityA 启动 ActivityB");  
  60.             startActivity(intent);  
  61.         } else if (v.getId() == R.id.btnFinish) {  
  62.             //单击了“Finish”按钮  
  63.             Log.i("Kathy",  
  64.                     "----------------------------------------------------------------------");  
  65.             Log.i("Kathy", "ActivityA 执行 finish");  
  66.             this.finish();  
  67.         }  
  68.     }  
  69.   
  70.     @Override  
  71.     protected void onDestroy() {  
  72.         super.onDestroy();  
  73.         Log.i("Kathy", "ActivityA - onDestroy");  
  74.     }  
  75. }  

创建ActivityB,代码如下:

[java] view plain copy
  1. /** 
  2.  * Created by Kathy on 17-2-6. 
  3.  */  
  4. public class ActivityB extends Activity implements Button.OnClickListener {  
  5.   
  6.     private TestTwoService service = null;  
  7.   
  8.     private boolean isBind = false;  
  9.   
  10.     private ServiceConnection conn = new ServiceConnection() {  
  11.         @Override  
  12.         public void onServiceConnected(ComponentName name, IBinder binder) {  
  13.             isBind = true;  
  14.             TestTwoService.MyBinder myBinder = (TestTwoService.MyBinder)binder;  
  15.             service = myBinder.getService();  
  16.             Log.i("Kathy", "ActivityB - onServiceConnected");  
  17.             int num = service.getRandomNumber();  
  18.             Log.i("Kathy", "ActivityB - getRandomNumber = " + num);  
  19.         }  
  20.   
  21.         @Override  
  22.         public void onServiceDisconnected(ComponentName name) {  
  23.             isBind = false;  
  24.             Log.i("Kathy", "ActivityB - onServiceDisconnected");  
  25.         }  
  26.     };  
  27.   
  28.     @Override  
  29.     protected void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.activity_b);  
  32.   
  33.         findViewById(R.id.btnBindService).setOnClickListener(this);  
  34.         findViewById(R.id.btnUnbindService).setOnClickListener(this);  
  35.         findViewById(R.id.btnFinish).setOnClickListener(this);  
  36.     }  
  37.   
  38.     @Override  
  39.     public void onClick(View v) {  
  40.         if(v.getId() == R.id.btnBindService){  
  41.             //单击了“bindService”按钮  
  42.             Intent intent = new Intent(this, TestTwoService.class);  
  43.             intent.putExtra("from", "ActivityB");  
  44.             Log.i("Kathy", "----------------------------------------------------------------------");  
  45.             Log.i("Kathy", "ActivityB 执行 bindService");  
  46.             bindService(intent, conn, BIND_AUTO_CREATE);  
  47.         }else if(v.getId() == R.id.btnUnbindService){  
  48.             //单击了“unbindService”按钮  
  49.             if(isBind){  
  50.                 Log.i("Kathy", "----------------------------------------------------------------------");  
  51.                 Log.i("Kathy", "ActivityB 执行 unbindService");  
  52.                 unbindService(conn);  
  53.             }  
  54.         }else if(v.getId() == R.id.btnFinish){  
  55.             //单击了“Finish”按钮  
  56.             Log.i("Kathy", "----------------------------------------------------------------------");  
  57.             Log.i("Kathy", "ActivityB 执行 finish");  
  58.             this.finish();  
  59.         }  
  60.     }  
  61.     @Override  
  62.     public void onDestroy(){  
  63.         super.onDestroy();  
  64.         Log.i("Kathy", "ActivityB - onDestroy");  
  65.     }  
  66. }  

测试步骤1

step1: 点击ActivityA的bindService按钮
step2: 再点击ActivityA的unbindService按钮
Log输出:


[java] view plain copy
 
  1. 02-07 14:09:38.031 1738-1738/com.demo.kathy.demo I/Kathy: ActivityA - onCreate - Thread = main  
  2. 02-07 14:09:39.488 1738-1738/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  3. 02-07 14:09:39.488 1738-1738/com.demo.kathy.demo I/Kathy: ActivityA 执行 bindService  
  4. 02-07 14:09:39.496 1738-1738/com.demo.kathy.demo I/Kathy: TestTwoService - onCreate - Thread = main  
  5. 02-07 14:09:39.497 1738-1738/com.demo.kathy.demo I/Kathy: TestTwoService - onBind - Thread = main  
  6. 02-07 14:09:39.500 1738-1738/com.demo.kathy.demo I/Kathy: ActivityA - onServiceConnected  
  7. 02-07 14:09:39.500 1738-1738/com.demo.kathy.demo I/Kathy: ActivityA - getRandomNumber = -1046987376  
  8. 02-07 14:09:50.866 1738-1738/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  9. 02-07 14:09:50.866 1738-1738/com.demo.kathy.demo I/Kathy: ActivityA 执行 unbindService  
  10. 02-07 14:09:50.870 1738-1738/com.demo.kathy.demo I/Kathy: TestTwoService - onUnbind - from = ActivityA  
  11. 02-07 14:09:50.871 1738-1738/com.demo.kathy.demo I/Kathy: TestTwoService - onDestroy - Thread = main  

总结调用bindService之后发生的事情:
1.client执行bindService()
2.如果Service不存在,则Service执行onCreate(),onBind()
3.client实例ServiceConnection执行onServiceConnected()方法

总结调用unbindService之后发生的事情:
1.client执行unbindService()
2.client与Service解除绑定连接状态
3.Service检测是否还有其他client与其连接,如果没有Service执行onUnbind()和onDestroy()

测试步骤2

step1: 点击ActivityA的bindService按钮
step2: 再点击ActivityA的Finish按钮
Log 输出

[java] view plain copy
  1. 02-07 14:49:16.626 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA - onCreate - Thread = main  
  2. 02-07 14:49:18.102 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  3. 02-07 14:49:18.102 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA 执行 bindService  
  4. 02-07 14:49:18.105 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onCreate - Thread = main  
  5. 02-07 14:49:18.110 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onBind - Thread = main  
  6. 02-07 14:49:18.112 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA - onServiceConnected  
  7. 02-07 14:49:18.112 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA - getRandomNumber = -318399886  
  8. 02-07 14:49:19.540 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  9. 02-07 14:49:19.540 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA 执行 finish  
  10. 02-07 14:49:19.789 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA - onDestroy  
  11. 02-07 14:49:19.798 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onUnbind - from = ActivityA  
  12. 02-07 14:49:19.798 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onDestroy - Thread = main  

 

总结:如果client销毁,那么client会自动与Service解除绑定。

测试步骤3

step1: 点击ActivityA的bindService按钮
step2: 点击ActivityA的startActivity B按钮,切换到ActivityB
step3: 点击ActivityB中的bindService按钮
step4: 点击ActivityB中的unbindService按钮
step5: 点击ActivityB中的Finish按钮
step6: 点击ActivityA中的unbindService按钮
得到Log:

[java] view plain copy
 
  1. 02-07 14:55:04.390 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA - onCreate - Thread = main  
  2. 02-07 14:55:08.191 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  3. 02-07 14:55:08.191 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA 执行 bindService  
  4. 02-07 14:55:08.197 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onCreate - Thread = main  
  5. 02-07 14:55:08.198 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onBind - Thread = main  
  6. 02-07 14:55:08.205 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA - onServiceConnected  
  7. 02-07 14:55:08.205 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA - getRandomNumber = -706215542  
  8. 02-07 14:55:23.261 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  9. 02-07 14:55:23.261 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA 启动 ActivityB  
  10. 02-07 14:55:29.239 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  11. 02-07 14:55:29.239 12566-12566/com.demo.kathy.demo I/Kathy: ActivityB 执行 bindService  
  12. 02-07 14:55:29.241 12566-12566/com.demo.kathy.demo I/Kathy: ActivityB - onServiceConnected  
  13. 02-07 14:55:29.241 12566-12566/com.demo.kathy.demo I/Kathy: ActivityB - getRandomNumber = 1827572726  
  14. 02-07 14:55:33.951 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  15. 02-07 14:55:33.951 12566-12566/com.demo.kathy.demo I/Kathy: ActivityB 执行 unbindService  
  16. 02-07 14:55:36.645 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  17. 02-07 14:55:36.645 12566-12566/com.demo.kathy.demo I/Kathy: ActivityB 执行 finish  
  18. 02-07 14:55:36.852 12566-12566/com.demo.kathy.demo I/Kathy: ActivityB - onDestroy  
  19. 02-07 14:55:43.137 12566-12566/com.demo.kathy.demo I/Kathy: ----------------------------------------------------------------------  
  20. 02-07 14:55:43.137 12566-12566/com.demo.kathy.demo I/Kathy: ActivityA 执行 unbindService  
  21. 02-07 14:55:43.143 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onUnbind - from = ActivityA  
  22. 02-07 14:55:43.143 12566-12566/com.demo.kathy.demo I/Kathy: TestTwoService - onDestroy - Thread = main  

总结bindService的生命周期:
1.点击ActivityA的bindService按钮
第一次调用bindService会实例化TestTwoService,然后执行其onBind()方法,得到IBinder类型的实例,将其作为参数传入ActivityA的ServiceConnection的onServiceConnected方法中,标志着ActivityA与TestTwoService建立了绑定。

2.点击ActivityB中的bindService按钮
由于TestTwoService已处于运行状态,所以再次调用bindService不会重新创建它的实例,所以也不会执行TestTwoService的onCreate()方法和onBind()方法。ActivityB与ActivityA共享IBinder实例。此时有两个client与TestTwoService绑定。

3.点击ActivityB中的unbindService按钮
ActivityB与TestTwoService解除了绑定,当没有任何client与Service绑定时,才会执行Service的onUnbind()方法。此时,ActivityA还在绑定连接中,所以不会执行Service的解绑方法。

4.点击ActivityA中的unbindService按钮
ActivityA执行unbindService之后,ActivityA与TestTwoService就解除绑定了,这样就没有client与TestTwoService绑定,这时候Android会销毁TestTwoService,在销毁前会先执行TestTwoService的onUnbind()方法,然后才会执行其onDestroy()方法,这样TestService就销毁了。

 4.4 service的生命周期

1. 生命周期常用方法

在Service的生命周期里,常用的有:

  • 4个手动调用的方法
手动调用方法作用
startService() 启动服务
stopService() 关闭服务
bindService() 绑定服务
unbindService() 解绑服务
  • 5个内部自动调用的方法
内部自动调用的方法作用
onCreat() 创建服务
onStartCommand() 开始服务
onDestroy() 销毁服务
onBind() 绑定服务
onUnbind() 解绑服务

2. 生命周期方法具体介绍

主要介绍内部调用方法和外部调用方法的关系。

2.1 startService()

  • 作用:启动Service服务
  • 手动调用startService()后,自动调用内部方法:onCreate()、onStartCommand()
  • 调用逻辑如下: 
    调用逻辑

2.2 stopService()

  • 作用:关闭Service服务
  • 手动调用stopService()后,自动调用内部方法:onDestory()
  • 调用的逻辑:+

调用逻辑

2.3 bindService()

  • 作用:绑定Service服务
  • 手动调用bindService()后,自动调用内部方法:onCreate()、onBind()
  • 调用的逻辑:

调用的逻辑

2.4 unbindService()

  • 作用:解绑Service服务
  • 手动调用unbindService()后,自动调用内部方法:onCreate()、onBind()、onDestory()
  • 调用的逻辑: 
    调用的逻辑

3. 常见的生命周期使用

3.1 只使用startService启动服务的生命周期

startService启动服务的生命周期

3.2 只使用BindService绑定服务的生命周期

BindService绑定服务的生命周期

3.3 同时使用startService()启动服务、BindService()绑定服务的生命周期

Paste_Image.png

3.4 特别注意

  • startService()和stopService()只能开启和关闭Service,无法操作Service; 

    bindService()和unbindService()可以操作Service

  • startService开启的Service,调用者退出后Service仍然存在; 
    BindService开启的Service,调用者退出后,Service随着调用者销毁。

4.BroadCast 

4.1 广播的定义

【说明】

【1】广播实现了不同程序之间的数据的共享,只要和发送广播的action相同的接收者都可以接收到广播,发送一个广播可以被许多的广播receiver接收;

【2】广播可以具有通知的作用;activity可以通过广播接收者直接接收从service中发送的数据,而不要让service和activity直接进行操作

4.2 广播使用的场景

 

4.3 广播的种类

4.4 广播的接收者

【说明】有两种注册的方式:

【静态注册】注册完成后就会一直运行,即使是注册的activity已经销毁了(进程杀死了),但是仍然在一直运行,仍然可以收到广播;是在Androidmenifest.xml中声明的;

【动态注册】跟随Activity的生命周期,一旦activity的生命周期结束,则动态注册的广播接收者也消失

 [注意]在动态注册之后的记得在activity的onDestory中unRegisterBroadcastReceiver(),否则容易引起内存泄露;

【区别】

【1】注册方式不同;

【2】生命周期不同;

【源码查看】

5.WebView 

【说明】

 

5.1 WebView 任意代码执行漏洞

出现该漏洞的原因有三个:

  • WebView 中 addJavascriptInterface() 接口
  • WebView 内置导出的 searchBoxJavaBridge_对象
  • WebView 内置导出的 accessibility 和 accessibilityTraversalObject 对象

 addJavascriptInterface 接口引起远程代码执行漏洞

A. 漏洞产生原因

JS调用Android的其中一个方式是通过addJavascriptInterface接口进行对象映射:

 webView.addJavascriptInterface(new JSObject(), "myObj");
// 参数1:Android的本地对象
// 参数2:JS的对象
// 通过对象映射将Android中的本地对象和JS中的对象进行关联,从而实现JS调用Android的对象和方法

所以,漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行

如可以执行命令获取本地设备的SD卡中的文件等信息从而造成信息泄露

具体获取系统类的描述:(结合 Java 反射机制)

  • Android中的对象有一公共的方法:getClass() ;
  • 该方法可以获取到当前类 类型Class
  • 该类有一关键的方法: Class.forName;
  • 该方法可以加载一个类(可加载 java.lang.Runtime 类)
  • 而该类是可以执行本地命令的5.2 WebView在布局文件中的使用

 

应对方案:

APP研发者:
1. 确保只在访问可信页面数据时才使用addjavascriptInterface。 
2. 在调用Java对象方法前对参数进行检查,避免执行恶意操作。
3. 对于在4.2(API 17+)系统运行的应用,使用JavascriptInterface代替addjavascriptInterface
4. 限制对于该接口的使用来源,只允许可信来源访问该接口。例如使用WebViewClient中的shouldOverrideUrlLoading()来对加载的URL进行检查。

APP使用者:
1. 关注应用厂商更新情况,尽快升级应用程序到最新版本。
2. 在厂商修补前,用户应尽量避免使用应用浏览不可信的网页链接和邮件。

Android 官方:
Android官方已提醒此功能是有安全风险的,在可能访问不可信网页内容时需要小心处理。
Android 4.2 (api 17)已经开始采用JavascriptInterface代替addjavascriptInterface。

 

解决方案

1,Android 4.2以上的系统
在Android 4.2以上的,google作了修正,通过在Java的远程方法上面声明一个@JavascriptInterface,如下面代码:
  1. class JsObject {  
  2.    @JavascriptInterface  
  3.    public String toString() { return "injectedObject"; }  
  4. }  
  5. webView.addJavascriptInterface(new JsObject(), "injectedObject");  
  6. webView.loadData("", "text/html", null);  
  7. webView.loadUrl("javascript:alert(injectedObject.toString())");  
2,Android 4.2以下的系统
这个问题比较难解决,但也不是不能解决。
首先,我们肯定不能再调用addJavascriptInterface方法了。关于这个问题,最核心的就是要知道JS事件这一个动作,JS与Java进行交互我们知道,有以下几种,比prompt, alert等,这样的动作都会对应到WebChromeClient类中相应的方法,对于prompt,它对应的方法是onJsPrompt方法,这个方法的声明如下:
 
  1. public boolean onJsPrompt(WebView view, String url, String message,   
  2.     String defaultValue, JsPromptResult result)  
通过这个方法,JS能把信息(文本)传递到Java,而Java也能把信息(文本)传递到JS中,通知这个思路我们能不能找到解决方案呢?
经过一番尝试与分析,找到一种比较可行的方案,请看下面几个小点:
【1】让JS调用一个Javascript方法,这个方法中是调用prompt方法,通过prompt把JS中的信息传递过来,这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等。在onJsPrompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到Java对象的方法。
【2】关于返回值,可以通过prompt返回回去,这样就可以把Java中方法的处理结果返回到Js中。
【3】我们需要动态生成一段声明Javascript方法的JS脚本,通过loadUrl来加载它,从而注册到html页面中,具体的代码如下:
 
  1. javascript:(function JsAddJavascriptInterface_(){  
  2.     if (typeof(window.jsInterface)!='undefined') {      
  3.         console.log('window.jsInterface_js_interface_name is exist!!');}   
  4.     else {  
  5.         window.jsInterface = {          
  6.             onButtonClick:function(arg0) {   
  7.                 return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]}));  
  8.             },  
  9.               
  10.             onImageClick:function(arg0,arg1,arg2) {   
  11.                 prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]}));  
  12.             },  
  13.         };  
  14.     }  
  15. }  
  16. )()  
说明:
1,上面代码中的jsInterface就是要注册的对象名,它注册了两个方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2),如果有返回值,就添加上return。
2,prompt中是我们约定的字符串,它包含特定的标识符MyApp:,后面包含了一串JSON字符串,它包含了方法名,参数,对象名等。
3,当JS调用onButtonClick或onImageClick时,就会回调到Java层中的onJsPrompt方法,我们再解析出方法名,参数,对象名,再反射调用方法。
4,window.jsInterface这表示在window上声明了一个Js对象,声明方法的形式是:方法名:function(参数1,参数2) 
 

5 一些思考

以下是在实现这个解决方案过程中遇到的一些问题和思考:
【1】生成Js方法后,加载这段Js的时机是什么?
刚开始时在当WebView正常加载URL后去加载Js,但发现会存在问题,如果当WebView跳转到下一个页面时,之前加载的Js就可能无效了,所以需要再次加载。这个问题经过尝试,需要在以下几个方法中加载Js,它们是WebChromeClient和WebViewClient的方法:
  • onLoadResource
  • doUpdateVisitedHistory
  • onPageStarted
  • onPageFinished
  • onReceivedTitle
  • onProgressChanged
目前测试了这几个地方,没什么问题,这里我也不能完全确保没有问题。
 
【2】需要过滤掉Object类的方法
由于通过反射的形式来得到指定对象的方法,他会把基类的方法也会得到,最顶层的基类就是Object,所以我们为了不把getClass方法注入到Js中,所以我们需要把Object的公有方法过滤掉。这里严格说来,应该有一个需要过滤方法的列表。目前我的实现中,需要过滤的方法有:
        "getClass",
        "hashCode",
        "notify",
        "notifyAll",
        "equals",
        "toString",
        "wait",
 
【3】通过手动loadUrl来加载一段js,这种方式难道js中的对象就不在window中吗?也就是说,通过遍历window的对象,不能找到我们通过loadUrl注入的js对象吗?
关于这个问题,我们的方法是通过Js声明的,通过loadUrl的形式来注入到页面中,其实本质相当于把我们这动态生成的这一段Js直接写在Html页面中,所以,这些Js中的window中虽然包含了我们声明的对象,但是他们并不是Java对象,他们是通过Js语法声明的,所以不存在getClass之类的方法。本质上他们是Js对象。
 
【4】在Android 3.0以下,系统自己添加了一个叫searchBoxJavaBridge_的Js接口,要解决这个安全问题,我们也需要把这个接口删除,调用removeJavascriptInterface方法。这个searchBoxJavaBridge_好像是跟google的搜索框相关的。
 
【5】在实现过程中,我们需要判断系统版本是否在4.2以下,因为在4.2以上,Android修复了这个安全问题。我们只是需要针对4.2以下的系统作修复。

 

【说明】一般的布局会将WebView动态的addView()添加到布局中,当离开的时候,需要销毁webView;

   在Activity的OnDestory()方法中,一定要先将布局中的webView先remove();然后再调用WebView的removeAllViews();再销毁WebView;

 

 5.3 jsBridge

什么是JSBridge
JSBridge:听其取名就是js和Native之前的桥梁,而实际上JSBridge确实是JS和Native之前的一种通信方式
简单的说,JSBridge就是定义Native和JS的通信,Native只通过一个固定的桥对象调用JS,JS也只通过固定的桥对象调用Native。

5.4 网页加载完毕之后的数据

WebViewClient.onPageFinished()。你永远无法确定当WebView调用这个方法的时候,网页内容是否真的加载完毕了。
当前正在加载的网页产生跳转的时候这个方法可能会被多次调用,StackOverflow上有比较具体的解释
(How to listen for a Webview finishing loading a URL in Android?), 但其中列举的解决方法并不完美。
所以当你的WebView需要加载各种各样的网页并且需要在页面加载完成时采取一些操作的话,
可能WebChromeClient.onProgressChanged()比WebViewClient.onPageFinished()都要靠谱一些。

 5.5 耗电

WebView后台耗电问题。当你的程序调用了WebView加载网页,WebView会自己开启一些线程(?),
如果你没有正确地将WebView销毁的话,这些残余的线程(?)会一直在后台运行,由此导致你的应用程序耗电量居高不下。
对此我采用的处理方式比较偷懒,简单又粗暴(不建议),
即在Activity.onDestroy()中直接调用System.exit(0)
使得应用程序完全被移出虚拟机,这样就不会有任何问题了。

 5.6 硬件加速导致的页面渲染问题

切换WebView闪屏问题。如果你需要在同一个ViewGroup中来回切换不同的WebView(包含了不同的网页内容)的话,你就会发现闪屏是不可避免的。
这应该是Android硬件加速的Bug,如果关闭硬件加速这种情况会好很多,但无法获得很好的浏览体验,你会感觉网页滑动的时候一卡一卡的,不跟手。

5.7 WebView的内存泄露问题

 

【参考文章】https://www.jianshu.com/p/3e8f7dbb0dc7

(方法4划重点)。
Android混合开发时经常用到WebView加载html等页面,而WebView的内存泄漏就是最经常遇到的问题,尤其是当项目中需要用webview加载的页面比较多时。

即使当我退出页面时在我的BrowserActivity的onDestroy()方法中进行内存占用回收(如下图)但并没有效果:

mWebView.removeAllViews();
mWebView.destroy();
mWebView=null;

当我点开了多少条新闻内存中就存在多少个BrowserActivity的实例,说明我退出时这个BrowserActivity没有被回收,这样的话当我浏览的新闻比较多时,

内存就会累积存在一定的OOM风险,而且新闻界面一般存在大量图片,所以这个问题是必须要解决的。

1. new一个而不是在.xml中定义webview节点

attention:最初在写这篇的时候这一小节可能写的不够严谨,要是造成误解真是抱歉;
写这篇小结时的目的也是想把知道的一些解决方法记下来方便自己查看,没想到能收到评论和质疑,
我还是很开心的,但是通过这个我也发现,发出来的东西还是要写的严谨一些,会慢慢改进的。所以这一小节重新说明了一下,要是有不对的地方还是欢迎大家拍砖。

不要在布局文件中定义webview的节点,而是在需要的时候动态生成。你可以在需要webview的布局位置放一个LinearLayout,需要时在代码中动态生成webview并add进去:

//mWebView=new WebView(this);
mWebView=new WebView(getApplicationContext());
LinearLayout linearLayout  = findViewById(R.id.xxx);
linearLayout.addView(mWebView);

然后在onDestroy()方法中调用:

@Override
protected void onDestroy() {
    if( mWebView!=null) {
       mWebView.setVisibility(View.GONE);
       mWebView.removeAllViews();
       mWebView.destroy();
    }
    super.onDestroy();
}

tips: 关于创建webview时new WebView(...);到底是传入ApplicationContext还是Activity的context,说法不一,但是网上较为一致的观点是采用application的context。
传ApplicationContext貌似可以防止webview对activity的引用而造成的内存泄漏;但是在很多情况下会报错,但是这个出错应该是webview的某些特殊动作产生由Application到Activity的类型转换错误;
采用activity的context细想来貌似和在xml中直接定义没有什么区别;

2. 手动删除引用

这个方法在我的项目中没有效果,但原文博主说在他的项目中效果很好,也许对其他人的情况有效,在这里也记下来。

public void setConfigCallback(WindowManager windowManager) {
    try {
        Field field = WebView.class.getDeclaredField("mWebViewCore");
        field = field.getType().getDeclaredField("mBrowserFrame");
        field = field.getType().getDeclaredField("sConfigCallback");
        field.setAccessible(true);
        Object configCallback = field.get(null);

        if (null == configCallback) {
            return;
        }

        field = field.getType().getDeclaredField("mWindowManager");
        field.setAccessible(true);
        field.set(configCallback, windowManager);
    } catch(Exception e) {
    }
}

然后在activity中调用:

   public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setConfigCallback(WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
    }

    public void onDestroy() {
        setConfigCallback(null);
        super.onDestroy();
    }

3. 进程

为加载WebView的界面开启新进程,在该页面退出之后关闭这个进程
这个方法我没有测试,不知道应用和效果如何,有兴趣的可以试试。

4. 从根源解决(划重点)

前面的方法都没有解决我内存泄漏的问题,然后我看到了一篇文章是从源码角度分析了webview内存泄漏的原因,最后按作者的方法解决了问题,后面会贴上原文地址。

这里简单说一下:
原文里说的webview引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。

org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;

系统会在attach和detach处进行注册和反注册component callback;
在onDetachedFromWindow() 方法的第一行中:

if (isDestroyed()) return;, 

如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作;我们的activity退出的时候,

都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;

destroy()的执行时间又在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。
然后解决方法就是:让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉。

ViewParent parent = mWebView.getParent();
if (parent != null) {
    ((ViewGroup) parent).removeView(mWebView);
}

mWebView.destroy();

完整的activity的onDestroy()方法:

@Override
protected void onDestroy() {
    if( mWebView!=null) {

        // 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
        // destory()
        ViewParent parent = mWebView.getParent();
        if (parent != null) {
            ((ViewGroup) parent).removeView(mWebView);
        }

        mWebView.stopLoading();
        // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.clearView();
        mWebView.removeAllViews();
        mWebView.destroy();

    }
    super.on Destroy();
}

这个方法亲测有效。
原文地址:http://blog.csdn.net/xygy8860/article/details/53334476?utm_source=itdadao&utm_medium=referral

附上检查内存泄漏的工具:leakcanary

6.Binder

【用打电话原理进行通信的比喻】只限于通信,在通信之前需要注册;

【完整的流程】

【跨进程的模型】

【什么是Binder?】

【asInterface方法功能】如果是跨进程就是用代理对象,如果是同一个进程就使用本地的对象;

posted @ 2018-05-21 20:14  OzTaking  阅读(364)  评论(0)    收藏  举报