android产品研发(十七)-->Hybird开发

转载请标明出处:一片枫叶的专栏

上一篇文章中我们介绍了android开发中经常会涉及到但又常常被忽视掉的开发者模式。主要讲解了包括如何打开手机的开发者模式,开发者模式中各个菜单的意义和作用,如何清除手机App数据,以及清除手机App数据具体清除那些数据等知识点,具体关于android中开发者模式的知识,可参考我的:android产品研发(十六)–>开发者选项

本文将介绍android中hybird开发相关的知识点。hybird开发实际上是混合开发的意思,这里的混合是H5开发与Native开发混合的意思。下面的文章中我们将逐个介绍一下hybird开发的概念、hybird开发的优势、android中如何实现hybird开发、简单的hybird开发的例子,以及在产品实践中对hybird开发的应用,希望通过本篇文章的介绍让您能够对android中的hybird开发有一个基本的认识。

一:hybird开发的概念

在具体介绍hybird开发之前,我们先看一下什么是hybird开发,在这里我们先引用一下百度百科中对hybird开发的定义:

Hybrid App(混合模式移动应用)是指介于web-app、native-app这两者之间的app,兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。

从定义中我们可以看到hybird开发其实就是在App开发过程中既使用到了web开发技术也使用到了native开发技术,通过这两种技术混合实现的App就是我们通常说的hybird app,而通过这两种技术混合开发就是hybird开发。

好吧,我们已经知道hybird开发的具体含义,那么一个问题就产生了,既然我们已经有了native开发了为何还需要hybird开发呢?它有什么好处么?答案是肯定的,下面我们就来看一下为何需要hybird开发方式。

二:为何需要hybird开发

下面我们简单看一下Native开发中存在的弊端以及使用Hybird开发方式的好处,通过对比你就能知道了hybird开发的优势,当然了,这里不是推崇使用hybird开发方式,native也有native开发的优势,hybird开发也有hybird开发的劣势,这里只是简单的看一下hybird相对于native开发的优势。

  • 使用Native开发的方式人员要求高,只是一个简单的功能就需要IOS程序员和Android程序员各自完成;
  • 使用Native开发的方式版本迭代周期慢,每次完成版本升级之后都需要上传到App Store并审核,升级,重新安装等,升级成本高;
  • 使用Hybird开发的方式简单方便,同一套代码既可以在IOS平台使用,也可以在android平台使用,提高了开发效率与代码的可维护性;
  • 使用Hybird开发的方式升级简单方便,只需要服务器端升级一下就好了,对用户而言完全是透明了,免去了Native升级中的种种不便;

通过对比可以发现hybird开发方式现对于native实现主要的优势就是更新版本快,代码维护方便,当然了这两个优点也是我们推崇使用hybird开发app的主要因素。知道了hybird开发的好处之后,我们如何在android中实现hybird开发呢?下面我们就将介绍这个问题。

三:android中如何实现Bybird开发

其实在android开发中使用hybird模式开发app,也是有两种方案的:

  • 使用第三方hybird框架
  • 自己使用webview加载

通过这两种方案实现Hybird开发各有利弊,具体如下:

  • 使用PhoneGap、AppCan之类的第三方框架,其实现的原理是以WebView作为用户界面层,以Javascript作为基本逻辑,以及和中间件通讯,再由中间件访问底层API的方式,进行应用开发。相当于为我们封装了webview与相应的native组件;
  • 使用webview控件加载H5网页的内容,其中客户端的webview只是作为一个加载H5页面的壳子,具体的实现效果是由H5实现的,这个需要Native程序员和H5程序员一起合作完成;
  • 使用第三方框架的方式的好处是许多功能已经被集成好了,只需要简单的调用即可,但是这种方式集成度高,不容易定制化处理,而且性能上也是一个打的问题;
  • 使用webview加载H5页面,定制化程度高,问题可控,但是相对与第三方框架集成度不够高,但是其已经可以满足我们日常的开发功能需要了,目前还是比较推荐使用这种方式实现Hybird开发;

下面我们就看一下如何在android系统中通过webview实现对H5页面的加载操作。

四:Hybird开发简单实现

  • 在AndroidManifest.xml中定义网络请求权限
<uses-permission android:name="android.permission.INTERNET"/>

注意这个权限是必须的,因为加载webview页面一般而言经常是网络上的H5页面,这时候的网络请求权限就是必须的了,好多时候测试webview加载网络H5页面失败,找了半天不知道是什么原因,最后才发现是网络权限没有添加…

  • 在Layout布局文件中定义Webview控件
<WebView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/webView"
    />

这里的WebView控件就是android原生的webview控件了,其和普通的android控件的使用没有什么不同都是在布局文件中定义,然后在Activity代码中获取并执行初始化操作等等。

  • 在代码中获取Webview控件加载本地或者网络H5资源

加载本地H5页面

/**
 * 加载本地H5资源文件
 */
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("file:///android_asset/example.html");

加载网络H5页面

/**
 * 加载网络H5资源
 */
webView = (WebView) findViewById(R.id.webView);
webView.loadUrl("http://baidu.com");

可以发现在获取到webview组件之后直接执行一个loadUrl方法传入一个url地址就可以了,这样在activity页面中就可以展示出webview页面了,契合普通的网页效果没什么不同,这里需要说明的是,webview不但能够加载网页地址,同样的也可以加载html代码,本地html资源等等,相对来说功能还是很强大的。

当然了以上只是最最简单的webview使用的例子,下面我们可以为我们的webview对象设置各种参数:

  • 为Webview控件设置参数
WebSettings webSettings = h5Fragment.mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setAllowFileAccess(false);
        webSettings.setUseWideViewPort(false);
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        webSettings.setDatabaseEnabled(false);
        webSettings.setAppCacheEnabled(false);
        webSettings.setBlockNetworkImage(true);

这里的WebSettings就是webview的设置参数对象,我们是通过它为webview设置各种参数值的,见名知意,看见名字我们就知道各个set方法的意思了。比如设置webview中的html页面js代码是否可用,是否可以访问系统文件,H5缓存是否可用,是否立即加载网页图片等等。

  • 为Webview控件设置WebChromeClient

WebChromeClient对象是webview的关于页面效果回调方法的实现对象,主要用于实现webview页面上一些效果的回调,我们可以看一下其中实现的一些回调方法:

/**
 * 自定义实现WebChromeClient对象
 */
public class MWebChromeClient extends WebChromeClient{

    /**
     * 当webview加载进度变化时回调该方法
     */
    @Override
    public void onProgressChanged(WebView view, int newProgress) {
        super.onProgressChanged(view, newProgress);
    }

    /**
     * 当加载到H5页面title的时候回调该方法
     */
    @Override
    public void onReceivedTitle(WebView view, String title) {
        super.onReceivedTitle(view, title);
    }

    /**
     * 当接收到icon的时候回调该方法
     */
    @Override
    public void onReceivedIcon(WebView view, Bitmap icon) {
        super.onReceivedIcon(view, icon);
    }

    /**
     * 当H5页面调用js的Alert方法的时候回调该方法
     */
    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        return super.onJsAlert(view, url, message, result);
    }

    /**
     * 当H5页面调用js的Confirm方法的时候回调该方法
     */
    @Override
    public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
        return super.onJsConfirm(view, url, message, result);
    }

    /**
     * 当H5页面调用js的Prompt方法的时候回调该方法
     */
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        return super.onJsPrompt(view, url, message, defaultValue, result);
    }
}

上面的WebChromeClient中我们重写了其中的几个字方法,我们已经在方法中添加了注释标明了各个方法的调用时机,而且通过方法名我们也不难发现各个方法的具体作用,这里就不在具体的介绍了。

  • 为Webview主要设置WebviewClient
/**
 * 自定义实现WebViewClient类
 */
public class MWebViewClient extends WebViewClient {

    /**
     * 在webview加载URL的时候可以截获这个动作, 这里主要说它的返回值的问题:
     *  1、返回: return true;  webview处理url是根据程序来执行的。
     *  2、返回: return false; webview处理url是在webview内部执行。
     */
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {

    }

    /**
     * 在webview开始加载页面的时候回调该方法
     */
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);

    }

    /**
     * 在webview加载页面结束的时候回调该方法
     */
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
    }

    /**
     * 加载页面失败的时候回调该方法
     */
    // 该方法为android23中新添加的API,android23中会执行该方法
    @TargetApi(21)
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {

    }

    /**
     * 加载页面失败的时候回调该方法
     */
    /**
     * 在android23中改方法被onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) 替代
     * 因此在android23中执行替代方法
     * 在android23之前执行该方法
     * @param view
     * @param errorCode
     * @param description
     * @param failingUrl
     */
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {

    }
}

这里我们只是暂时看一下WebViewClient中的几个比较重要的方法,shouldOverrideUrlLoading方法,onPageStarted方法,onPageFinished方法,onReceivedError方法等,相关的方法说明已经有注释了,这里就不在做过多的说明了。好了介绍完了相关的API之后我们来看一下我们在产品中关于Hybird开发的实践。

  • 友友用车中关于Hybird开发的实践

Hybird这么高逼格的东西友友用车怎么能不涉及呢?在我们的产品开发中也使用到了Webview,并封装了自己的Webview库,下面我们就看一下友友用车中关于Hybird开发的实践。

(1)定义H5Activity类,用于展示H5页面

/**
 * 自定义实现的H5Activity类,主要用于在页面中展示H5页面,整个Activity只有一个Fragment控件
 */
public class H5Activity extends BaseActivity {

    public H5Fragment h5Fragment = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_h5);
        h5Fragment = new H5Fragment();
        getSupportFragmentManager().beginTransaction().replace(R.id.mfl_content_container, h5Fragment).commit();
    }
}

(2)在H5Fragment中具体实现对H5页面的加载操作

/**
 * 具体实现H5页面加载Fragment,只有一个Webview控件
 */
public class H5Fragment extends BaseFragment implements SwipeRefreshLayout.OnRefreshListener {

    @BindView(R.id.sswipeRefreshLayout)
    public SwipeRefreshLayout swipeRefreshLayout;
    /**
     * H5页面 WebView
     */
    @BindView(R.id.mwebview)
    public WebView mWebView = null;
    @BindView(R.id.rl)
    public RelativeLayout rl;
    /**
     * 页面title
     */
    public String title = "";
    /**
     * 页面当前URL
     */
    public String currentUrl = "";
    /**
     * 判断网页是否加载成功
     */
    public boolean isSuccess = true;
    /**
     * 判断前一页H5是否需要刷新
     */
    public boolean isNeedFlushPreH5 = false;

    private BasePayFragmentUtils payFragmentUtils;

    public static final String KEY_DIALOG_WEB_VIEW = "dialog_webView";
    /**
     * 是否是弹窗中的WebView
     */
    private boolean isDialogWebView = false;

    View.OnClickListener errorOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mProgressLayout.showLoading();
            isSuccess = true;
            reflush();
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        payFragmentUtils = new BasePayFragmentUtils(this, BasePayFragmentUtils.ORDER_TYPE_H5);

        Bundle bundle = getArguments();
        if (bundle != null && bundle.containsKey(KEY_DIALOG_WEB_VIEW)) {
            isDialogWebView = bundle.getBoolean(KEY_DIALOG_WEB_VIEW, false);
        }
    }

    @Override
    public View setView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_h5, null);
        ButterKnife.bind(this, rootView);

        if (getActivity() instanceof H5Activity) {
            H5Activity h5Activity = (H5Activity) getActivity();
            mProgressLayout = h5Activity.mProgressLayout;
        }

        initView();
        initData();
        return rootView;
    }

    @Override
    public void onResume() {
        super.onResume();
        payFragmentUtils.onPayResume();
        if (H5Constant.isNeedFlush == true || isNeedFlushPreH5 == true) {
            H5Constant.isNeedFlush = false;
            isNeedFlushPreH5 = false;
            // 加载数据
            initData();
        }
    }

    /**
     * 执行组件初始化的操作
     */
    private void initView() {
        // 判断下拉刷新组件是否可用
        isSwipeEnable();
        // 初始化WebView组件
        H5FragmentUtils.initH5View(this);
        // 设置WebView的Client
        mWebView.setWebViewClient(new MWebViewClient(this));
        // 设置可现实js的alert弹窗
        mWebView.setWebChromeClient(new WebChromeClient());

        if (isDialogWebView) {
            mProgressLayout.setCornerResId(R.drawable.map_confirm_bg);
        }
    }

    /**
     * 执行初始化加载数据的操作
     */
    private void initData() {
        mProgressLayout.showLoading();
        // 设置title
        H5FragmentUtils.setTitle(this, title);
        // 获取请求URL
        currentUrl = H5FragmentUtils.getUrl(this, currentUrl);
        // 刷新页面
        reflush();
    }

    /**
     * 判断下拉刷新组件是否可用
     */
    private void isSwipeEnable() {
        if (getActivity() == null) {
            return;
        }

        if (isDialogWebView) {
            getActivity().getIntent().putExtra(H5Constant.CARFLUSH, false);
        }
        //判断滑动组件是否可用
        if (getActivity().getIntent().getBooleanExtra(H5Constant.CARFLUSH, true)) {
            swipeRefreshLayout.setEnabled(true);
            swipeRefreshLayout.setColorSchemeResources(R.color.c1, R.color.c1, R.color.c1);
            swipeRefreshLayout.setOnRefreshListener(this);
            mWebView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getAction() == MotionEvent.ACTION_DOWN) {
                        int downY = (int) event.getY();
                        if (downY <= DisplayUtil.screenhightPx / 3) {
                            swipeRefreshLayout.setEnabled(true);
                        } else {
                            swipeRefreshLayout.setEnabled(false);
                        }
                    }
                    return false;
                }
            });
        } else {
            swipeRefreshLayout.setEnabled(false);
        }
    }

    /**
     * 执行Webview的下拉刷新操作
     */
    @Override
    public void onRefresh() {
        //判断是否执行刷新动作
        reflush();
    }

    /**
     * 刷新当前页面
     */
    private void reflush() {
        if (Config.isNetworkConnected(mContext)) {
            if (!TextUtils.isEmpty(currentUrl)) {
                H5Cookie.synCookies(mContext, currentUrl, H5Cookie.getToken());
                mWebView.loadUrl(currentUrl);
            } else {
                swipeRefreshLayout.setRefreshing(false);
                mProgressLayout.showError(errorOnClickListener);
            }
        } else {
            swipeRefreshLayout.setRefreshing(false);
            mProgressLayout.showError(errorOnClickListener);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        /*swipeRefreshLayout.removeView(mWebView);
        mWebView.removeAllViews();
        mWebView.destroy();*/
    }
}

(3)初始化WebView组件

/**
     * 初始化组件WebView
     *
     * @param h5Fragment
     */
    public static void initH5View(H5Fragment h5Fragment) {
        if (h5Fragment == null || h5Fragment.getActivity() == null) {
            return;
        }

        if (h5Fragment.getActivity().getIntent().getBooleanExtra(H5Constant.SOFT_INPUT_IS_CHANGE_LAYOUT, false)) {
            h5Fragment.getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
        }
        // 设置H5页面默认能够长按复制
        /*if (!h5Fragment.getActivity().getIntent().getBooleanExtra(H5Constant.OPENLONGCLICK, false)) {

            h5Fragment.mWebView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return true;
                }
            });
        }*/
        WebSettings webSettings = h5Fragment.mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setLoadWithOverviewMode(true);
        webSettings.setAllowFileAccess(false);
        webSettings.setUseWideViewPort(false);
        webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
        webSettings.setDatabaseEnabled(false);
        webSettings.setAppCacheEnabled(false);
        webSettings.setBlockNetworkImage(true);
    }

(4)自定义实现WebviewClient对象

/**
 * 自定义实现WebviewClient类
 */
public class MWebViewClient extends WebViewClient {

    public H5Fragment h5Fragment = null;
    public Activity h5Activity = null;

    public MWebViewClient(H5Fragment h5Fragment) {
        this.h5Fragment = h5Fragment;
        if (h5Fragment.getActivity() == null) {
            h5Activity = Config.currentContext;
        } else {
            h5Activity = h5Fragment.getActivity();
        }
    }

    /**
     * 拦截H5页面的a标签跳转,解析scheme协议
     * 相当于放弃了a标签的使用,转而使用自定义的scheme协议
     */
    @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);
                }
                // 解析SCHEME跳转
                {
                    // 若设置刷新,则刷新页面
                    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;
        }
    }

    /**
     * H5页面刚刚开始被webview加载时回调该方法
     */
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);

    }

    /**
     * H5页面结束被加载时回调该方法
     */
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        h5Fragment.swipeRefreshLayout.setRefreshing(false);
        if (h5Activity.getTitle().toString().equals("找不到网页")) {
            h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);
            return;
        }
        if (h5Fragment.isSuccess)
            h5Fragment.mProgressLayout.showContent();
        else
            h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);

        h5Fragment.onLoadFinish(h5Fragment.isSuccess);
        if (h5Fragment.isSuccess) {
            h5Fragment.mWebView.getSettings().setBlockNetworkImage(false);
        }
    }

    // 该方法为android23中新添加的API,android23中会执行该方法
    @TargetApi(21)
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        if (Build.VERSION.SDK_INT >= 21) {
            if (request.isForMainFrame()) {
                h5Fragment.isSuccess = false;
                h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);
            }
        }
    }

    /**
     * 在android23中改方法被onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) 替代
     * 因此在android23中执行替代方法
     * 在android23之前执行该方法
     * @param view
     * @param errorCode
     * @param description
     * @param failingUrl
     */
    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        if (Build.VERSION.SDK_INT < 23) {
            h5Fragment.isSuccess = false;
            h5Fragment.mProgressLayout.showError(h5Fragment.errorOnClickListener);
        }
    }
}

(5)打开H5页面

Intent intent = new Intent(context, H5Activity.class);
        intent.putExtra(H5Constant.MURL, currentUrl);
        intent.putExtra(H5Constant.TITLE, title);
        context.startActivity(intent);

可以发现在产品实际开发过程中使用webview的页面都是整个的Activity页面,也就是说整个Activity页面只有一个webview控件,所以这时候页面的内容都是通过H5实现的。

然后当我们需要打开H5页面的时候可以通过服务器下发H5页面的url和title,并作为参数传递给H5Activity,然后打开该url所表示的网页。

同时我们使用Fragment用于实现加载H5页面的所以,所以以后当我们需要在其他地方使用加载H5页面的时候可以很方便的一直。

在MWebviewClient的shouldOverrideUrlLoading方法中我们拦截了所有的a标签跳转,转而实现我们自身的scheme协议,即a标签的跳转链接不再是常规的http链接,而是我们自定义的scheme协议,具体可参考:android产品研发(十一)–>应用内跳转scheme协议

总结:

  • 本文中我们介绍了Hybird开发的概念,hybird开发的作用,android中如何实现hybird开发,android实现hybird的例子,产品中对hybird开发的实践
  • 在定义webview的时候可以设置WebviewSettings,设置WebviewClient,设置WebChromeClient等参数对象
  • 可以在WebviewClient的shouldOverrideUrlLoading方法中拦截a标签的跳转并执行相应的逻辑

另外对产品研发技术,技巧,实践方面感兴趣的同学可以参考我的:

android产品研发(一)–>实用开发规范

android产品研发(二)–>启动页优化

android产品研发(三)–>基类Activity

android产品研发(四)–>减小Apk大小

android产品研发(五)–>多渠道打包

android产品研发(六)–>Apk混淆

android产品研发(七)–>Apk热修复

android产品研发(八)–>App数据统计

android产品研发(九)–>App网络传输协议

android产品研发(十)–>不使用静态变量保存数据

android产品研发(十一)–>应用内跳转scheme协议

android产品研发(十二)–>App长连接实现

android产品研发(十三)–>App轮训操作

android产品研发(十四)–>App升级与更新

android产品研发(十五)–>内存对象序列化

android产品研发(十六)–>开发者选项



本文以同步至github中:https://github.com/yipianfengye/androidProject,欢迎star和follow


时间: 2024-11-07 17:25:42

android产品研发(十七)-->Hybird开发的相关文章

android产品研发--&gt;总结(持续更新中)

转载请标明出处:一片枫叶的专栏 最近的android产品研发系列主要讲解的是android产品研发过程中涉及到的技术,技巧,实践等.前面我们讲解了android源码系列的文章,源码系列的文章东西比较多比较复杂,并且一些东西还没有讲完,这里已经更新了30篇了,后续的东西一定会更新的.考虑一直讲源码系列可能看的比较累,这里就有了产品研发系列的文章.本个系列的文章主要是讲解android产品研发过程中一些需要注意的技术技巧与实践.其主要面对产品研发,对App稳定性,友好型,兼容性要求较高的App. 下

android产品研发(一)--&gt;实用开发规范

从这篇文章开始我们暂停一下对android源码的分析,开始讲一下android产品研发中一些常用的技术,技巧,方法,实践等姿势.这里需要强调的是我们所讲解的这些东西可能对产品开发中比较常用的,因为对于项目开发中,可能更多的强调管理,进度方法的东西,对工程化的东西比较强调,而我们这里更多的是对产品技术方面的归纳总结. 而本文中选择将开发规范作为这个系列的第一篇文章,就是个人感觉产品研发过程中,开发规范真的很重要,很重要,非常重要(重要的事情说三遍),一个好的开发规范可以让团队中的人对他人的代码更熟

android产品研发(二十一)--&gt;UI优化

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android产品研发过程中的代码Review.通过代码Review能够提高产品质量,增强团队成员之间的沟通,提高开发效率,所以良好的产品开发迭代过程中,代码Review是一个必不可少的步骤.那么如何进行代码Review呢?我们主要讲解了团队成员之间的代码Review,代码lint检查,开发规范等方面的知识点,更多关于代码Review相关的知识可参考我的:android产品研发(二十)–>代码Review 本文我们将讲解一下android U

android产品研发(二十二)--&gt;android实用调试技巧

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android UI优化方面的知识.我们讲解了android中的include.marge.ViewStub标签,在使用这些标签时可以简化我们的布局文件,优化组件绘制流程:讲解了android中的过度绘制相关知识点,通过优化我们的App过度绘制可以提高App的UI绘制流程与性能:我们还讲解了App中一些UI优化的小tips.更多关于android UI优化方面的知识可以参考我的:android产品研发(二十一)–>android中的UI优化

android产品研发(十九)--&gt;android studio中的单元测试

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了webview中问题集锦,讲解了webview的性能优化.webview种入Cookie信息.activity退出的时候清除webview信息报错.如何通过java代码和js代码相互交互.webview如何下载文件以及腾讯的X5浏览服务等知识,这些都是我在使用webview中遇到的问题,难点,实践等,更多关于这些问题的说明,可以参考我的:android产品研发(十八)–>webview趟过的坑 本文我们将讲解如何在android studio

android产品研发(七)--&gt;Apk热修复

转载请标明出处:一片枫叶的专栏 去年一整年android社区中刮过了一阵热修复的风,各大厂商,逼格大牛纷纷开源了热修复框架,恩,产品过程中怎么可能没有bug呢?重新打包上线?成本太高用户体验也不好,咋办?上热修复呗. 好吧,既然要开始上热修复的功能,那么就得调研一下热修复的原理.下面我将分别讲述一下热修复的原理,各大热修复框架的比较,以及自身产品中热修复功能的实践. 热修复的原理 通过更改dex加载顺序实现热修复 最新github上开源了很多热补丁动态修复框架,大致有: HotFix      

android产品研发(六)--&gt;Apk混淆

前面一篇文章中我们讲解了android里面的多渠道打包,对于大型的app来说,几百个上千个渠道包都是很正常的事,所以效率定制化是一件很重要的事.主要讲解了三种多渠道打包方式,并分析了其各自的利弊,在各自产品多渠道打包的时候,可以根据自身的产品需求选择相应的打包方式. 而本文主要讲解Apk的混淆,这里的混淆分为两种代码混淆和资源文件混淆.实际的产品研发中为了防止自己的劳动成果被别人窃取,混淆代码能有效防止apk文件被反编译,进而查看源代码.说来惭愧,作为互联网创业公司的我们也确实对竞品Apk反编译

android产品研发(十)--&gt;不使用静态变量保存数据

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了Android中的几种常见网络协议:xml,json,protobuf等,以及各自的优缺点,一般而言主要我们的App涉及到了网络传输都会有这方面的内容,具体可根据项目的需求确定各自的网络传输协议.这里可参考android产品研发(九)–>App网络传输协议 而本文讲解的其实并不是一个技术方面,而是一个android产品研发过程中的技巧:尽量不使用静态变量保存核心数据.这是为什么呢?这是因为android的进程并不是安全的,包括applicat

android产品研发(十三)--&gt;App轮训操作

转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了android app实现长连接的几种方式,各自的优缺点以及具体的实现,一般而言使用第三方的推送服务已经可以满足了基本的业务需求,当然了若是对技术有追求的可以通过NIO或者是MINA实现自身的长连接服务,但是自己实现的长连接服务一来比较复杂耗时比较多,而且可能过程中有许多坑要填,一般而言推荐使用第三方的推送服务,稳定简单,具体管理长连接部分的模块可参考:android产品研发(十二)–>App长连接实现. 而本文将讲解app端的轮训请求服务,